Update ktlint to 1.5.0.

This commit is contained in:
jeffrey-signal
2025-12-05 09:48:22 -06:00
committed by GitHub
parent cc381513ef
commit 109fc7f1fa
24 changed files with 1254 additions and 385 deletions

View File

@@ -78,7 +78,7 @@ wire {
} }
ktlint { ktlint {
version.set("1.2.1") version.set("1.5.0")
} }
android { android {

View File

@@ -162,7 +162,9 @@ class DatabaseAttachment : Attachment {
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
return other != null && return other != null &&
other is DatabaseAttachment && other.attachmentId == attachmentId && other.uri == uri other is DatabaseAttachment &&
other.attachmentId == attachmentId &&
other.uri == uri
} }
override fun hashCode(): Int { override fun hashCode(): Int {

View File

@@ -48,22 +48,19 @@ class SystemEmojiDrawable(emoji: CharSequence) : Drawable() {
companion object { companion object {
private val textPaint: TextPaint = TextPaint() private val textPaint: TextPaint = TextPaint()
private fun getStaticLayout(emoji: CharSequence): StaticLayout = private fun getStaticLayout(emoji: CharSequence): StaticLayout = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { StaticLayout.Builder.obtain(emoji, 0, emoji.length, textPaint, Int.MAX_VALUE).build()
StaticLayout.Builder.obtain(emoji, 0, emoji.length, textPaint, Int.MAX_VALUE).build() } else {
} else { @Suppress("DEPRECATION")
@Suppress("DEPRECATION") StaticLayout(emoji, textPaint, Int.MAX_VALUE, Layout.Alignment.ALIGN_NORMAL, 0f, 0f, true)
StaticLayout(emoji, textPaint, Int.MAX_VALUE, Layout.Alignment.ALIGN_NORMAL, 0f, 0f, true) }
}
private fun getProcessedEmoji(emoji: CharSequence): CharSequence = private fun getProcessedEmoji(emoji: CharSequence): CharSequence = try {
try { EmojiCompat.get().process(emoji) ?: emoji
EmojiCompat.get().process(emoji) ?: emoji } catch (e: IllegalStateException) {
} catch (e: IllegalStateException) { emoji
emoji }
}
private fun StaticLayout.getBounds(): RectF = private fun StaticLayout.getBounds(): RectF = RectF(getLineLeft(0), 0f, getLineRight(0), getLineDescent(0) - getLineAscent(0).toFloat())
RectF(getLineLeft(0), 0f, getLineRight(0), getLineDescent(0) - getLineAscent(0).toFloat())
} }
} }

View File

@@ -36,14 +36,11 @@ sealed class DSLSettingsText {
} }
companion object { companion object {
fun from(@StringRes stringId: Int, @ColorInt textColor: Int): DSLSettingsText = fun from(@StringRes stringId: Int, @ColorInt textColor: Int): DSLSettingsText = FromResource(stringId, listOf(ColorModifier(textColor)))
FromResource(stringId, listOf(ColorModifier(textColor)))
fun from(@StringRes stringId: Int, vararg modifiers: Modifier): DSLSettingsText = fun from(@StringRes stringId: Int, vararg modifiers: Modifier): DSLSettingsText = FromResource(stringId, modifiers.toList())
FromResource(stringId, modifiers.toList())
fun from(charSequence: CharSequence, vararg modifiers: Modifier): DSLSettingsText = fun from(charSequence: CharSequence, vararg modifiers: Modifier): DSLSettingsText = FromCharSequence(charSequence, modifiers.toList())
FromCharSequence(charSequence, modifiers.toList())
} }
interface Modifier { interface Modifier {

View File

@@ -267,7 +267,8 @@ class BackupStateObserver(
Log.d(TAG, "[getNetworkBackupState][subscriptionStateMismatchDetected] No purchase found in Google Play Billing: $purchaseResult") Log.d(TAG, "[getNetworkBackupState][subscriptionStateMismatchDetected] No purchase found in Google Play Billing: $purchaseResult")
false false
} }
} || SignalStore.backup.backupTierInternalOverride == MessageBackupTier.PAID } ||
SignalStore.backup.backupTierInternalOverride == MessageBackupTier.PAID
Log.d(TAG, "[getNetworkBackupState][subscriptionStateMismatchDetected] googlePlayBillingSubscriptionIsActiveAndWillRenew: $googlePlayBillingSubscriptionIsActiveAndWillRenew") Log.d(TAG, "[getNetworkBackupState][subscriptionStateMismatchDetected] googlePlayBillingSubscriptionIsActiveAndWillRenew: $googlePlayBillingSubscriptionIsActiveAndWillRenew")

View File

