mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-28 12:35:17 +00:00
commit
c6c1aa60e9
@ -28,8 +28,8 @@ configurations.all {
|
|||||||
exclude module: "commons-logging"
|
exclude module: "commons-logging"
|
||||||
}
|
}
|
||||||
|
|
||||||
def canonicalVersionCode = 376
|
def canonicalVersionCode = 377
|
||||||
def canonicalVersionName = "1.18.6"
|
def canonicalVersionName = "1.19.0"
|
||||||
|
|
||||||
def postFixSize = 10
|
def postFixSize = 10
|
||||||
def abiPostFix = ['armeabi-v7a' : 1,
|
def abiPostFix = ['armeabi-v7a' : 1,
|
||||||
|
@ -86,6 +86,7 @@ import org.thoughtcrime.securesms.sskenvironment.ProfileManager;
|
|||||||
import org.thoughtcrime.securesms.sskenvironment.ReadReceiptManager;
|
import org.thoughtcrime.securesms.sskenvironment.ReadReceiptManager;
|
||||||
import org.thoughtcrime.securesms.sskenvironment.TypingStatusRepository;
|
import org.thoughtcrime.securesms.sskenvironment.TypingStatusRepository;
|
||||||
import org.thoughtcrime.securesms.util.Broadcaster;
|
import org.thoughtcrime.securesms.util.Broadcaster;
|
||||||
|
import org.thoughtcrime.securesms.util.VersionDataFetcher;
|
||||||
import org.thoughtcrime.securesms.util.dynamiclanguage.LocaleParseHelper;
|
import org.thoughtcrime.securesms.util.dynamiclanguage.LocaleParseHelper;
|
||||||
import org.thoughtcrime.securesms.webrtc.CallMessageProcessor;
|
import org.thoughtcrime.securesms.webrtc.CallMessageProcessor;
|
||||||
import org.webrtc.PeerConnectionFactory;
|
import org.webrtc.PeerConnectionFactory;
|
||||||
@ -110,7 +111,6 @@ import javax.inject.Inject;
|
|||||||
import dagger.hilt.EntryPoints;
|
import dagger.hilt.EntryPoints;
|
||||||
import dagger.hilt.android.HiltAndroidApp;
|
import dagger.hilt.android.HiltAndroidApp;
|
||||||
import kotlin.Unit;
|
import kotlin.Unit;
|
||||||
import kotlinx.coroutines.Job;
|
|
||||||
import network.loki.messenger.BuildConfig;
|
import network.loki.messenger.BuildConfig;
|
||||||
import network.loki.messenger.libsession_util.ConfigBase;
|
import network.loki.messenger.libsession_util.ConfigBase;
|
||||||
import network.loki.messenger.libsession_util.UserProfile;
|
import network.loki.messenger.libsession_util.UserProfile;
|
||||||
@ -151,6 +151,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
|
|||||||
@Inject PushRegistry pushRegistry;
|
@Inject PushRegistry pushRegistry;
|
||||||
@Inject ConfigFactory configFactory;
|
@Inject ConfigFactory configFactory;
|
||||||
@Inject LastSentTimestampCache lastSentTimestampCache;
|
@Inject LastSentTimestampCache lastSentTimestampCache;
|
||||||
|
@Inject VersionDataFetcher versionDataFetcher;
|
||||||
CallMessageProcessor callMessageProcessor;
|
CallMessageProcessor callMessageProcessor;
|
||||||
MessagingModuleConfiguration messagingModuleConfiguration;
|
MessagingModuleConfiguration messagingModuleConfiguration;
|
||||||
|
|
||||||
@ -275,6 +276,9 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
|
|||||||
|
|
||||||
OpenGroupManager.INSTANCE.startPolling();
|
OpenGroupManager.INSTANCE.startPolling();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// fetch last version data
|
||||||
|
versionDataFetcher.startTimedVersionCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -287,12 +291,14 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
|
|||||||
poller.stopIfNeeded();
|
poller.stopIfNeeded();
|
||||||
}
|
}
|
||||||
ClosedGroupPollerV2.getShared().stopAll();
|
ClosedGroupPollerV2.getShared().stopAll();
|
||||||
|
versionDataFetcher.stopTimedVersionCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTerminate() {
|
public void onTerminate() {
|
||||||
stopKovenant(); // Loki
|
stopKovenant(); // Loki
|
||||||
OpenGroupManager.INSTANCE.stopPolling();
|
OpenGroupManager.INSTANCE.stopPolling();
|
||||||
|
versionDataFetcher.stopTimedVersionCheck();
|
||||||
super.onTerminate();
|
super.onTerminate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ fun showMuteDialog(
|
|||||||
|
|
||||||
private enum class Option(@StringRes val stringRes: Int, val getTime: () -> Long) {
|
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)),
|
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)),
|
TWO_HOURS(R.string.arrays__mute_for_two_hours, duration = TimeUnit.HOURS.toMillis(2)),
|
||||||
ONE_DAY(R.string.arrays__mute_for_one_day, duration = TimeUnit.DAYS.toMillis(1)),
|
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)),
|
SEVEN_DAYS(R.string.arrays__mute_for_seven_days, duration = TimeUnit.DAYS.toMillis(7)),
|
||||||
FOREVER(R.string.arrays__mute_forever, getTime = { Long.MAX_VALUE });
|
FOREVER(R.string.arrays__mute_forever, getTime = { Long.MAX_VALUE });
|
||||||
|
@ -27,7 +27,11 @@ import org.thoughtcrime.securesms.groups.JoinCommunityFragment
|
|||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class StartConversationFragment : BottomSheetDialogFragment(), StartConversationDelegate {
|
class StartConversationFragment : BottomSheetDialogFragment(), StartConversationDelegate {
|
||||||
|
|
||||||
private val defaultPeekHeight: Int by lazy { (Resources.getSystem().displayMetrics.heightPixels * 0.94).toInt() }
|
companion object{
|
||||||
|
const val PEEK_RATIO = 0.94f
|
||||||
|
}
|
||||||
|
|
||||||
|
private val defaultPeekHeight: Int by lazy { (Resources.getSystem().displayMetrics.heightPixels * PEEK_RATIO).toInt() }
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
package org.thoughtcrime.securesms.conversation.start.newmessage
|
package org.thoughtcrime.securesms.conversation.start.newmessage
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import android.graphics.Rect
|
||||||
|
import android.os.Build
|
||||||
|
import android.view.ViewTreeObserver
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
@ -15,23 +18,31 @@ import androidx.compose.foundation.pager.rememberPagerState
|
|||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.platform.LocalView
|
||||||
|
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.emptyFlow
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.thoughtcrime.securesms.onboarding.ui.ContinuePrimaryOutlineButton
|
import org.thoughtcrime.securesms.conversation.start.StartConversationFragment.Companion.PEEK_RATIO
|
||||||
import org.thoughtcrime.securesms.ui.LoadingArcOr
|
import org.thoughtcrime.securesms.ui.LoadingArcOr
|
||||||
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.theme.ThemeColors
|
|
||||||
import org.thoughtcrime.securesms.ui.theme.LocalColors
|
|
||||||
import org.thoughtcrime.securesms.ui.components.AppBar
|
import org.thoughtcrime.securesms.ui.components.AppBar
|
||||||
import org.thoughtcrime.securesms.ui.components.BorderlessButtonWithIcon
|
import org.thoughtcrime.securesms.ui.components.BorderlessButtonWithIcon
|
||||||
import org.thoughtcrime.securesms.ui.components.MaybeScanQrCode
|
import org.thoughtcrime.securesms.ui.components.MaybeScanQrCode
|
||||||
@ -39,7 +50,13 @@ import org.thoughtcrime.securesms.ui.components.PrimaryOutlineButton
|
|||||||
import org.thoughtcrime.securesms.ui.components.SessionOutlinedTextField
|
import org.thoughtcrime.securesms.ui.components.SessionOutlinedTextField
|
||||||
import org.thoughtcrime.securesms.ui.components.SessionTabRow
|
import org.thoughtcrime.securesms.ui.components.SessionTabRow
|
||||||
import org.thoughtcrime.securesms.ui.contentDescription
|
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.LocalType
|
||||||
|
import org.thoughtcrime.securesms.ui.theme.PreviewTheme
|
||||||
|
import org.thoughtcrime.securesms.ui.theme.SessionColorsParameterProvider
|
||||||
|
import org.thoughtcrime.securesms.ui.theme.ThemeColors
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
private val TITLES = listOf(R.string.enter_account_id, R.string.qrScan)
|
private val TITLES = listOf(R.string.enter_account_id, R.string.qrScan)
|
||||||
|
|
||||||
@ -76,63 +93,113 @@ private fun EnterAccountId(
|
|||||||
callbacks: Callbacks,
|
callbacks: Callbacks,
|
||||||
onHelp: () -> Unit = {}
|
onHelp: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
Column(
|
// the scaffold is required to provide the contentPadding. That contentPadding is needed
|
||||||
modifier = Modifier
|
// to properly handle the ime padding.
|
||||||
.fillMaxSize()
|
Scaffold() { contentPadding ->
|
||||||
.verticalScroll(rememberScrollState())
|
// we need this extra surface to handle nested scrolling properly,
|
||||||
.imePadding()
|
// because this scrollable component is inside a bottomSheet dialog which is itself scrollable
|
||||||
) {
|
Surface(
|
||||||
Column(
|
modifier = Modifier.nestedScroll(rememberNestedScrollInteropConnection()),
|
||||||
modifier = Modifier.padding(vertical = LocalDimensions.current.spacing),
|
color = LocalColors.current.backgroundSecondary
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
) {
|
) {
|
||||||
SessionOutlinedTextField(
|
|
||||||
text = state.newMessageIdOrOns,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(horizontal = LocalDimensions.current.spacing),
|
|
||||||
contentDescription = "Session id input box",
|
|
||||||
placeholder = stringResource(R.string.accountIdOrOnsEnter),
|
|
||||||
onChange = callbacks::onChange,
|
|
||||||
onContinue = callbacks::onContinue,
|
|
||||||
error = state.error?.string(),
|
|
||||||
isTextErrorColor = state.isTextErrorColor
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(LocalDimensions.current.xxxsSpacing))
|
var accountModifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
|
||||||
BorderlessButtonWithIcon(
|
// There is a known issue with the ime padding on android versions below 30
|
||||||
text = stringResource(R.string.messageNewDescription),
|
/// So on these older versions we need to resort to some manual padding based on the visible height
|
||||||
modifier = Modifier
|
// when the keyboard is up
|
||||||
.contentDescription(R.string.AccessibilityId_help_desk_link)
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||||
.padding(horizontal = LocalDimensions.current.mediumSpacing)
|
val keyboardHeight by keyboardHeight()
|
||||||
.fillMaxWidth(),
|
accountModifier = accountModifier.padding(bottom = keyboardHeight)
|
||||||
style = LocalType.current.small,
|
} else {
|
||||||
color = LocalColors.current.textSecondary,
|
accountModifier = accountModifier
|
||||||
iconRes = R.drawable.ic_circle_question_mark,
|
.consumeWindowInsets(contentPadding)
|
||||||
onClick = onHelp
|
.imePadding()
|
||||||
)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(LocalDimensions.current.smallSpacing))
|
Column(
|
||||||
Spacer(Modifier.weight(2f))
|
modifier = accountModifier
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(vertical = LocalDimensions.current.spacing),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
SessionOutlinedTextField(
|
||||||
|
text = state.newMessageIdOrOns,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = LocalDimensions.current.spacing),
|
||||||
|
contentDescription = "Session id input box",
|
||||||
|
placeholder = stringResource(R.string.accountIdOrOnsEnter),
|
||||||
|
onChange = callbacks::onChange,
|
||||||
|
onContinue = callbacks::onContinue,
|
||||||
|
error = state.error?.string(),
|
||||||
|
isTextErrorColor = state.isTextErrorColor
|
||||||
|
)
|
||||||
|
|
||||||
PrimaryOutlineButton(
|
Spacer(modifier = Modifier.height(LocalDimensions.current.xxxsSpacing))
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.CenterHorizontally)
|
BorderlessButtonWithIcon(
|
||||||
.padding(horizontal = LocalDimensions.current.xlargeSpacing)
|
text = stringResource(R.string.messageNewDescription),
|
||||||
.padding(bottom = LocalDimensions.current.smallSpacing)
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.contentDescription(R.string.AccessibilityId_help_desk_link)
|
||||||
.contentDescription(R.string.next),
|
.padding(horizontal = LocalDimensions.current.mediumSpacing)
|
||||||
enabled = state.isNextButtonEnabled,
|
.fillMaxWidth(),
|
||||||
onClick = callbacks::onContinue
|
style = LocalType.current.small,
|
||||||
) {
|
color = LocalColors.current.textSecondary,
|
||||||
LoadingArcOr(state.loading) {
|
iconRes = R.drawable.ic_circle_question_mark,
|
||||||
Text(stringResource(R.string.next))
|
onClick = onHelp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(LocalDimensions.current.smallSpacing))
|
||||||
|
Spacer(Modifier.weight(2f))
|
||||||
|
|
||||||
|
PrimaryOutlineButton(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.CenterHorizontally)
|
||||||
|
.padding(horizontal = LocalDimensions.current.xlargeSpacing)
|
||||||
|
.padding(bottom = LocalDimensions.current.smallSpacing)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.contentDescription(R.string.next),
|
||||||
|
enabled = state.isNextButtonEnabled,
|
||||||
|
onClick = callbacks::onContinue
|
||||||
|
) {
|
||||||
|
LoadingArcOr(state.loading) {
|
||||||
|
Text(stringResource(R.string.next))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun keyboardHeight(): MutableState<Dp> {
|
||||||
|
val view = LocalView.current
|
||||||
|
var keyboardHeight = remember { mutableStateOf(0.dp) }
|
||||||
|
val density = LocalDensity.current
|
||||||
|
|
||||||
|
DisposableEffect(view) {
|
||||||
|
val listener = ViewTreeObserver.OnGlobalLayoutListener {
|
||||||
|
val rect = Rect()
|
||||||
|
view.getWindowVisibleDisplayFrame(rect)
|
||||||
|
val screenHeight = view.rootView.height * PEEK_RATIO
|
||||||
|
val keypadHeightPx = max( screenHeight - rect.bottom, 0f)
|
||||||
|
|
||||||
|
keyboardHeight.value = with(density) { keypadHeightPx.toDp() }
|
||||||
|
}
|
||||||
|
|
||||||
|
view.viewTreeObserver.addOnGlobalLayoutListener(listener)
|
||||||
|
onDispose {
|
||||||
|
view.viewTreeObserver.removeOnGlobalLayoutListener(listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyboardHeight
|
||||||
|
}
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
private fun PreviewNewMessage(
|
private fun PreviewNewMessage(
|
||||||
|
@ -46,7 +46,7 @@ internal class NewMessageViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onContinue() {
|
override fun onContinue() {
|
||||||
val idOrONS = state.value.newMessageIdOrOns
|
val idOrONS = state.value.newMessageIdOrOns.trim()
|
||||||
|
|
||||||
if (PublicKeyValidation.isValid(idOrONS, isPrefixRequired = false)) {
|
if (PublicKeyValidation.isValid(idOrONS, isPrefixRequired = false)) {
|
||||||
onUnvalidatedPublicKey(publicKey = idOrONS)
|
onUnvalidatedPublicKey(publicKey = idOrONS)
|
||||||
|
@ -164,7 +164,6 @@ import org.thoughtcrime.securesms.mms.VideoSlide
|
|||||||
import org.thoughtcrime.securesms.permissions.Permissions
|
import org.thoughtcrime.securesms.permissions.Permissions
|
||||||
import org.thoughtcrime.securesms.reactions.ReactionsDialogFragment
|
import org.thoughtcrime.securesms.reactions.ReactionsDialogFragment
|
||||||
import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiDialogFragment
|
import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiDialogFragment
|
||||||
import org.thoughtcrime.securesms.recoverypassword.RecoveryPasswordActivity
|
|
||||||
import org.thoughtcrime.securesms.showSessionDialog
|
import org.thoughtcrime.securesms.showSessionDialog
|
||||||
import org.thoughtcrime.securesms.util.ActivityDispatcher
|
import org.thoughtcrime.securesms.util.ActivityDispatcher
|
||||||
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
|
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
|
||||||
@ -177,7 +176,6 @@ import org.thoughtcrime.securesms.util.isScrolledToBottom
|
|||||||
import org.thoughtcrime.securesms.util.isScrolledToWithin30dpOfBottom
|
import org.thoughtcrime.securesms.util.isScrolledToWithin30dpOfBottom
|
||||||
import org.thoughtcrime.securesms.util.push
|
import org.thoughtcrime.securesms.util.push
|
||||||
import org.thoughtcrime.securesms.util.show
|
import org.thoughtcrime.securesms.util.show
|
||||||
import org.thoughtcrime.securesms.util.start
|
|
||||||
import org.thoughtcrime.securesms.util.toPx
|
import org.thoughtcrime.securesms.util.toPx
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
@ -1589,8 +1587,15 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
val text = getMessageBody()
|
val text = getMessageBody()
|
||||||
val userPublicKey = textSecurePreferences.getLocalNumber()
|
val userPublicKey = textSecurePreferences.getLocalNumber()
|
||||||
val isNoteToSelf = (recipient.isContactRecipient && recipient.address.toString() == userPublicKey)
|
val isNoteToSelf = (recipient.isContactRecipient && recipient.address.toString() == userPublicKey)
|
||||||
if (text.contains(seed) && !isNoteToSelf && !hasPermissionToSendSeed) {
|
if (seed in text && !isNoteToSelf && !hasPermissionToSendSeed) {
|
||||||
start<RecoveryPasswordActivity>()
|
showSessionDialog {
|
||||||
|
title(R.string.dialog_send_seed_title)
|
||||||
|
text(R.string.dialog_send_seed_explanation)
|
||||||
|
button(R.string.dialog_send_seed_send_button_title) { sendTextOnlyMessage(true) }
|
||||||
|
cancelButton()
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
// Create the message
|
// Create the message
|
||||||
val message = VisibleMessage().applyExpiryMode(viewModel.threadId)
|
val message = VisibleMessage().applyExpiryMode(viewModel.threadId)
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.conversation.v2.dialogs
|
|
||||||
|
|
||||||
import android.app.Dialog
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.fragment.app.DialogFragment
|
|
||||||
import network.loki.messenger.R
|
|
||||||
import org.thoughtcrime.securesms.createSessionDialog
|
|
||||||
|
|
||||||
/** Shown if the user is about to send their recovery phrase to someone. */
|
|
||||||
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() }
|
|
||||||
cancelButton()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun send() {
|
|
||||||
proceed?.invoke()
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
@ -80,16 +80,14 @@ import org.thoughtcrime.securesms.util.start
|
|||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
private const val NEW_ACCOUNT = "HomeActivity_NEW_ACCOUNT"
|
||||||
|
private const val FROM_ONBOARDING = "HomeActivity_FROM_ONBOARDING"
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class HomeActivity : PassphraseRequiredActionBarActivity(),
|
class HomeActivity : PassphraseRequiredActionBarActivity(),
|
||||||
ConversationClickListener,
|
ConversationClickListener,
|
||||||
GlobalSearchInputLayout.GlobalSearchInputLayoutListener {
|
GlobalSearchInputLayout.GlobalSearchInputLayoutListener {
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val NEW_ACCOUNT = "HomeActivity_NEW_ACCOUNT"
|
|
||||||
const val FROM_ONBOARDING = "HomeActivity_FROM_ONBOARDING"
|
|
||||||
}
|
|
||||||
|
|
||||||
private lateinit var binding: ActivityHomeBinding
|
private lateinit var binding: ActivityHomeBinding
|
||||||
private lateinit var glide: RequestManager
|
private lateinit var glide: RequestManager
|
||||||
|
|
||||||
@ -137,7 +135,8 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val isNewAccount: Boolean get() = intent.getBooleanExtra(FROM_ONBOARDING, false)
|
private val isFromOnboarding: Boolean get() = intent.getBooleanExtra(FROM_ONBOARDING, false)
|
||||||
|
private val isNewAccount: Boolean get() = intent.getBooleanExtra(NEW_ACCOUNT, false)
|
||||||
|
|
||||||
// region Lifecycle
|
// region Lifecycle
|
||||||
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
|
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
|
||||||
@ -251,7 +250,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||||||
}
|
}
|
||||||
else -> buildList {
|
else -> buildList {
|
||||||
result.contactAndGroupList.takeUnless { it.isEmpty() }?.let {
|
result.contactAndGroupList.takeUnless { it.isEmpty() }?.let {
|
||||||
add(GlobalSearchAdapter.Model.Header(R.string.contacts))
|
add(GlobalSearchAdapter.Model.Header(R.string.conversations))
|
||||||
addAll(it)
|
addAll(it)
|
||||||
}
|
}
|
||||||
result.messageResults.takeUnless { it.isEmpty() }?.let {
|
result.messageResults.takeUnless { it.isEmpty() }?.let {
|
||||||
@ -264,8 +263,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
EventBus.getDefault().register(this@HomeActivity)
|
EventBus.getDefault().register(this@HomeActivity)
|
||||||
if (intent.hasExtra(FROM_ONBOARDING)
|
if (isFromOnboarding) {
|
||||||
&& intent.getBooleanExtra(FROM_ONBOARDING, false)) {
|
|
||||||
if (Build.VERSION.SDK_INT >= 33 &&
|
if (Build.VERSION.SDK_INT >= 33 &&
|
||||||
(getSystemService(NOTIFICATION_SERVICE) as NotificationManager).areNotificationsEnabled().not()) {
|
(getSystemService(NOTIFICATION_SERVICE) as NotificationManager).areNotificationsEnabled().not()) {
|
||||||
Permissions.with(this)
|
Permissions.with(this)
|
||||||
@ -637,10 +635,10 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.startHomeActivity(isNewAccount: Boolean) {
|
fun Context.startHomeActivity(isFromOnboarding: Boolean, isNewAccount: Boolean) {
|
||||||
Intent(this, HomeActivity::class.java).apply {
|
Intent(this, HomeActivity::class.java).apply {
|
||||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||||
putExtra(HomeActivity.NEW_ACCOUNT, true)
|
putExtra(NEW_ACCOUNT, isNewAccount)
|
||||||
putExtra(HomeActivity.FROM_ONBOARDING, true)
|
putExtra(FROM_ONBOARDING, isFromOnboarding)
|
||||||
}.also(::startActivity)
|
}.also(::startActivity)
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ class LoadingActivity: BaseActionBarActivity() {
|
|||||||
|
|
||||||
when {
|
when {
|
||||||
loadFailed -> startPickDisplayNameActivity(loadFailed = true)
|
loadFailed -> startPickDisplayNameActivity(loadFailed = true)
|
||||||
else -> startHomeActivity(isNewAccount = false)
|
else -> startHomeActivity(isNewAccount = false, isFromOnboarding = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
finish()
|
finish()
|
||||||
|
@ -8,6 +8,7 @@ import org.session.libsignal.utilities.KeyHelper
|
|||||||
import org.session.libsignal.utilities.hexEncodedPublicKey
|
import org.session.libsignal.utilities.hexEncodedPublicKey
|
||||||
import org.thoughtcrime.securesms.crypto.KeyPairUtilities
|
import org.thoughtcrime.securesms.crypto.KeyPairUtilities
|
||||||
import org.thoughtcrime.securesms.dependencies.ConfigFactory
|
import org.thoughtcrime.securesms.dependencies.ConfigFactory
|
||||||
|
import org.thoughtcrime.securesms.util.VersionDataFetcher
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@ -16,6 +17,7 @@ class CreateAccountManager @Inject constructor(
|
|||||||
private val application: Application,
|
private val application: Application,
|
||||||
private val prefs: TextSecurePreferences,
|
private val prefs: TextSecurePreferences,
|
||||||
private val configFactory: ConfigFactory,
|
private val configFactory: ConfigFactory,
|
||||||
|
private val versionDataFetcher: VersionDataFetcher
|
||||||
) {
|
) {
|
||||||
private val database: LokiAPIDatabaseProtocol
|
private val database: LokiAPIDatabaseProtocol
|
||||||
get() = SnodeModule.shared.storage
|
get() = SnodeModule.shared.storage
|
||||||
@ -41,5 +43,7 @@ class CreateAccountManager @Inject constructor(
|
|||||||
prefs.setLocalRegistrationId(registrationID)
|
prefs.setLocalRegistrationId(registrationID)
|
||||||
prefs.setLocalNumber(userHexEncodedPublicKey)
|
prefs.setLocalNumber(userHexEncodedPublicKey)
|
||||||
prefs.setRestorationTime(0)
|
prefs.setRestorationTime(0)
|
||||||
|
|
||||||
|
versionDataFetcher.startTimedVersionCheck()
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -12,6 +12,7 @@ import org.session.libsignal.utilities.hexEncodedPublicKey
|
|||||||
import org.thoughtcrime.securesms.ApplicationContext
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
import org.thoughtcrime.securesms.crypto.KeyPairUtilities
|
import org.thoughtcrime.securesms.crypto.KeyPairUtilities
|
||||||
import org.thoughtcrime.securesms.dependencies.ConfigFactory
|
import org.thoughtcrime.securesms.dependencies.ConfigFactory
|
||||||
|
import org.thoughtcrime.securesms.util.VersionDataFetcher
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@ -19,7 +20,8 @@ import javax.inject.Singleton
|
|||||||
class LoadAccountManager @Inject constructor(
|
class LoadAccountManager @Inject constructor(
|
||||||
@dagger.hilt.android.qualifiers.ApplicationContext private val context: Context,
|
@dagger.hilt.android.qualifiers.ApplicationContext private val context: Context,
|
||||||
private val configFactory: ConfigFactory,
|
private val configFactory: ConfigFactory,
|
||||||
private val prefs: TextSecurePreferences
|
private val prefs: TextSecurePreferences,
|
||||||
|
private val versionDataFetcher: VersionDataFetcher
|
||||||
) {
|
) {
|
||||||
private val database: LokiAPIDatabaseProtocol
|
private val database: LokiAPIDatabaseProtocol
|
||||||
get() = SnodeModule.shared.storage
|
get() = SnodeModule.shared.storage
|
||||||
@ -52,6 +54,8 @@ class LoadAccountManager @Inject constructor(
|
|||||||
setHasViewedSeed(true)
|
setHasViewedSeed(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
versionDataFetcher.startTimedVersionCheck()
|
||||||
|
|
||||||
ApplicationContext.getInstance(context).retrieveUserProfile()
|
ApplicationContext.getInstance(context).retrieveUserProfile()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ class MessageNotificationsActivity : BaseActionBarActivity() {
|
|||||||
viewModel.events.collect {
|
viewModel.events.collect {
|
||||||
when (it) {
|
when (it) {
|
||||||
Event.Loading -> start<LoadingActivity>()
|
Event.Loading -> start<LoadingActivity>()
|
||||||
Event.OnboardingComplete -> startHomeActivity(isNewAccount = true)
|
Event.OnboardingComplete -> startHomeActivity(isNewAccount = true, isFromOnboarding = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ class PickDisplayNameActivity : BaseActionBarActivity() {
|
|||||||
viewModel.events.collect {
|
viewModel.events.collect {
|
||||||
when (it) {
|
when (it) {
|
||||||
is Event.CreateAccount -> startMessageNotificationsActivity(it.profileName)
|
is Event.CreateAccount -> startMessageNotificationsActivity(it.profileName)
|
||||||
Event.LoadAccountComplete -> startHomeActivity(isNewAccount = false)
|
Event.LoadAccountComplete -> startHomeActivity(isNewAccount = false, isFromOnboarding = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
|
|||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
import org.thoughtcrime.securesms.ui.theme.selectedTheme
|
import org.thoughtcrime.securesms.ui.theme.invalidateComposeThemeColors
|
||||||
import org.thoughtcrime.securesms.util.ThemeState
|
import org.thoughtcrime.securesms.util.ThemeState
|
||||||
import org.thoughtcrime.securesms.util.themeState
|
import org.thoughtcrime.securesms.util.themeState
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -21,6 +21,8 @@ class AppearanceSettingsViewModel @Inject constructor(private val prefs: TextSec
|
|||||||
prefs.setAccentColorStyle(newAccentColorStyle)
|
prefs.setAccentColorStyle(newAccentColorStyle)
|
||||||
// update UI state
|
// update UI state
|
||||||
_uiState.value = prefs.themeState()
|
_uiState.value = prefs.themeState()
|
||||||
|
|
||||||
|
invalidateComposeThemeColors()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setNewStyle(newThemeStyle: String) {
|
fun setNewStyle(newThemeStyle: String) {
|
||||||
@ -28,16 +30,13 @@ class AppearanceSettingsViewModel @Inject constructor(private val prefs: TextSec
|
|||||||
// update UI state
|
// update UI state
|
||||||
_uiState.value = prefs.themeState()
|
_uiState.value = prefs.themeState()
|
||||||
|
|
||||||
// force compose to refresh its style reference
|
invalidateComposeThemeColors()
|
||||||
selectedTheme = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setNewFollowSystemSettings(followSystemSettings: Boolean) {
|
fun setNewFollowSystemSettings(followSystemSettings: Boolean) {
|
||||||
prefs.setFollowSystemSettings(followSystemSettings)
|
prefs.setFollowSystemSettings(followSystemSettings)
|
||||||
_uiState.value = prefs.themeState()
|
_uiState.value = prefs.themeState()
|
||||||
|
|
||||||
// force compose to refresh its style reference
|
invalidateComposeThemeColors()
|
||||||
selectedTheme = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,10 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.ui.theme
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class holds two instances of [ThemeColors], [light] representing the [ThemeColors] to use when the system is in a
|
|
||||||
* light theme, and [dark] representing the [ThemeColors] to use when the system is in a dark theme.
|
|
||||||
*/
|
|
||||||
data class ThemeColorSet(
|
|
||||||
val light: ThemeColors,
|
|
||||||
val dark: ThemeColors
|
|
||||||
)
|
|
@ -0,0 +1,20 @@
|
|||||||
|
package org.thoughtcrime.securesms.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
|
fun interface ThemeColorsProvider {
|
||||||
|
@Composable
|
||||||
|
fun get(): ThemeColors
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("FunctionName")
|
||||||
|
fun FollowSystemThemeColorsProvider(light: ThemeColors, dark: ThemeColors) = ThemeColorsProvider {
|
||||||
|
when {
|
||||||
|
isSystemInDarkTheme() -> dark
|
||||||
|
else -> light
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("FunctionName")
|
||||||
|
fun ThemeColorsProvider(colors: ThemeColors) = ThemeColorsProvider { colors }
|
@ -1,7 +1,5 @@
|
|||||||
package org.thoughtcrime.securesms.ui.theme
|
package org.thoughtcrime.securesms.ui.theme
|
||||||
|
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.BLUE_ACCENT
|
import org.session.libsession.utilities.TextSecurePreferences.Companion.BLUE_ACCENT
|
||||||
@ -17,38 +15,25 @@ import org.session.libsession.utilities.TextSecurePreferences.Companion.YELLOW_A
|
|||||||
* Some behaviour is hardcoded to cater for legacy usage of people with themes already set
|
* Some behaviour is hardcoded to cater for legacy usage of people with themes already set
|
||||||
* But future themes will be picked and set directly from the "Appearance" screen
|
* But future themes will be picked and set directly from the "Appearance" screen
|
||||||
*/
|
*/
|
||||||
@Composable
|
fun TextSecurePreferences.getColorsProvider(): ThemeColorsProvider {
|
||||||
fun TextSecurePreferences.getComposeTheme(): ThemeColors {
|
|
||||||
val selectedTheme = getThemeStyle()
|
val selectedTheme = getThemeStyle()
|
||||||
|
|
||||||
// get the chosen primary color from the preferences
|
// get the chosen primary color from the preferences
|
||||||
val selectedPrimary = primaryColor()
|
val selectedPrimary = primaryColor()
|
||||||
|
|
||||||
// create a theme set with the appropriate primary
|
val isOcean = "ocean" in selectedTheme
|
||||||
val colorSet = when(selectedTheme){
|
|
||||||
TextSecurePreferences.OCEAN_DARK,
|
val createLight = if (isOcean) ::OceanLight else ::ClassicLight
|
||||||
TextSecurePreferences.OCEAN_LIGHT -> ThemeColorSet(
|
val createDark = if (isOcean) ::OceanDark else ::ClassicDark
|
||||||
light = OceanLight(selectedPrimary),
|
|
||||||
dark = OceanDark(selectedPrimary)
|
return when {
|
||||||
)
|
getFollowSystemSettings() -> FollowSystemThemeColorsProvider(
|
||||||
|
light = createLight(selectedPrimary),
|
||||||
else -> ThemeColorSet(
|
dark = createDark(selectedPrimary)
|
||||||
light = ClassicLight(selectedPrimary),
|
|
||||||
dark = ClassicDark(selectedPrimary)
|
|
||||||
)
|
)
|
||||||
|
"light" in selectedTheme -> ThemeColorsProvider(createLight(selectedPrimary))
|
||||||
|
else -> ThemeColorsProvider(createDark(selectedPrimary))
|
||||||
}
|
}
|
||||||
|
|
||||||
// deliver the right set from the light/dark mode chosen
|
|
||||||
val theme = when{
|
|
||||||
getFollowSystemSettings() -> if(isSystemInDarkTheme()) colorSet.dark else colorSet.light
|
|
||||||
|
|
||||||
selectedTheme == TextSecurePreferences.CLASSIC_LIGHT ||
|
|
||||||
selectedTheme == TextSecurePreferences.OCEAN_LIGHT -> colorSet.light
|
|
||||||
|
|
||||||
else -> colorSet.dark
|
|
||||||
}
|
|
||||||
|
|
||||||
return theme
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun TextSecurePreferences.primaryColor(): Color = when(getSelectedAccentColor()) {
|
fun TextSecurePreferences.primaryColor(): Color = when(getSelectedAccentColor()) {
|
||||||
@ -60,6 +45,3 @@ fun TextSecurePreferences.primaryColor(): Color = when(getSelectedAccentColor())
|
|||||||
YELLOW_ACCENT -> primaryYellow
|
YELLOW_ACCENT -> primaryYellow
|
||||||
else -> primaryGreen
|
else -> primaryGreen
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -20,7 +20,12 @@ import org.session.libsession.utilities.AppTextSecurePreferences
|
|||||||
val LocalColors = compositionLocalOf <ThemeColors> { ClassicDark() }
|
val LocalColors = compositionLocalOf <ThemeColors> { ClassicDark() }
|
||||||
val LocalType = compositionLocalOf { sessionTypography }
|
val LocalType = compositionLocalOf { sessionTypography }
|
||||||
|
|
||||||
var selectedTheme: ThemeColors? = null
|
var cachedColorsProvider: ThemeColorsProvider? = null
|
||||||
|
|
||||||
|
fun invalidateComposeThemeColors() {
|
||||||
|
// invalidate compose theme colors
|
||||||
|
cachedColorsProvider = null
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply a Material2 compose theme based on user selections in SharedPreferences.
|
* Apply a Material2 compose theme based on user selections in SharedPreferences.
|
||||||
@ -29,15 +34,15 @@ var selectedTheme: ThemeColors? = null
|
|||||||
fun SessionMaterialTheme(
|
fun SessionMaterialTheme(
|
||||||
content: @Composable () -> Unit
|
content: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
// set the theme data if it hasn't been done yet
|
val context = LocalContext.current
|
||||||
if(selectedTheme == null) {
|
val preferences = AppTextSecurePreferences(context)
|
||||||
// Some values can be set from the preferences, and if not should fallback to a default value
|
|
||||||
val context = LocalContext.current
|
|
||||||
val preferences = AppTextSecurePreferences(context)
|
|
||||||
selectedTheme = preferences.getComposeTheme()
|
|
||||||
}
|
|
||||||
|
|
||||||
SessionMaterialTheme(colors = selectedTheme ?: ClassicDark()) { content() }
|
val cachedColors = cachedColorsProvider ?: preferences.getColorsProvider().also { cachedColorsProvider = it }
|
||||||
|
|
||||||
|
SessionMaterialTheme(
|
||||||
|
colors = cachedColors.get(),
|
||||||
|
content = content
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -58,9 +63,8 @@ fun SessionMaterialTheme(
|
|||||||
LocalType provides sessionTypography,
|
LocalType provides sessionTypography,
|
||||||
LocalContentColor provides colors.text,
|
LocalContentColor provides colors.text,
|
||||||
LocalTextSelectionColors provides colors.textSelectionColors,
|
LocalTextSelectionColors provides colors.textSelectionColors,
|
||||||
) {
|
content = content
|
||||||
content()
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,60 @@
|
|||||||
|
package org.thoughtcrime.securesms.util
|
||||||
|
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.session.libsession.messaging.file_server.FileServerApi
|
||||||
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
|
import org.session.libsignal.utilities.Log
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
import kotlin.time.Duration.Companion.hours
|
||||||
|
|
||||||
|
private val TAG: String = VersionDataFetcher::class.java.simpleName
|
||||||
|
private val REFRESH_TIME_MS = 4.hours.inWholeMilliseconds
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class VersionDataFetcher @Inject constructor(
|
||||||
|
private val prefs: TextSecurePreferences
|
||||||
|
) {
|
||||||
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
|
private val fetchVersionData = Runnable {
|
||||||
|
scope.launch {
|
||||||
|
try {
|
||||||
|
// Perform the version check
|
||||||
|
val clientVersion = FileServerApi.getClientVersion()
|
||||||
|
Log.i(TAG, "Fetched version data: $clientVersion")
|
||||||
|
prefs.setLastVersionCheck()
|
||||||
|
startTimedVersionCheck()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// We can silently ignore the error
|
||||||
|
Log.e(TAG, "Error fetching version data", e)
|
||||||
|
// Schedule the next check for 4 hours from now, but do not setLastVersionCheck
|
||||||
|
// so the app will retry when the app is next foregrounded.
|
||||||
|
startTimedVersionCheck(REFRESH_TIME_MS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val scope = CoroutineScope(Dispatchers.Default)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedules fetching version data.
|
||||||
|
*
|
||||||
|
* @param delayMillis The delay before fetching version data. Default value is 4 hours from the
|
||||||
|
* last check or 0 if there was no previous check or if it was longer than 4 hours ago.
|
||||||
|
*/
|
||||||
|
@JvmOverloads
|
||||||
|
fun startTimedVersionCheck(
|
||||||
|
delayMillis: Long = REFRESH_TIME_MS + prefs.getLastVersionCheck() - System.currentTimeMillis()
|
||||||
|
) {
|
||||||
|
stopTimedVersionCheck()
|
||||||
|
handler.postDelayed(fetchVersionData, delayMillis)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stopTimedVersionCheck() {
|
||||||
|
handler.removeCallbacks(fetchVersionData)
|
||||||
|
}
|
||||||
|
}
|
@ -1 +1 @@
|
|||||||
Subproject commit 20c06674d85369c2d12261582dd36a9f21504233
|
Subproject commit 0193c36e0dad461385d6407a00f33b7314e6d740
|
@ -30,6 +30,7 @@ set(SOURCES
|
|||||||
config_base.cpp
|
config_base.cpp
|
||||||
contacts.cpp
|
contacts.cpp
|
||||||
conversation.cpp
|
conversation.cpp
|
||||||
|
blinded_key.cpp
|
||||||
util.cpp)
|
util.cpp)
|
||||||
|
|
||||||
add_library( # Sets the name of the library.
|
add_library( # Sets the name of the library.
|
||||||
|
34
libsession-util/src/main/cpp/blinded_key.cpp
Normal file
34
libsession-util/src/main/cpp/blinded_key.cpp
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#include <jni.h>
|
||||||
|
#include <session/blinding.hpp>
|
||||||
|
|
||||||
|
#include "util.h"
|
||||||
|
#include "jni_utils.h"
|
||||||
|
|
||||||
|
//
|
||||||
|
// Created by Thomas Ruffie on 29/7/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
JNIEXPORT jobject JNICALL
|
||||||
|
Java_network_loki_messenger_libsession_1util_util_BlindKeyAPI_blindVersionKeyPair(JNIEnv *env,
|
||||||
|
jobject thiz,
|
||||||
|
jbyteArray ed25519_secret_key) {
|
||||||
|
return jni_utils::run_catching_cxx_exception_or_throws<jobject>(env, [=] {
|
||||||
|
const auto [pk, sk] = session::blind_version_key_pair(util::ustring_from_bytes(env, ed25519_secret_key));
|
||||||
|
|
||||||
|
jclass kp_class = env->FindClass("network/loki/messenger/libsession_util/util/KeyPair");
|
||||||
|
jmethodID kp_constructor = env->GetMethodID(kp_class, "<init>", "([B[B)V");
|
||||||
|
return env->NewObject(kp_class, kp_constructor, util::bytes_from_ustring(env, {pk.data(), pk.size()}), util::bytes_from_ustring(env, {sk.data(), sk.size()}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
extern "C"
|
||||||
|
JNIEXPORT jbyteArray JNICALL
|
||||||
|
Java_network_loki_messenger_libsession_1util_util_BlindKeyAPI_blindVersionSign(JNIEnv *env,
|
||||||
|
jobject thiz,
|
||||||
|
jbyteArray ed25519_secret_key,
|
||||||
|
jlong timestamp) {
|
||||||
|
return jni_utils::run_catching_cxx_exception_or_throws<jbyteArray>(env, [=] {
|
||||||
|
auto bytes = session::blind_version_sign(util::ustring_from_bytes(env, ed25519_secret_key), session::Platform::android, timestamp);
|
||||||
|
return util::bytes_from_ustring(env, bytes);
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package network.loki.messenger.libsession_util.util
|
||||||
|
|
||||||
|
object BlindKeyAPI {
|
||||||
|
private val loadLibrary by lazy {
|
||||||
|
System.loadLibrary("session_util")
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
// Ensure the library is loaded at initialization
|
||||||
|
loadLibrary
|
||||||
|
}
|
||||||
|
|
||||||
|
external fun blindVersionKeyPair(ed25519SecretKey: ByteArray): KeyPair
|
||||||
|
external fun blindVersionSign(ed25519SecretKey: ByteArray, timestamp: Long): ByteArray
|
||||||
|
}
|
@ -1,18 +1,22 @@
|
|||||||
package org.session.libsession.messaging.file_server
|
package org.session.libsession.messaging.file_server
|
||||||
|
|
||||||
|
import android.util.Base64
|
||||||
|
import network.loki.messenger.libsession_util.util.BlindKeyAPI
|
||||||
import nl.komponents.kovenant.Promise
|
import nl.komponents.kovenant.Promise
|
||||||
import nl.komponents.kovenant.functional.map
|
import nl.komponents.kovenant.functional.map
|
||||||
import okhttp3.Headers
|
|
||||||
import okhttp3.Headers.Companion.toHeaders
|
import okhttp3.Headers.Companion.toHeaders
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
import okhttp3.MediaType
|
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.RequestBody
|
import okhttp3.RequestBody
|
||||||
|
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||||
import org.session.libsession.snode.OnionRequestAPI
|
import org.session.libsession.snode.OnionRequestAPI
|
||||||
|
import org.session.libsession.snode.utilities.await
|
||||||
import org.session.libsignal.utilities.HTTP
|
import org.session.libsignal.utilities.HTTP
|
||||||
import org.session.libsignal.utilities.JsonUtil
|
import org.session.libsignal.utilities.JsonUtil
|
||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
|
import org.session.libsignal.utilities.toHexString
|
||||||
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
object FileServerApi {
|
object FileServerApi {
|
||||||
|
|
||||||
@ -23,6 +27,7 @@ object FileServerApi {
|
|||||||
sealed class Error(message: String) : Exception(message) {
|
sealed class Error(message: String) : Exception(message) {
|
||||||
object ParsingFailed : Error("Invalid response.")
|
object ParsingFailed : Error("Invalid response.")
|
||||||
object InvalidURL : Error("Invalid URL.")
|
object InvalidURL : Error("Invalid URL.")
|
||||||
|
object NoEd25519KeyPair : Error("Couldn't find ed25519 key pair.")
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Request(
|
data class Request(
|
||||||
@ -105,4 +110,50 @@ object FileServerApi {
|
|||||||
val request = Request(verb = HTTP.Verb.GET, endpoint = "file/$file")
|
val request = Request(verb = HTTP.Verb.GET, endpoint = "file/$file")
|
||||||
return send(request)
|
return send(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current version of session
|
||||||
|
* This is effectively proxying (and caching) the response from the github release
|
||||||
|
* page.
|
||||||
|
*
|
||||||
|
* Note that the value is cached and can be up to 30 minutes out of date normally, and up to 24
|
||||||
|
* hours out of date if we cannot reach the Github API for some reason.
|
||||||
|
*
|
||||||
|
* https://github.com/oxen-io/session-file-server/blob/dev/doc/api.yaml#L119
|
||||||
|
*/
|
||||||
|
suspend fun getClientVersion(): VersionData {
|
||||||
|
// Generate the auth signature
|
||||||
|
val secretKey = MessagingModuleConfiguration.shared.getUserED25519KeyPair()?.secretKey?.asBytes
|
||||||
|
?: throw (Error.NoEd25519KeyPair)
|
||||||
|
|
||||||
|
val blindedKeys = BlindKeyAPI.blindVersionKeyPair(secretKey)
|
||||||
|
val timestamp = System.currentTimeMillis().milliseconds.inWholeSeconds // The current timestamp in seconds
|
||||||
|
val signature = BlindKeyAPI.blindVersionSign(secretKey, timestamp)
|
||||||
|
|
||||||
|
// The hex encoded version-blinded public key with a 07 prefix
|
||||||
|
val blindedPkHex = "07" + blindedKeys.pubKey.toHexString()
|
||||||
|
|
||||||
|
val request = Request(
|
||||||
|
verb = HTTP.Verb.GET,
|
||||||
|
endpoint = "session_version",
|
||||||
|
queryParameters = mapOf("platform" to "android"),
|
||||||
|
headers = mapOf(
|
||||||
|
"X-FS-Pubkey" to blindedPkHex,
|
||||||
|
"X-FS-Timestamp" to timestamp.toString(),
|
||||||
|
"X-FS-Signature" to Base64.encodeToString(signature, Base64.NO_WRAP)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// transform the promise into a coroutine
|
||||||
|
val result = send(request).await()
|
||||||
|
|
||||||
|
// map out the result
|
||||||
|
return JsonUtil.fromJson(result, Map::class.java).let {
|
||||||
|
VersionData(
|
||||||
|
statusCode = it["status_code"] as? Int ?: 0,
|
||||||
|
version = it["result"] as? String ?: "",
|
||||||
|
updated = it["updated"] as? Double ?: 0.0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package org.session.libsession.messaging.file_server
|
||||||
|
|
||||||
|
data class VersionData(
|
||||||
|
val statusCode: Int, // The value 200. Included for backwards compatibility, and may be removed someday.
|
||||||
|
val version: String, // The Session version.
|
||||||
|
val updated: Double // The unix timestamp when this version was retrieved from Github; this can be up to 24 hours ago in case of consistent fetch errors, though normally will be within the last 30 minutes.
|
||||||
|
)
|
@ -0,0 +1,13 @@
|
|||||||
|
package org.session.libsession.snode.utilities
|
||||||
|
|
||||||
|
import nl.komponents.kovenant.Promise
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
import kotlin.coroutines.resumeWithException
|
||||||
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
|
suspend fun <T, E: Throwable> Promise<T, E>.await(): T {
|
||||||
|
return suspendCoroutine { cont ->
|
||||||
|
success(cont::resume)
|
||||||
|
fail(cont::resumeWithException)
|
||||||
|
}
|
||||||
|
}
|
@ -13,6 +13,7 @@ import kotlinx.coroutines.channels.BufferOverflow
|
|||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
import org.session.libsession.R
|
import org.session.libsession.R
|
||||||
|
import org.session.libsession.utilities.TextSecurePreferences.Companion
|
||||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.AUTOPLAY_AUDIO_MESSAGES
|
import org.session.libsession.utilities.TextSecurePreferences.Companion.AUTOPLAY_AUDIO_MESSAGES
|
||||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.CALL_NOTIFICATIONS_ENABLED
|
import org.session.libsession.utilities.TextSecurePreferences.Companion.CALL_NOTIFICATIONS_ENABLED
|
||||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.CLASSIC_DARK
|
import org.session.libsession.utilities.TextSecurePreferences.Companion.CLASSIC_DARK
|
||||||
@ -20,6 +21,7 @@ import org.session.libsession.utilities.TextSecurePreferences.Companion.CLASSIC_
|
|||||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.FOLLOW_SYSTEM_SETTINGS
|
import org.session.libsession.utilities.TextSecurePreferences.Companion.FOLLOW_SYSTEM_SETTINGS
|
||||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.HIDE_PASSWORD
|
import org.session.libsession.utilities.TextSecurePreferences.Companion.HIDE_PASSWORD
|
||||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.LAST_VACUUM_TIME
|
import org.session.libsession.utilities.TextSecurePreferences.Companion.LAST_VACUUM_TIME
|
||||||
|
import org.session.libsession.utilities.TextSecurePreferences.Companion.LAST_VERSION_CHECK
|
||||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.LEGACY_PREF_KEY_SELECTED_UI_MODE
|
import org.session.libsession.utilities.TextSecurePreferences.Companion.LEGACY_PREF_KEY_SELECTED_UI_MODE
|
||||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.OCEAN_DARK
|
import org.session.libsession.utilities.TextSecurePreferences.Companion.OCEAN_DARK
|
||||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.OCEAN_LIGHT
|
import org.session.libsession.utilities.TextSecurePreferences.Companion.OCEAN_LIGHT
|
||||||
@ -186,6 +188,8 @@ interface TextSecurePreferences {
|
|||||||
fun clearAll()
|
fun clearAll()
|
||||||
fun getHidePassword(): Boolean
|
fun getHidePassword(): Boolean
|
||||||
fun setHidePassword(value: Boolean)
|
fun setHidePassword(value: Boolean)
|
||||||
|
fun getLastVersionCheck(): Long
|
||||||
|
fun setLastVersionCheck()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val TAG = TextSecurePreferences::class.simpleName
|
val TAG = TextSecurePreferences::class.simpleName
|
||||||
@ -272,6 +276,7 @@ interface TextSecurePreferences {
|
|||||||
const val AUTOPLAY_AUDIO_MESSAGES = "pref_autoplay_audio"
|
const val AUTOPLAY_AUDIO_MESSAGES = "pref_autoplay_audio"
|
||||||
const val FINGERPRINT_KEY_GENERATED = "fingerprint_key_generated"
|
const val FINGERPRINT_KEY_GENERATED = "fingerprint_key_generated"
|
||||||
const val SELECTED_ACCENT_COLOR = "selected_accent_color"
|
const val SELECTED_ACCENT_COLOR = "selected_accent_color"
|
||||||
|
const val LAST_VERSION_CHECK = "pref_last_version_check"
|
||||||
|
|
||||||
const val HAS_RECEIVED_LEGACY_CONFIG = "has_received_legacy_config"
|
const val HAS_RECEIVED_LEGACY_CONFIG = "has_received_legacy_config"
|
||||||
const val HAS_FORCED_NEW_CONFIG = "has_forced_new_config"
|
const val HAS_FORCED_NEW_CONFIG = "has_forced_new_config"
|
||||||
@ -1541,6 +1546,14 @@ class AppTextSecurePreferences @Inject constructor(
|
|||||||
setLongPreference(LAST_VACUUM_TIME, System.currentTimeMillis())
|
setLongPreference(LAST_VACUUM_TIME, System.currentTimeMillis())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getLastVersionCheck(): Long {
|
||||||
|
return getLongPreference(LAST_VERSION_CHECK, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setLastVersionCheck() {
|
||||||
|
setLongPreference(LAST_VERSION_CHECK, System.currentTimeMillis())
|
||||||
|
}
|
||||||
|
|
||||||
override fun setShownCallNotification(): Boolean {
|
override fun setShownCallNotification(): Boolean {
|
||||||
val previousValue = getBooleanPreference(SHOWN_CALL_NOTIFICATION, false)
|
val previousValue = getBooleanPreference(SHOWN_CALL_NOTIFICATION, false)
|
||||||
if (previousValue) return false
|
if (previousValue) return false
|
||||||
|
Loading…
Reference in New Issue
Block a user