mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-24 18:45:19 +00:00
commit
c6c1aa60e9
@ -28,8 +28,8 @@ configurations.all {
|
||||
exclude module: "commons-logging"
|
||||
}
|
||||
|
||||
def canonicalVersionCode = 376
|
||||
def canonicalVersionName = "1.18.6"
|
||||
def canonicalVersionCode = 377
|
||||
def canonicalVersionName = "1.19.0"
|
||||
|
||||
def postFixSize = 10
|
||||
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.TypingStatusRepository;
|
||||
import org.thoughtcrime.securesms.util.Broadcaster;
|
||||
import org.thoughtcrime.securesms.util.VersionDataFetcher;
|
||||
import org.thoughtcrime.securesms.util.dynamiclanguage.LocaleParseHelper;
|
||||
import org.thoughtcrime.securesms.webrtc.CallMessageProcessor;
|
||||
import org.webrtc.PeerConnectionFactory;
|
||||
@ -110,7 +111,6 @@ import javax.inject.Inject;
|
||||
import dagger.hilt.EntryPoints;
|
||||
import dagger.hilt.android.HiltAndroidApp;
|
||||
import kotlin.Unit;
|
||||
import kotlinx.coroutines.Job;
|
||||
import network.loki.messenger.BuildConfig;
|
||||
import network.loki.messenger.libsession_util.ConfigBase;
|
||||
import network.loki.messenger.libsession_util.UserProfile;
|
||||
@ -151,6 +151,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
|
||||
@Inject PushRegistry pushRegistry;
|
||||
@Inject ConfigFactory configFactory;
|
||||
@Inject LastSentTimestampCache lastSentTimestampCache;
|
||||
@Inject VersionDataFetcher versionDataFetcher;
|
||||
CallMessageProcessor callMessageProcessor;
|
||||
MessagingModuleConfiguration messagingModuleConfiguration;
|
||||
|
||||
@ -275,6 +276,9 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
|
||||
|
||||
OpenGroupManager.INSTANCE.startPolling();
|
||||
});
|
||||
|
||||
// fetch last version data
|
||||
versionDataFetcher.startTimedVersionCheck();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -287,12 +291,14 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
|
||||
poller.stopIfNeeded();
|
||||
}
|
||||
ClosedGroupPollerV2.getShared().stopAll();
|
||||
versionDataFetcher.stopTimedVersionCheck();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTerminate() {
|
||||
stopKovenant(); // Loki
|
||||
OpenGroupManager.INSTANCE.stopPolling();
|
||||
versionDataFetcher.stopTimedVersionCheck();
|
||||
super.onTerminate();
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@ fun showMuteDialog(
|
||||
|
||||
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)),
|
||||
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)),
|
||||
SEVEN_DAYS(R.string.arrays__mute_for_seven_days, duration = TimeUnit.DAYS.toMillis(7)),
|
||||
FOREVER(R.string.arrays__mute_forever, getTime = { Long.MAX_VALUE });
|
||||
|
@ -27,7 +27,11 @@ import org.thoughtcrime.securesms.groups.JoinCommunityFragment
|
||||
@AndroidEntryPoint
|
||||
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(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
|
@ -1,10 +1,13 @@
|
||||
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.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
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.verticalScroll
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
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.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.tooling.preview.Preview
|
||||
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.emptyFlow
|
||||
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.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.BorderlessButtonWithIcon
|
||||
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.SessionTabRow
|
||||
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
|
||||
import kotlin.math.max
|
||||
|
||||
private val TITLES = listOf(R.string.enter_account_id, R.string.qrScan)
|
||||
|
||||
@ -76,11 +93,34 @@ private fun EnterAccountId(
|
||||
callbacks: Callbacks,
|
||||
onHelp: () -> Unit = {}
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
// the scaffold is required to provide the contentPadding. That contentPadding is needed
|
||||
// to properly handle the ime padding.
|
||||
Scaffold() { contentPadding ->
|
||||
// we need this extra surface to handle nested scrolling properly,
|
||||
// because this scrollable component is inside a bottomSheet dialog which is itself scrollable
|
||||
Surface(
|
||||
modifier = Modifier.nestedScroll(rememberNestedScrollInteropConnection()),
|
||||
color = LocalColors.current.backgroundSecondary
|
||||
) {
|
||||
|
||||
var accountModifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
|
||||
// There is a known issue with the ime padding on android versions below 30
|
||||
/// So on these older versions we need to resort to some manual padding based on the visible height
|
||||
// when the keyboard is up
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||
val keyboardHeight by keyboardHeight()
|
||||
accountModifier = accountModifier.padding(bottom = keyboardHeight)
|
||||
} else {
|
||||
accountModifier = accountModifier
|
||||
.consumeWindowInsets(contentPadding)
|
||||
.imePadding()
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = accountModifier
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(vertical = LocalDimensions.current.spacing),
|
||||
@ -131,6 +171,33 @@ private fun EnterAccountId(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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
|
||||
|
@ -46,7 +46,7 @@ internal class NewMessageViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
override fun onContinue() {
|
||||
val idOrONS = state.value.newMessageIdOrOns
|
||||
val idOrONS = state.value.newMessageIdOrOns.trim()
|
||||
|
||||
if (PublicKeyValidation.isValid(idOrONS, isPrefixRequired = false)) {
|
||||
onUnvalidatedPublicKey(publicKey = idOrONS)
|
||||
|
@ -164,7 +164,6 @@ import org.thoughtcrime.securesms.mms.VideoSlide
|
||||
import org.thoughtcrime.securesms.permissions.Permissions
|
||||
import org.thoughtcrime.securesms.reactions.ReactionsDialogFragment
|
||||
import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiDialogFragment
|
||||
import org.thoughtcrime.securesms.recoverypassword.RecoveryPasswordActivity
|
||||
import org.thoughtcrime.securesms.showSessionDialog
|
||||
import org.thoughtcrime.securesms.util.ActivityDispatcher
|
||||
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.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
|
||||
@ -1589,8 +1587,15 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
val text = getMessageBody()
|
||||
val userPublicKey = textSecurePreferences.getLocalNumber()
|
||||
val isNoteToSelf = (recipient.isContactRecipient && recipient.address.toString() == userPublicKey)
|
||||
if (text.contains(seed) && !isNoteToSelf && !hasPermissionToSendSeed) {
|
||||
start<RecoveryPasswordActivity>()
|
||||
if (seed in text && !isNoteToSelf && !hasPermissionToSendSeed) {
|
||||
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
|
||||
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 javax.inject.Inject
|
||||
|
||||
private const val NEW_ACCOUNT = "HomeActivity_NEW_ACCOUNT"
|
||||
private const val FROM_ONBOARDING = "HomeActivity_FROM_ONBOARDING"
|
||||
|
||||
@AndroidEntryPoint
|
||||
class HomeActivity : PassphraseRequiredActionBarActivity(),
|
||||
ConversationClickListener,
|
||||
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 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
|
||||
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
|
||||
@ -251,7 +250,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
||||
}
|
||||
else -> buildList {
|
||||
result.contactAndGroupList.takeUnless { it.isEmpty() }?.let {
|
||||
add(GlobalSearchAdapter.Model.Header(R.string.contacts))
|
||||
add(GlobalSearchAdapter.Model.Header(R.string.conversations))
|
||||
addAll(it)
|
||||
}
|
||||
result.messageResults.takeUnless { it.isEmpty() }?.let {
|
||||
@ -264,8 +263,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
||||
}
|
||||
}
|
||||
EventBus.getDefault().register(this@HomeActivity)
|
||||
if (intent.hasExtra(FROM_ONBOARDING)
|
||||
&& intent.getBooleanExtra(FROM_ONBOARDING, false)) {
|
||||
if (isFromOnboarding) {
|
||||
if (Build.VERSION.SDK_INT >= 33 &&
|
||||
(getSystemService(NOTIFICATION_SERVICE) as NotificationManager).areNotificationsEnabled().not()) {
|
||||
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 {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
putExtra(HomeActivity.NEW_ACCOUNT, true)
|
||||
putExtra(HomeActivity.FROM_ONBOARDING, true)
|
||||
putExtra(NEW_ACCOUNT, isNewAccount)
|
||||
putExtra(FROM_ONBOARDING, isFromOnboarding)
|
||||
}.also(::startActivity)
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ class LoadingActivity: BaseActionBarActivity() {
|
||||
|
||||
when {
|
||||
loadFailed -> startPickDisplayNameActivity(loadFailed = true)
|
||||
else -> startHomeActivity(isNewAccount = false)
|
||||
else -> startHomeActivity(isNewAccount = false, isFromOnboarding = true)
|
||||
}
|
||||
|
||||
finish()
|
||||
|
@ -8,6 +8,7 @@ import org.session.libsignal.utilities.KeyHelper
|
||||
import org.session.libsignal.utilities.hexEncodedPublicKey
|
||||
import org.thoughtcrime.securesms.crypto.KeyPairUtilities
|
||||
import org.thoughtcrime.securesms.dependencies.ConfigFactory
|
||||
import org.thoughtcrime.securesms.util.VersionDataFetcher
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -16,6 +17,7 @@ class CreateAccountManager @Inject constructor(
|
||||
private val application: Application,
|
||||
private val prefs: TextSecurePreferences,
|
||||
private val configFactory: ConfigFactory,
|
||||
private val versionDataFetcher: VersionDataFetcher
|
||||
) {
|
||||
private val database: LokiAPIDatabaseProtocol
|
||||
get() = SnodeModule.shared.storage
|
||||
@ -41,5 +43,7 @@ class CreateAccountManager @Inject constructor(
|
||||
prefs.setLocalRegistrationId(registrationID)
|
||||
prefs.setLocalNumber(userHexEncodedPublicKey)
|
||||
prefs.setRestorationTime(0)
|
||||
|
||||
versionDataFetcher.startTimedVersionCheck()
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@ import org.session.libsignal.utilities.hexEncodedPublicKey
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.crypto.KeyPairUtilities
|
||||
import org.thoughtcrime.securesms.dependencies.ConfigFactory
|
||||
import org.thoughtcrime.securesms.util.VersionDataFetcher
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -19,7 +20,8 @@ import javax.inject.Singleton
|
||||
class LoadAccountManager @Inject constructor(
|
||||
@dagger.hilt.android.qualifiers.ApplicationContext private val context: Context,
|
||||
private val configFactory: ConfigFactory,
|
||||
private val prefs: TextSecurePreferences
|
||||
private val prefs: TextSecurePreferences,
|
||||
private val versionDataFetcher: VersionDataFetcher
|
||||
) {
|
||||
private val database: LokiAPIDatabaseProtocol
|
||||
get() = SnodeModule.shared.storage
|
||||
@ -52,6 +54,8 @@ class LoadAccountManager @Inject constructor(
|
||||
setHasViewedSeed(true)
|
||||
}
|
||||
|
||||
versionDataFetcher.startTimedVersionCheck()
|
||||
|
||||
ApplicationContext.getInstance(context).retrieveUserProfile()
|
||||
}
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ class MessageNotificationsActivity : BaseActionBarActivity() {
|
||||
viewModel.events.collect {
|
||||
when (it) {
|
||||
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 {
|
||||
when (it) {
|
||||
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.StateFlow
|
||||
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 javax.inject.Inject
|
||||
@ -21,6 +21,8 @@ class AppearanceSettingsViewModel @Inject constructor(private val prefs: TextSec
|
||||
prefs.setAccentColorStyle(newAccentColorStyle)
|
||||
// update UI state
|
||||
_uiState.value = prefs.themeState()
|
||||
|
||||
invalidateComposeThemeColors()
|
||||
}
|
||||
|
||||
fun setNewStyle(newThemeStyle: String) {
|
||||
@ -28,16 +30,13 @@ class AppearanceSettingsViewModel @Inject constructor(private val prefs: TextSec
|
||||
// update UI state
|
||||
_uiState.value = prefs.themeState()
|
||||
|
||||
// force compose to refresh its style reference
|
||||
selectedTheme = null
|
||||
invalidateComposeThemeColors()
|
||||
}
|
||||
|
||||
fun setNewFollowSystemSettings(followSystemSettings: Boolean) {
|
||||
prefs.setFollowSystemSettings(followSystemSettings)
|
||||
_uiState.value = prefs.themeState()
|
||||
|
||||
// force compose to refresh its style reference
|
||||
selectedTheme = null
|
||||
invalidateComposeThemeColors()
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
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
|
||||
* But future themes will be picked and set directly from the "Appearance" screen
|
||||
*/
|
||||
@Composable
|
||||
fun TextSecurePreferences.getComposeTheme(): ThemeColors {
|
||||
fun TextSecurePreferences.getColorsProvider(): ThemeColorsProvider {
|
||||
val selectedTheme = getThemeStyle()
|
||||
|
||||
// get the chosen primary color from the preferences
|
||||
val selectedPrimary = primaryColor()
|
||||
|
||||
// create a theme set with the appropriate primary
|
||||
val colorSet = when(selectedTheme){
|
||||
TextSecurePreferences.OCEAN_DARK,
|
||||
TextSecurePreferences.OCEAN_LIGHT -> ThemeColorSet(
|
||||
light = OceanLight(selectedPrimary),
|
||||
dark = OceanDark(selectedPrimary)
|
||||
)
|
||||
|
||||
else -> ThemeColorSet(
|
||||
light = ClassicLight(selectedPrimary),
|
||||
dark = ClassicDark(selectedPrimary)
|
||||
val isOcean = "ocean" in selectedTheme
|
||||
|
||||
val createLight = if (isOcean) ::OceanLight else ::ClassicLight
|
||||
val createDark = if (isOcean) ::OceanDark else ::ClassicDark
|
||||
|
||||
return when {
|
||||
getFollowSystemSettings() -> FollowSystemThemeColorsProvider(
|
||||
light = createLight(selectedPrimary),
|
||||
dark = createDark(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()) {
|
||||
@ -60,6 +45,3 @@ fun TextSecurePreferences.primaryColor(): Color = when(getSelectedAccentColor())
|
||||
YELLOW_ACCENT -> primaryYellow
|
||||
else -> primaryGreen
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -20,7 +20,12 @@ import org.session.libsession.utilities.AppTextSecurePreferences
|
||||
val LocalColors = compositionLocalOf <ThemeColors> { ClassicDark() }
|
||||
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.
|
||||
@ -29,15 +34,15 @@ var selectedTheme: ThemeColors? = null
|
||||
fun SessionMaterialTheme(
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
// set the theme data if it hasn't been done yet
|
||||
if(selectedTheme == null) {
|
||||
// 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,
|
||||
LocalContentColor provides colors.text,
|
||||
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
|
||||
contacts.cpp
|
||||
conversation.cpp
|
||||
blinded_key.cpp
|
||||
util.cpp)
|
||||
|
||||
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
|
||||
|
||||
import android.util.Base64
|
||||
import network.loki.messenger.libsession_util.util.BlindKeyAPI
|
||||
import nl.komponents.kovenant.Promise
|
||||
import nl.komponents.kovenant.functional.map
|
||||
import okhttp3.Headers
|
||||
import okhttp3.Headers.Companion.toHeaders
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import okhttp3.MediaType
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.RequestBody
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.snode.OnionRequestAPI
|
||||
import org.session.libsession.snode.utilities.await
|
||||
import org.session.libsignal.utilities.HTTP
|
||||
import org.session.libsignal.utilities.JsonUtil
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.session.libsignal.utilities.toHexString
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
object FileServerApi {
|
||||
|
||||
@ -23,6 +27,7 @@ object FileServerApi {
|
||||
sealed class Error(message: String) : Exception(message) {
|
||||
object ParsingFailed : Error("Invalid response.")
|
||||
object InvalidURL : Error("Invalid URL.")
|
||||
object NoEd25519KeyPair : Error("Couldn't find ed25519 key pair.")
|
||||
}
|
||||
|
||||
data class Request(
|
||||
@ -105,4 +110,50 @@ object FileServerApi {
|
||||
val request = Request(verb = HTTP.Verb.GET, endpoint = "file/$file")
|
||||
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.asSharedFlow
|
||||
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.CALL_NOTIFICATIONS_ENABLED
|
||||
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.HIDE_PASSWORD
|
||||
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.OCEAN_DARK
|
||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.OCEAN_LIGHT
|
||||
@ -186,6 +188,8 @@ interface TextSecurePreferences {
|
||||
fun clearAll()
|
||||
fun getHidePassword(): Boolean
|
||||
fun setHidePassword(value: Boolean)
|
||||
fun getLastVersionCheck(): Long
|
||||
fun setLastVersionCheck()
|
||||
|
||||
companion object {
|
||||
val TAG = TextSecurePreferences::class.simpleName
|
||||
@ -272,6 +276,7 @@ interface TextSecurePreferences {
|
||||
const val AUTOPLAY_AUDIO_MESSAGES = "pref_autoplay_audio"
|
||||
const val FINGERPRINT_KEY_GENERATED = "fingerprint_key_generated"
|
||||
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_FORCED_NEW_CONFIG = "has_forced_new_config"
|
||||
@ -1541,6 +1546,14 @@ class AppTextSecurePreferences @Inject constructor(
|
||||
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 {
|
||||
val previousValue = getBooleanPreference(SHOWN_CALL_NOTIFICATION, false)
|
||||
if (previousValue) return false
|
||||
|
Loading…
Reference in New Issue
Block a user