@@ -75,32 +75,31 @@ class ChangeNumberRepository(
return accountManager.whoAmI return accountManager.whoAmI
} }
suspend fun ensureDecryptionsDrained(timeout: Duration = 15.seconds) = suspend fun ensureDecryptionsDrained(timeout: Duration = 15.seconds) = withTimeoutOrNull(timeout) {
withTimeoutOrNull(timeout) { suspendCancellableCoroutine {
suspendCancellableCoroutine { val drainedListener = object : Runnable {
val drainedListener = object : Runnable { override fun run() {
override fun run() {
AppDependencies
.incomingMessageObserver
.removeDecryptionDrainedListener(this)
Log.d(TAG, "Decryptions drained.")
it.resume(true)
}
}
it.invokeOnCancellation { cancellationCause ->
AppDependencies AppDependencies
.incomingMessageObserver .incomingMessageObserver
.removeDecryptionDrainedListener(drainedListener) .removeDecryptionDrainedListener(this)
Log.d(TAG, "Decryptions draining canceled.", cancellationCause) Log.d(TAG, "Decryptions drained.")
it.resume(true)
} }
}
it.invokeOnCancellation { cancellationCause ->
AppDependencies AppDependencies
.incomingMessageObserver .incomingMessageObserver
.addDecryptionDrainedListener(drainedListener) .removeDecryptionDrainedListener(drainedListener)
Log.d(TAG, "Waiting for decryption drain.") Log.d(TAG, "Decryptions draining canceled.", cancellationCause)
} }
AppDependencies
.incomingMessageObserver
.addDecryptionDrainedListener(drainedListener)
Log.d(TAG, "Waiting for decryption drain.")
} }
}
@WorkerThread @WorkerThread
fun changeLocalNumber(e164: String, pni: ServiceId.PNI) { fun changeLocalNumber(e164: String, pni: ServiceId.PNI) {

View File

@@ -140,11 +140,12 @@ class NotificationsSettingsViewModel(private val sharedPreferences: SharedPrefer
) )
private fun canEnableNotifications(): Boolean { private fun canEnableNotifications(): Boolean {
val areNotificationsDisabledBySystem = Build.VERSION.SDK_INT >= 26 && ( val areNotificationsDisabledBySystem = Build.VERSION.SDK_INT >= 26 &&
!NotificationChannels.getInstance().isMessageChannelEnabled || (
!NotificationChannels.getInstance().isMessagesChannelGroupEnabled || !NotificationChannels.getInstance().isMessageChannelEnabled ||
!NotificationChannels.getInstance().areNotificationsEnabled() !NotificationChannels.getInstance().isMessagesChannelGroupEnabled ||
) !NotificationChannels.getInstance().areNotificationsEnabled()
)
return !areNotificationsDisabledBySystem return !areNotificationsDisabledBySystem
} }

View File

@@ -50,7 +50,8 @@ object InAppDonations {
InAppPaymentType.ONE_TIME_GIFT -> true InAppPaymentType.ONE_TIME_GIFT -> true
InAppPaymentType.RECURRING_DONATION -> true InAppPaymentType.RECURRING_DONATION -> true
InAppPaymentType.RECURRING_BACKUP -> false InAppPaymentType.RECURRING_BACKUP -> false
} && !LocaleRemoteConfig.isPayPalDisabled() } &&
!LocaleRemoteConfig.isPayPalDisabled()
} }
/** /**

View File

@@ -178,12 +178,10 @@ class ChatColors(
} }
@JvmStatic @JvmStatic
fun forGradient(id: Id, linearGradient: LinearGradient): ChatColors = fun forGradient(id: Id, linearGradient: LinearGradient): ChatColors = ChatColors(id, linearGradient, null)
ChatColors(id, linearGradient, null)
@JvmStatic @JvmStatic
fun forColor(id: Id, @ColorInt color: Int): ChatColors = fun forColor(id: Id, @ColorInt color: Int): ChatColors = ChatColors(id, null, color)
ChatColors(id, null, color)
} }
sealed class Id(val longValue: Long) : Parcelable { sealed class Id(val longValue: Long) : Parcelable {

View File

@@ -423,12 +423,13 @@ object SvrRepository {
false false
} }
newToken = newToken || if (Svr3Migration.shouldWriteToSvr2) { newToken = newToken ||
val credentials: AuthCredentials = svr2.authorization() if (Svr3Migration.shouldWriteToSvr2) {
SignalStore.svr.appendSvr2AuthTokenToList(credentials.asBasic()) val credentials: AuthCredentials = svr2.authorization()
} else { SignalStore.svr.appendSvr2AuthTokenToList(credentials.asBasic())
false } else {
} false
}
if (newToken && SignalStore.svr.hasPin()) { if (newToken && SignalStore.svr.hasPin()) {
BackupManager(AppDependencies.application).dataChanged() BackupManager(AppDependencies.application).dataChanged()

View File

@@ -110,10 +110,9 @@ object RegistrationRepository {
/** /**
* Retrieve the FCM token from the Firebase service. * Retrieve the FCM token from the Firebase service.
*/ */
suspend fun getFcmToken(context: Context): String? = suspend fun getFcmToken(context: Context): String? = withContext(Dispatchers.Default) {
withContext(Dispatchers.Default) { FcmUtil.getToken(context).orElse(null)
FcmUtil.getToken(context).orElse(null) }
}
/** /**
* Queries, and creates if needed, the local registration ID. * Queries, and creates if needed, the local registration ID.
@@ -147,122 +146,120 @@ object RegistrationRepository {
* Queries, and creates if needed, the local profile key. * Queries, and creates if needed, the local profile key.
*/ */
@JvmStatic @JvmStatic
suspend fun getProfileKey(e164: String): ProfileKey = suspend fun getProfileKey(e164: String): ProfileKey = withContext(Dispatchers.IO) {
withContext(Dispatchers.IO) { // TODO [regv2]: make creation more explicit instead of hiding it in this getter
// TODO [regv2]: make creation more explicit instead of hiding it in this getter val recipientTable = SignalDatabase.recipients
val recipientTable = SignalDatabase.recipients val recipient = recipientTable.getByE164(e164)
val recipient = recipientTable.getByE164(e164) var profileKey = if (recipient.isPresent) {
var profileKey = if (recipient.isPresent) { ProfileKeyUtil.profileKeyOrNull(Recipient.resolved(recipient.get()).profileKey)
ProfileKeyUtil.profileKeyOrNull(Recipient.resolved(recipient.get()).profileKey) } else {
} else { null
null
}
if (profileKey == null) {
profileKey = ProfileKeyUtil.createNew()
Log.i(TAG, "No profile key found, created a new one")
}
profileKey
} }
if (profileKey == null) {
profileKey = ProfileKeyUtil.createNew()
Log.i(TAG, "No profile key found, created a new one")
}
profileKey
}
/** /**
* Takes a server response from a successful registration and persists the relevant data. * Takes a server response from a successful registration and persists the relevant data.
*/ */
@JvmStatic @JvmStatic
suspend fun registerAccountLocally(context: Context, data: LocalRegistrationMetadata) = suspend fun registerAccountLocally(context: Context, data: LocalRegistrationMetadata) = withContext(Dispatchers.IO) {
withContext(Dispatchers.IO) { Log.v(TAG, "registerAccountLocally()")
Log.v(TAG, "registerAccountLocally()") if (data.linkedDeviceInfo != null) {
if (data.linkedDeviceInfo != null) { SignalStore.account.deviceId = data.linkedDeviceInfo.deviceId
SignalStore.account.deviceId = data.linkedDeviceInfo.deviceId SignalStore.account.deviceName = data.linkedDeviceInfo.deviceName
SignalStore.account.deviceName = data.linkedDeviceInfo.deviceName }
val aciIdentityKeyPair = data.getAciIdentityKeyPair()
val pniIdentityKeyPair = data.getPniIdentityKeyPair()
SignalStore.account.restoreAciIdentityKeyFromBackup(aciIdentityKeyPair.publicKey.serialize(), aciIdentityKeyPair.privateKey.serialize())
SignalStore.account.restorePniIdentityKeyFromBackup(pniIdentityKeyPair.publicKey.serialize(), pniIdentityKeyPair.privateKey.serialize())
val aciPreKeyCollection = data.getAciPreKeyCollection()
val pniPreKeyCollection = data.getPniPreKeyCollection()
val aci: ACI = ACI.parseOrThrow(data.aci)
val pni: PNI = PNI.parseOrThrow(data.pni)
val hasPin: Boolean = data.hasPin
SignalStore.account.setAci(aci)
SignalStore.account.setPni(pni)
AppDependencies.resetProtocolStores()
AppDependencies.protocolStore.aci().sessions().archiveAllSessions()
AppDependencies.protocolStore.pni().sessions().archiveAllSessions()
SenderKeyUtil.clearAllState()
val aciProtocolStore = AppDependencies.protocolStore.aci()
val aciMetadataStore = SignalStore.account.aciPreKeys
val pniProtocolStore = AppDependencies.protocolStore.pni()
val pniMetadataStore = SignalStore.account.pniPreKeys
storeSignedAndLastResortPreKeys(aciProtocolStore, aciMetadataStore, aciPreKeyCollection)
storeSignedAndLastResortPreKeys(pniProtocolStore, pniMetadataStore, pniPreKeyCollection)
val recipientTable = SignalDatabase.recipients
val selfId = Recipient.trustedPush(aci, pni, data.e164).id
recipientTable.setProfileSharing(selfId, true)
recipientTable.markRegisteredOrThrow(selfId, aci)
recipientTable.linkIdsForSelf(aci, pni, data.e164)
recipientTable.setProfileKey(selfId, ProfileKey(data.profileKey.toByteArray()))
AppDependencies.recipientCache.clearSelf()
SignalStore.account.setE164(data.e164)
SignalStore.account.fcmToken = data.fcmToken
SignalStore.account.fcmEnabled = data.fcmEnabled
val now = System.currentTimeMillis()
saveOwnIdentityKey(selfId, aci, aciProtocolStore, now)
saveOwnIdentityKey(selfId, pni, pniProtocolStore, now)
if (data.linkedDeviceInfo != null) {
if (data.linkedDeviceInfo.accountEntropyPool != null) {
SignalStore.account.setAccountEntropyPoolFromPrimaryDevice(AccountEntropyPool(data.linkedDeviceInfo.accountEntropyPool))
} }
val aciIdentityKeyPair = data.getAciIdentityKeyPair() if (data.linkedDeviceInfo.mediaRootBackupKey != null) {
val pniIdentityKeyPair = data.getPniIdentityKeyPair() SignalStore.backup.mediaRootBackupKey = MediaRootBackupKey(data.linkedDeviceInfo.mediaRootBackupKey.toByteArray())
SignalStore.account.restoreAciIdentityKeyFromBackup(aciIdentityKeyPair.publicKey.serialize(), aciIdentityKeyPair.privateKey.serialize())
SignalStore.account.restorePniIdentityKeyFromBackup(pniIdentityKeyPair.publicKey.serialize(), pniIdentityKeyPair.privateKey.serialize())
val aciPreKeyCollection = data.getAciPreKeyCollection()
val pniPreKeyCollection = data.getPniPreKeyCollection()
val aci: ACI = ACI.parseOrThrow(data.aci)
val pni: PNI = PNI.parseOrThrow(data.pni)
val hasPin: Boolean = data.hasPin
SignalStore.account.setAci(aci)
SignalStore.account.setPni(pni)
AppDependencies.resetProtocolStores()
AppDependencies.protocolStore.aci().sessions().archiveAllSessions()
AppDependencies.protocolStore.pni().sessions().archiveAllSessions()
SenderKeyUtil.clearAllState()
val aciProtocolStore = AppDependencies.protocolStore.aci()
val aciMetadataStore = SignalStore.account.aciPreKeys
val pniProtocolStore = AppDependencies.protocolStore.pni()
val pniMetadataStore = SignalStore.account.pniPreKeys
storeSignedAndLastResortPreKeys(aciProtocolStore, aciMetadataStore, aciPreKeyCollection)
storeSignedAndLastResortPreKeys(pniProtocolStore, pniMetadataStore, pniPreKeyCollection)
val recipientTable = SignalDatabase.recipients
val selfId = Recipient.trustedPush(aci, pni, data.e164).id
recipientTable.setProfileSharing(selfId, true)
recipientTable.markRegisteredOrThrow(selfId, aci)
recipientTable.linkIdsForSelf(aci, pni, data.e164)
recipientTable.setProfileKey(selfId, ProfileKey(data.profileKey.toByteArray()))
AppDependencies.recipientCache.clearSelf()
SignalStore.account.setE164(data.e164)
SignalStore.account.fcmToken = data.fcmToken
SignalStore.account.fcmEnabled = data.fcmEnabled
val now = System.currentTimeMillis()
saveOwnIdentityKey(selfId, aci, aciProtocolStore, now)
saveOwnIdentityKey(selfId, pni, pniProtocolStore, now)
if (data.linkedDeviceInfo != null) {
if (data.linkedDeviceInfo.accountEntropyPool != null) {
SignalStore.account.setAccountEntropyPoolFromPrimaryDevice(AccountEntropyPool(data.linkedDeviceInfo.accountEntropyPool))
}
if (data.linkedDeviceInfo.mediaRootBackupKey != null) {
SignalStore.backup.mediaRootBackupKey = MediaRootBackupKey(data.linkedDeviceInfo.mediaRootBackupKey.toByteArray())
}
}
SignalStore.account.setServicePassword(data.servicePassword)
SignalStore.account.setRegistered(true)
TextSecurePreferences.setPromptedPushRegistration(context, true)
TextSecurePreferences.setUnauthorizedReceived(context, false)
NotificationManagerCompat.from(context).cancel(NotificationIds.UNREGISTERED_NOTIFICATION_ID)
val masterKey = if (data.masterKey != null) MasterKey(data.masterKey.toByteArray()) else null
SvrRepository.onRegistrationComplete(masterKey, data.pin, hasPin, data.reglockEnabled, SignalStore.account.restoredAccountEntropyPool)
AppDependencies.resetNetwork()
AppDependencies.startNetwork()
PreKeysSyncJob.enqueue()
val jobManager = AppDependencies.jobManager
if (data.linkedDeviceInfo == null) {
jobManager.add(DirectoryRefreshJob(false))
jobManager.add(RotateCertificateJob())
DirectoryRefreshListener.schedule(context)
RotateSignedPreKeyListener.schedule(context)
} else {
SignalStore.account.isMultiDevice = true
jobManager.runJobBlocking(RefreshOwnProfileJob(), 30.seconds)
jobManager.add(RotateCertificateJob())
RotateSignedPreKeyListener.schedule(context)
} }
} }
SignalStore.account.setServicePassword(data.servicePassword)
SignalStore.account.setRegistered(true)
TextSecurePreferences.setPromptedPushRegistration(context, true)
TextSecurePreferences.setUnauthorizedReceived(context, false)
NotificationManagerCompat.from(context).cancel(NotificationIds.UNREGISTERED_NOTIFICATION_ID)
val masterKey = if (data.masterKey != null) MasterKey(data.masterKey.toByteArray()) else null
SvrRepository.onRegistrationComplete(masterKey, data.pin, hasPin, data.reglockEnabled, SignalStore.account.restoredAccountEntropyPool)
AppDependencies.resetNetwork()
AppDependencies.startNetwork()
PreKeysSyncJob.enqueue()
val jobManager = AppDependencies.jobManager
if (data.linkedDeviceInfo == null) {
jobManager.add(DirectoryRefreshJob(false))
jobManager.add(RotateCertificateJob())
DirectoryRefreshListener.schedule(context)
RotateSignedPreKeyListener.schedule(context)
} else {
SignalStore.account.isMultiDevice = true
jobManager.runJobBlocking(RefreshOwnProfileJob(), 30.seconds)
jobManager.add(RotateCertificateJob())
RotateSignedPreKeyListener.schedule(context)
}
}
@JvmStatic @JvmStatic
private fun saveOwnIdentityKey(selfId: RecipientId, serviceId: ServiceId, protocolStore: SignalServiceAccountDataStoreImpl, now: Long) { private fun saveOwnIdentityKey(selfId: RecipientId, serviceId: ServiceId, protocolStore: SignalServiceAccountDataStoreImpl, now: Long) {
protocolStore.identities().saveIdentityWithoutSideEffects( protocolStore.identities().saveIdentityWithoutSideEffects(
@@ -299,49 +296,46 @@ object RegistrationRepository {
return PinHashUtil.verifyLocalPinHash(pinHash, pin) return PinHashUtil.verifyLocalPinHash(pinHash, pin)
} }
suspend fun fetchMasterKeyFromSvrRemote(pin: String, svr2Credentials: AuthCredentials?, svr3Credentials: Svr3Credentials?): MasterKey = suspend fun fetchMasterKeyFromSvrRemote(pin: String, svr2Credentials: AuthCredentials?, svr3Credentials: Svr3Credentials?): MasterKey = withContext(Dispatchers.IO) {
withContext(Dispatchers.IO) { val credentialSet = SvrAuthCredentialSet(svr2Credentials = svr2Credentials, svr3Credentials = svr3Credentials)
val credentialSet = SvrAuthCredentialSet(svr2Credentials = svr2Credentials, svr3Credentials = svr3Credentials) val masterKey = SvrRepository.restoreMasterKeyPreRegistration(credentialSet, pin)
val masterKey = SvrRepository.restoreMasterKeyPreRegistration(credentialSet, pin) return@withContext masterKey
return@withContext masterKey }
}
/** /**
* Validates a session ID. * Validates a session ID.
*/ */
private suspend fun validateSession(context: Context, sessionId: String, e164: String, password: String): RegistrationSessionCheckResult = private suspend fun validateSession(context: Context, sessionId: String, e164: String, password: String): RegistrationSessionCheckResult = withContext(Dispatchers.IO) {
withContext(Dispatchers.IO) { val api: RegistrationApi = AccountManagerFactory.getInstance().createUnauthenticated(context, e164, SignalServiceAddress.DEFAULT_DEVICE_ID, password).registrationApi
val api: RegistrationApi = AccountManagerFactory.getInstance().createUnauthenticated(context, e164, SignalServiceAddress.DEFAULT_DEVICE_ID, password).registrationApi Log.d(TAG, "Validating registration session with service.")
Log.d(TAG, "Validating registration session with service.") val registrationSessionResult = api.getRegistrationSessionStatus(sessionId)
val registrationSessionResult = api.getRegistrationSessionStatus(sessionId) return@withContext RegistrationSessionCheckResult.from(registrationSessionResult)
return@withContext RegistrationSessionCheckResult.from(registrationSessionResult) }
}
/** /**
* Initiates a new registration session on the service. * Initiates a new registration session on the service.
*/ */
suspend fun createSession(context: Context, e164: String, password: String, mcc: String?, mnc: String?): RegistrationSessionCreationResult = suspend fun createSession(context: Context, e164: String, password: String, mcc: String?, mnc: String?): RegistrationSessionCreationResult = withContext(Dispatchers.IO) {
withContext(Dispatchers.IO) { Log.d(TAG, "About to create a registration session…")
Log.d(TAG, "About to create a registration session…") val fcmToken: String? = FcmUtil.getToken(context).orElse(null)
val fcmToken: String? = FcmUtil.getToken(context).orElse(null) val api: RegistrationApi = AccountManagerFactory.getInstance().createUnauthenticated(context, e164, SignalServiceAddress.DEFAULT_DEVICE_ID, password).registrationApi
val api: RegistrationApi = AccountManagerFactory.getInstance().createUnauthenticated(context, e164, SignalServiceAddress.DEFAULT_DEVICE_ID, password).registrationApi
val registrationSessionResult = if (fcmToken == null) { val registrationSessionResult = if (fcmToken == null) {
Log.d(TAG, "Creating registration session without FCM token.") Log.d(TAG, "Creating registration session without FCM token.")
api.createRegistrationSession(null, mcc, mnc) api.createRegistrationSession(null, mcc, mnc)
} else { } else {
Log.d(TAG, "Creating registration session with FCM token.") Log.d(TAG, "Creating registration session with FCM token.")
createSessionAndBlockForPushChallenge(api, fcmToken, mcc, mnc) createSessionAndBlockForPushChallenge(api, fcmToken, mcc, mnc)
}
val result = RegistrationSessionCreationResult.from(registrationSessionResult)
if (result is RegistrationSessionCreationResult.Success) {
Log.d(TAG, "Updating registration session and E164 in value store.")
SignalStore.registration.sessionId = result.sessionId
SignalStore.registration.sessionE164 = e164
}
return@withContext result
} }
val result = RegistrationSessionCreationResult.from(registrationSessionResult)
if (result is RegistrationSessionCreationResult.Success) {
Log.d(TAG, "Updating registration session and E164 in value store.")
SignalStore.registration.sessionId = result.sessionId
SignalStore.registration.sessionE164 = e164
}
return@withContext result
}
/** /**
* Validates an existing session, if its ID is provided. If the session is expired/invalid, or none is provided, it will attempt to initiate a new session. * Validates an existing session, if its ID is provided. If the session is expired/invalid, or none is provided, it will attempt to initiate a new session.
@@ -379,108 +373,103 @@ object RegistrationRepository {
/** /**
* Asks the service to send a verification code through one of our supported channels (SMS, phone call). * Asks the service to send a verification code through one of our supported channels (SMS, phone call).
*/ */
suspend fun requestSmsCode(context: Context, sessionId: String, e164: String, password: String, mode: E164VerificationMode): VerificationCodeRequestResult = suspend fun requestSmsCode(context: Context, sessionId: String, e164: String, password: String, mode: E164VerificationMode): VerificationCodeRequestResult = withContext(Dispatchers.IO) {
withContext(Dispatchers.IO) { val api: RegistrationApi = AccountManagerFactory.getInstance().createUnauthenticated(context, e164, SignalServiceAddress.DEFAULT_DEVICE_ID, password).registrationApi
val api: RegistrationApi = AccountManagerFactory.getInstance().createUnauthenticated(context, e164, SignalServiceAddress.DEFAULT_DEVICE_ID, password).registrationApi
val codeRequestResult = api.requestSmsVerificationCode(sessionId, Locale.getDefault(), mode.isSmsRetrieverSupported, mode.transport) val codeRequestResult = api.requestSmsVerificationCode(sessionId, Locale.getDefault(), mode.isSmsRetrieverSupported, mode.transport)
return@withContext VerificationCodeRequestResult.from(codeRequestResult) return@withContext VerificationCodeRequestResult.from(codeRequestResult)
} }
/** /**
* Submits the user-entered verification code to the service. * Submits the user-entered verification code to the service.
*/ */
suspend fun submitVerificationCode(context: Context, sessionId: String, registrationData: RegistrationData): VerificationCodeRequestResult = suspend fun submitVerificationCode(context: Context, sessionId: String, registrationData: RegistrationData): VerificationCodeRequestResult = withContext(Dispatchers.IO) {
withContext(Dispatchers.IO) { val api: RegistrationApi = AccountManagerFactory.getInstance().createUnauthenticated(context, registrationData.e164, SignalServiceAddress.DEFAULT_DEVICE_ID, registrationData.password).registrationApi
val api: RegistrationApi = AccountManagerFactory.getInstance().createUnauthenticated(context, registrationData.e164, SignalServiceAddress.DEFAULT_DEVICE_ID, registrationData.password).registrationApi val result = api.verifyAccount(sessionId = sessionId, verificationCode = registrationData.code)
val result = api.verifyAccount(sessionId = sessionId, verificationCode = registrationData.code) return@withContext VerificationCodeRequestResult.from(result)
return@withContext VerificationCodeRequestResult.from(result) }
}
/** /**
* Submits the solved captcha token to the service. * Submits the solved captcha token to the service.
*/ */
suspend fun submitCaptchaToken(context: Context, e164: String, password: String, sessionId: String, captchaToken: String): VerificationCodeRequestResult = suspend fun submitCaptchaToken(context: Context, e164: String, password: String, sessionId: String, captchaToken: String): VerificationCodeRequestResult = withContext(Dispatchers.IO) {
withContext(Dispatchers.IO) { val api: RegistrationApi = AccountManagerFactory.getInstance().createUnauthenticated(context, e164, SignalServiceAddress.DEFAULT_DEVICE_ID, password).registrationApi
val api: RegistrationApi = AccountManagerFactory.getInstance().createUnauthenticated(context, e164, SignalServiceAddress.DEFAULT_DEVICE_ID, password).registrationApi val captchaSubmissionResult = api.submitCaptchaToken(sessionId = sessionId, captchaToken = captchaToken)
val captchaSubmissionResult = api.submitCaptchaToken(sessionId = sessionId, captchaToken = captchaToken) return@withContext VerificationCodeRequestResult.from(captchaSubmissionResult)
return@withContext VerificationCodeRequestResult.from(captchaSubmissionResult) }
}
suspend fun requestAndVerifyPushToken(context: Context, sessionId: String, e164: String, password: String) = suspend fun requestAndVerifyPushToken(context: Context, sessionId: String, e164: String, password: String) = withContext(Dispatchers.IO) {
withContext(Dispatchers.IO) { val fcmToken = getFcmToken(context)
val fcmToken = getFcmToken(context) val accountManager = AccountManagerFactory.getInstance().createUnauthenticated(context, e164, SignalServiceAddress.DEFAULT_DEVICE_ID, password)
val accountManager = AccountManagerFactory.getInstance().createUnauthenticated(context, e164, SignalServiceAddress.DEFAULT_DEVICE_ID, password) val pushChallenge = PushChallengeRequest.getPushChallengeBlocking(accountManager, sessionId, Optional.ofNullable(fcmToken), PUSH_REQUEST_TIMEOUT).orElse(null)
val pushChallenge = PushChallengeRequest.getPushChallengeBlocking(accountManager, sessionId, Optional.ofNullable(fcmToken), PUSH_REQUEST_TIMEOUT).orElse(null) val pushSubmissionResult = accountManager.registrationApi.submitPushChallengeToken(sessionId = sessionId, pushChallengeToken = pushChallenge)
val pushSubmissionResult = accountManager.registrationApi.submitPushChallengeToken(sessionId = sessionId, pushChallengeToken = pushChallenge) return@withContext VerificationCodeRequestResult.from(pushSubmissionResult)
return@withContext VerificationCodeRequestResult.from(pushSubmissionResult) }
}
/** /**
* Submit the necessary assets as a verified account so that the user can actually use the service. * Submit the necessary assets as a verified account so that the user can actually use the service.
*/ */
suspend fun registerAccount(context: Context, sessionId: String?, registrationData: RegistrationData, pin: String? = null, masterKeyProducer: MasterKeyProducer? = null): RegisterAccountResult = suspend fun registerAccount(context: Context, sessionId: String?, registrationData: RegistrationData, pin: String? = null, masterKeyProducer: MasterKeyProducer? = null): RegisterAccountResult = withContext(Dispatchers.IO) {
withContext(Dispatchers.IO) { Log.v(TAG, "registerAccount()")
Log.v(TAG, "registerAccount()") val api: RegistrationApi = AccountManagerFactory.getInstance().createUnauthenticated(context, registrationData.e164, SignalServiceAddress.DEFAULT_DEVICE_ID, registrationData.password).registrationApi
val api: RegistrationApi = AccountManagerFactory.getInstance().createUnauthenticated(context, registrationData.e164, SignalServiceAddress.DEFAULT_DEVICE_ID, registrationData.password).registrationApi
val universalUnidentifiedAccess: Boolean = TextSecurePreferences.isUniversalUnidentifiedAccess(context) val universalUnidentifiedAccess: Boolean = TextSecurePreferences.isUniversalUnidentifiedAccess(context)
val unidentifiedAccessKey: ByteArray = UnidentifiedAccess.deriveAccessKeyFrom(registrationData.profileKey) val unidentifiedAccessKey: ByteArray = UnidentifiedAccess.deriveAccessKeyFrom(registrationData.profileKey)
val masterKey: MasterKey? val masterKey: MasterKey?
try { try {
masterKey = masterKeyProducer?.produceMasterKey() masterKey = masterKeyProducer?.produceMasterKey()
} catch (e: SvrNoDataException) { } catch (e: SvrNoDataException) {
return@withContext RegisterAccountResult.SvrNoData(e) return@withContext RegisterAccountResult.SvrNoData(e)
} catch (e: SvrWrongPinException) { } catch (e: SvrWrongPinException) {
return@withContext RegisterAccountResult.SvrWrongPin(e) return@withContext RegisterAccountResult.SvrWrongPin(e)
} catch (e: IOException) { } catch (e: IOException) {
return@withContext RegisterAccountResult.UnknownError(e) return@withContext RegisterAccountResult.UnknownError(e)
}
val registrationLock: String? = masterKey?.deriveRegistrationLock()
val accountAttributes = AccountAttributes(
signalingKey = null,
registrationId = registrationData.registrationId,
fetchesMessages = registrationData.isNotFcm,
registrationLock = registrationLock,
unidentifiedAccessKey = unidentifiedAccessKey,
unrestrictedUnidentifiedAccess = universalUnidentifiedAccess,
capabilities = AppCapabilities.getCapabilities(true),
discoverableByPhoneNumber = SignalStore.phoneNumberPrivacy.phoneNumberDiscoverabilityMode == PhoneNumberPrivacyValues.PhoneNumberDiscoverabilityMode.DISCOVERABLE,
name = null,
pniRegistrationId = registrationData.pniRegistrationId,
recoveryPassword = registrationData.recoveryPassword
)
SignalStore.account.generateAciIdentityKeyIfNecessary()
val aciIdentity: IdentityKeyPair = SignalStore.account.aciIdentityKey
SignalStore.account.generatePniIdentityKeyIfNecessary()
val pniIdentity: IdentityKeyPair = SignalStore.account.pniIdentityKey
val aciPreKeyCollection = generateSignedAndLastResortPreKeys(aciIdentity, SignalStore.account.aciPreKeys)
val pniPreKeyCollection = generateSignedAndLastResortPreKeys(pniIdentity, SignalStore.account.pniPreKeys)
val result: NetworkResult<AccountRegistrationResult> = api.registerAccount(sessionId, registrationData.recoveryPassword, accountAttributes, aciPreKeyCollection, pniPreKeyCollection, registrationData.fcmToken, true)
.map { accountRegistrationResponse: VerifyAccountResponse ->
AccountRegistrationResult(
uuid = accountRegistrationResponse.uuid,
pni = accountRegistrationResponse.pni,
storageCapable = accountRegistrationResponse.storageCapable,
number = accountRegistrationResponse.number,
masterKey = masterKey,
pin = pin,
aciPreKeyCollection = aciPreKeyCollection,
pniPreKeyCollection = pniPreKeyCollection,
reRegistration = accountRegistrationResponse.reregistration
)
} }
val registrationLock: String? = masterKey?.deriveRegistrationLock() return@withContext RegisterAccountResult.from(result)
}
val accountAttributes = AccountAttributes(
signalingKey = null,
registrationId = registrationData.registrationId,
fetchesMessages = registrationData.isNotFcm,
registrationLock = registrationLock,
unidentifiedAccessKey = unidentifiedAccessKey,
unrestrictedUnidentifiedAccess = universalUnidentifiedAccess,
capabilities = AppCapabilities.getCapabilities(true),
discoverableByPhoneNumber = SignalStore.phoneNumberPrivacy.phoneNumberDiscoverabilityMode == PhoneNumberPrivacyValues.PhoneNumberDiscoverabilityMode.DISCOVERABLE,
name = null,
pniRegistrationId = registrationData.pniRegistrationId,
recoveryPassword = registrationData.recoveryPassword
)
SignalStore.account.generateAciIdentityKeyIfNecessary()
val aciIdentity: IdentityKeyPair = SignalStore.account.aciIdentityKey
SignalStore.account.generatePniIdentityKeyIfNecessary()
val pniIdentity: IdentityKeyPair = SignalStore.account.pniIdentityKey
val aciPreKeyCollection = generateSignedAndLastResortPreKeys(aciIdentity, SignalStore.account.aciPreKeys)
val pniPreKeyCollection = generateSignedAndLastResortPreKeys(pniIdentity, SignalStore.account.pniPreKeys)
val result: NetworkResult<AccountRegistrationResult> = api.registerAccount(sessionId, registrationData.recoveryPassword, accountAttributes, aciPreKeyCollection, pniPreKeyCollection, registrationData.fcmToken, true)
.map { accountRegistrationResponse: VerifyAccountResponse ->
AccountRegistrationResult(
uuid = accountRegistrationResponse.uuid,
pni = accountRegistrationResponse.pni,
storageCapable = accountRegistrationResponse.storageCapable,
number = accountRegistrationResponse.number,
masterKey = masterKey,
pin = pin,
aciPreKeyCollection = aciPreKeyCollection,
pniPreKeyCollection = pniPreKeyCollection,
reRegistration = accountRegistrationResponse.reregistration
)
}
return@withContext RegisterAccountResult.from(result)
}
@WorkerThread @WorkerThread
fun registerAsLinkedDevice( fun registerAsLinkedDevice(
@@ -539,85 +528,83 @@ object RegistrationRepository {
} }
} }
private suspend fun createSessionAndBlockForPushChallenge(accountManager: RegistrationApi, fcmToken: String, mcc: String?, mnc: String?): NetworkResult<RegistrationSessionMetadataResponse> = private suspend fun createSessionAndBlockForPushChallenge(accountManager: RegistrationApi, fcmToken: String, mcc: String?, mnc: String?): NetworkResult<RegistrationSessionMetadataResponse> = withContext(Dispatchers.IO) {
withContext(Dispatchers.IO) { // TODO [regv2]: do not use event bus nor latch
// TODO [regv2]: do not use event bus nor latch val subscriber = PushTokenChallengeSubscriber()
val subscriber = PushTokenChallengeSubscriber() val eventBus = EventBus.getDefault()
val eventBus = EventBus.getDefault() eventBus.register(subscriber)
eventBus.register(subscriber)
try { try {
Log.d(TAG, "Requesting a registration session with FCM token…") Log.d(TAG, "Requesting a registration session with FCM token…")
val sessionCreationResponse = accountManager.createRegistrationSession(fcmToken, mcc, mnc) val sessionCreationResponse = accountManager.createRegistrationSession(fcmToken, mcc, mnc)
if (sessionCreationResponse !is NetworkResult.Success) { if (sessionCreationResponse !is NetworkResult.Success) {
return@withContext sessionCreationResponse
}
val receivedPush = subscriber.latch.await(PUSH_REQUEST_TIMEOUT, TimeUnit.MILLISECONDS)
eventBus.unregister(subscriber)
if (receivedPush) {
val challenge = subscriber.challenge
if (challenge != null) {
Log.i(TAG, "Push challenge token received.")
return@withContext accountManager.submitPushChallengeToken(sessionCreationResponse.result.metadata.id, challenge)
} else {
Log.w(TAG, "Push received but challenge token was null.")
}
} else {
Log.i(TAG, "Push challenge timed out.")
}
Log.i(TAG, "Push challenge unsuccessful. Continuing with session created without one.")
return@withContext sessionCreationResponse return@withContext sessionCreationResponse
} catch (ex: Exception) {
Log.w(TAG, "Exception caught, but the earlier try block should have caught it?", ex)
return@withContext NetworkResult.ApplicationError<RegistrationSessionMetadataResponse>(ex)
} }
}
suspend fun hasValidSvrAuthCredentials(context: Context, e164: String, password: String): BackupAuthCheckResult = val receivedPush = subscriber.latch.await(PUSH_REQUEST_TIMEOUT, TimeUnit.MILLISECONDS)
withContext(Dispatchers.IO) { eventBus.unregister(subscriber)
val api: RegistrationApi = AccountManagerFactory.getInstance().createUnauthenticated(context, e164, SignalServiceAddress.DEFAULT_DEVICE_ID, password).registrationApi
val svr3Result = SignalStore.svr.svr3AuthTokens if (receivedPush) {
?.takeIf { Svr3Migration.shouldReadFromSvr3 } val challenge = subscriber.challenge
?.takeIf { it.isNotEmpty() } if (challenge != null) {
?.toSvrCredentials() Log.i(TAG, "Push challenge token received.")
?.let { authTokens -> return@withContext accountManager.submitPushChallengeToken(sessionCreationResponse.result.metadata.id, challenge)
api } else {
.validateSvr3AuthCredential(e164, authTokens) Log.w(TAG, "Push received but challenge token was null.")
.runIfSuccessful {
val removedInvalidTokens = SignalStore.svr.removeSvr3AuthTokens(it.invalid)
if (removedInvalidTokens) {
BackupManager(context).dataChanged()
}
}
.let { BackupAuthCheckResult.fromV3(it) }
} }
} else {
Log.i(TAG, "Push challenge timed out.")
}
Log.i(TAG, "Push challenge unsuccessful. Continuing with session created without one.")
return@withContext sessionCreationResponse
} catch (ex: Exception) {
Log.w(TAG, "Exception caught, but the earlier try block should have caught it?", ex)
return@withContext NetworkResult.ApplicationError<RegistrationSessionMetadataResponse>(ex)
}
}
if (svr3Result is BackupAuthCheckResult.SuccessWithCredentials) { suspend fun hasValidSvrAuthCredentials(context: Context, e164: String, password: String): BackupAuthCheckResult = withContext(Dispatchers.IO) {
Log.d(TAG, "Found valid SVR3 credentials.") val api: RegistrationApi = AccountManagerFactory.getInstance().createUnauthenticated(context, e164, SignalServiceAddress.DEFAULT_DEVICE_ID, password).registrationApi
return@withContext svr3Result
val svr3Result = SignalStore.svr.svr3AuthTokens
?.takeIf { Svr3Migration.shouldReadFromSvr3 }
?.takeIf { it.isNotEmpty() }
?.toSvrCredentials()
?.let { authTokens ->
api
.validateSvr3AuthCredential(e164, authTokens)
.runIfSuccessful {
val removedInvalidTokens = SignalStore.svr.removeSvr3AuthTokens(it.invalid)
if (removedInvalidTokens) {
BackupManager(context).dataChanged()
}
}
.let { BackupAuthCheckResult.fromV3(it) }
} }
Log.d(TAG, "No valid SVR3 credentials, looking for SVR2.") if (svr3Result is BackupAuthCheckResult.SuccessWithCredentials) {
Log.d(TAG, "Found valid SVR3 credentials.")
return@withContext SignalStore.svr.svr2AuthTokens return@withContext svr3Result
?.takeIf { it.isNotEmpty() }
?.toSvrCredentials()
?.let { authTokens ->
api
.validateSvr2AuthCredential(e164, authTokens)
.runIfSuccessful {
val removedInvalidTokens = SignalStore.svr.removeSvr2AuthTokens(it.invalid)
if (removedInvalidTokens) {
BackupManager(context).dataChanged()
}
}
.let { BackupAuthCheckResult.fromV2(it) }
} ?: BackupAuthCheckResult.SuccessWithoutCredentials()
} }
Log.d(TAG, "No valid SVR3 credentials, looking for SVR2.")
return@withContext SignalStore.svr.svr2AuthTokens
?.takeIf { it.isNotEmpty() }
?.toSvrCredentials()
?.let { authTokens ->
api
.validateSvr2AuthCredential(e164, authTokens)
.runIfSuccessful {
val removedInvalidTokens = SignalStore.svr.removeSvr2AuthTokens(it.invalid)
if (removedInvalidTokens) {
BackupManager(context).dataChanged()
}
}
.let { BackupAuthCheckResult.fromV2(it) }
} ?: BackupAuthCheckResult.SuccessWithoutCredentials()
}
/** Converts the basic-auth creds we have locally into username:password pairs that are suitable for handing off to the service. */ /** Converts the basic-auth creds we have locally into username:password pairs that are suitable for handing off to the service. */
private fun List<String?>.toSvrCredentials(): List<String> { private fun List<String?>.toSvrCredentials(): List<String> {
return this return this

View File

@@ -102,7 +102,9 @@ class ContactRecordProcessor(
if (!hasAci && !hasPni) { if (!hasAci && !hasPni) {
Log.w(TAG, "Found a ContactRecord with neither an ACI nor a PNI -- marking as invalid.") Log.w(TAG, "Found a ContactRecord with neither an ACI nor a PNI -- marking as invalid.")
return true return true
} else if (selfAci != null && selfAci == remote.proto.signalAci || } else if (
selfAci != null &&
selfAci == remote.proto.signalAci ||
(selfPni != null && selfPni == remote.proto.signalPni) || (selfPni != null && selfPni == remote.proto.signalPni) ||
(selfE164 != null && remote.proto.e164.isNotBlank() && remote.proto.e164 == selfE164) (selfE164 != null && remote.proto.e164.isNotBlank() && remote.proto.e164 == selfE164)
) { ) {

View File

@@ -309,7 +309,8 @@ class FullSignalAudioManager(context: Context, eventListener: EventListener?) :
} }
val needBluetoothAudioStart = signalBluetoothManager.state == SignalBluetoothManager.State.AVAILABLE && val needBluetoothAudioStart = signalBluetoothManager.state == SignalBluetoothManager.State.AVAILABLE &&
(userSelectedAudioDevice == AudioDevice.NONE || userSelectedAudioDevice == AudioDevice.BLUETOOTH || autoSwitchToBluetooth) && !androidAudioManager.isBluetoothScoOn (userSelectedAudioDevice == AudioDevice.NONE || userSelectedAudioDevice == AudioDevice.BLUETOOTH || autoSwitchToBluetooth) &&
!androidAudioManager.isBluetoothScoOn
val needBluetoothAudioStop = (signalBluetoothManager.state == SignalBluetoothManager.State.CONNECTED || signalBluetoothManager.state == SignalBluetoothManager.State.CONNECTING) && val needBluetoothAudioStop = (signalBluetoothManager.state == SignalBluetoothManager.State.CONNECTED || signalBluetoothManager.state == SignalBluetoothManager.State.CONNECTING) &&
(userSelectedAudioDevice != AudioDevice.NONE && userSelectedAudioDevice != AudioDevice.BLUETOOTH) (userSelectedAudioDevice != AudioDevice.NONE && userSelectedAudioDevice != AudioDevice.BLUETOOTH)

View File

@@ -171,9 +171,8 @@ class EmojiJsonParserTest {
private fun uriFactory(sprite: String, format: String) = Uri.parse("file:///$sprite") private fun uriFactory(sprite: String, format: String) = Uri.parse("file:///$sprite")
private fun EmojiPageModel.isSameAs(other: EmojiPageModel) = private fun EmojiPageModel.isSameAs(other: EmojiPageModel) = this.javaClass == other.javaClass &&
this.javaClass == other.javaClass && this.emoji == other.emoji &&
this.emoji == other.emoji && this.iconAttr == other.iconAttr &&
this.iconAttr == other.iconAttr && this.spriteUri == other.spriteUri
this.spriteUri == other.spriteUri
} }

View File

@@ -14,6 +14,5 @@ class GiphyMp4PlaybackControllerRangeComparatorTest {
Assert.assertArrayEquals(expected, sorted) Assert.assertArrayEquals(expected, sorted)
} }
private fun createComparator(start: Int, end: Int): GiphyMp4PlaybackController.RangeComparator = private fun createComparator(start: Int, end: Int): GiphyMp4PlaybackController.RangeComparator = GiphyMp4PlaybackController.RangeComparator(start, end)
GiphyMp4PlaybackController.RangeComparator(start, end)
} }

View File

@@ -3,5 +3,5 @@ plugins {
} }
ktlint { ktlint {
version.set("1.2.1") version.set("1.5.0")
} }

View File

@@ -21,7 +21,7 @@ kotlin {
// NOTE: For now, in order to run ktlint on this project, you have to manually run ./gradlew :build-logic:tools:ktlintFormat // NOTE: For now, in order to run ktlint on this project, you have to manually run ./gradlew :build-logic:tools:ktlintFormat
// Gotta figure out how to get it auto-included in the normal ./gradlew ktlintFormat // Gotta figure out how to get it auto-included in the normal ./gradlew ktlintFormat
ktlint { ktlint {
version.set("1.2.1") version.set("1.5.0")
} }
dependencies { dependencies {

View File

@@ -39,13 +39,12 @@ object IconButtons {
disabledContainerColor: Color = Color.Transparent, disabledContainerColor: Color = Color.Transparent,
disabledContentColor: Color = disabledContentColor: Color =
contentColor.copy(alpha = 0.38f) contentColor.copy(alpha = 0.38f)
): IconButtonColors = ): IconButtonColors = IconButtonColors(
IconButtonColors( containerColor = containerColor,
containerColor = containerColor, contentColor = contentColor,
contentColor = contentColor, disabledContainerColor = disabledContainerColor,
disabledContainerColor = disabledContainerColor, disabledContentColor = disabledContentColor
disabledContentColor = disabledContentColor )
)
@Composable @Composable
fun iconToggleButtonColors( fun iconToggleButtonColors(
@@ -56,15 +55,14 @@ object IconButtons {
contentColor.copy(alpha = 0.38f), contentColor.copy(alpha = 0.38f),
checkedContainerColor: Color = Color.Transparent, checkedContainerColor: Color = Color.Transparent,
checkedContentColor: Color = MaterialTheme.colorScheme.primary checkedContentColor: Color = MaterialTheme.colorScheme.primary
): IconToggleButtonColors = ): IconToggleButtonColors = IconToggleButtonColors(
IconToggleButtonColors( containerColor = containerColor,
containerColor = containerColor, contentColor = contentColor,
contentColor = contentColor, disabledContainerColor = disabledContainerColor,
disabledContainerColor = disabledContainerColor, disabledContentColor = disabledContentColor,
disabledContentColor = disabledContentColor, checkedContainerColor = checkedContainerColor,
checkedContainerColor = checkedContainerColor, checkedContentColor = checkedContentColor
checkedContentColor = checkedContentColor )
)
@Composable @Composable
fun IconButton( fun IconButton(

View File

@@ -127,5 +127,4 @@ suspend fun AwaitPointerEventScope.awaitLongPressOrCancellation(
} }
} }
private fun PointerEvent.isPointerUp(pointerId: PointerId): Boolean = private fun PointerEvent.isPointerUp(pointerId: PointerId): Boolean = changes.fastFirstOrNull { it.id == pointerId }?.pressed != true
changes.fastFirstOrNull { it.id == pointerId }?.pressed != true

View File

@@ -62,8 +62,7 @@ class ResultEventBus {
/** /**
* Provides a flow for the given resultKey. * Provides a flow for the given resultKey.
*/ */
inline fun <reified T> getResultFlow(resultKey: String = T::class.toString()) = inline fun <reified T> getResultFlow(resultKey: String = T::class.toString()) = channelMap[resultKey]?.receiveAsFlow()
channelMap[resultKey]?.receiveAsFlow()
/** /**
* Sends a result into the channel associated with the given resultKey. * Sends a result into the channel associated with the given resultKey.

View File

@@ -72,7 +72,8 @@ fun setupWebView(
webview.evaluateJavascript("editor.setValue($originalContent, -1);", null) webview.evaluateJavascript("editor.setValue($originalContent, -1);", null)
} }
copyButton.setOnClickListener { // In Signal app, use Util.writeTextToClipboard(context, value) instead copyButton.setOnClickListener {
// In Signal app, use Util.writeTextToClipboard(context, value) instead
webview.evaluateJavascript("editor.getValue();") { value -> webview.evaluateJavascript("editor.getValue();") { value ->
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText(context.getString(R.string.app_name), value) val clip = ClipData.newPlainText(context.getString(R.string.app_name), value)

View File

@@ -22,7 +22,7 @@ accompanist = "0.28.0"
nanohttpd = "2.3.1" nanohttpd = "2.3.1"
navigation-safe-args-gradle-plugin = "2.8.5" navigation-safe-args-gradle-plugin = "2.8.5"
protobuf-gradle-plugin = "0.9.0" protobuf-gradle-plugin = "0.9.0"
ktlint = "12.1.1" ktlint = "14.0.1"
ui-test-junit4 = "1.9.4" ui-test-junit4 = "1.9.4"
[plugins] [plugins]

File diff suppressed because it is too large Load Diff

View File

@@ -69,7 +69,7 @@ afterEvaluate {
} }
ktlint { ktlint {
version.set("1.2.1") version.set("1.5.0")
filter { filter {
exclude { entry -> exclude { entry ->