Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
781dce8
feat: Display tags by alphabetical order
ohassine Nov 12, 2025
b53431b
chore: updating strings (WPB-21710) (#4404)
sbakhtiarov Nov 12, 2025
9baf147
fix: text colors for cell attachment file names and reply preview (WP…
sbakhtiarov Nov 12, 2025
420cf9c
fix: bubble feedback improvements [WPB-21718] (#4408)
Garzas Nov 12, 2025
c81ead6
feat: view wire cell images in internal viewer (WPB-21325) (#4405)
sbakhtiarov Nov 14, 2025
cf96727
chore: removing string duplicate with invalid text (WPB-21420)
sbakhtiarov Nov 14, 2025
3cbcc91
feat: edit multipart message text (WPB-16899) (#4401)
sbakhtiarov Nov 14, 2025
c179728
fix: missing message content with bubble disabled [WPB-21763] (#4418)
Garzas Nov 14, 2025
9c0d72c
refactor: replace amplituda with avs built-in solution v2 [WPB-19814]…
saleniuk Nov 14, 2025
301dbb7
feat(apps): new flows for toggling apps in conversations (WPB-21442) …
yamilmedina Nov 14, 2025
f9b1348
chore(deps): [WPB-9777] bump actions/upload-artifact from 4 to 5 (#4403)
dependabot[bot] Nov 15, 2025
7bfde08
chore(l10n): update localization strings via Crowdin (WPB-9776) (#4382)
AndroidBob Nov 15, 2025
7dc4fd4
fix: message paging performance [WPB-20805] [WPB-19642]
MohamadJaara Nov 17, 2025
6b42102
refactor: pass reactions as one map with flag it it includes self or …
MohamadJaara Nov 17, 2025
a69c4ba
fix(apps): update copies to allow easier translations management (WPB…
yamilmedina Nov 17, 2025
70358be
Merge remote-tracking branch 'origin/Display-tags-by-alphabetical-ord…
ohassine Nov 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build-unified.yml
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ jobs:
run: mv app/version.txt app/build/outputs/

- name: Upload build artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: ${{ matrix.flavor }}-${{ matrix.variant }}-artifacts
path: app/build/outputs/
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/generate-screenshots.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ jobs:

- name: Upload Screenshot Test Report
id: upload_artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: screenshot-test-report
path: screenshot-test-report.zip
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/gradle-run-ui-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ jobs:
zip -r integration-tests-android_${{ env.COMMIT_HASH }}.zip acceptanceTests/flavors/

- name: Upload zipped test results
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: integration-tests-android_${{ env.COMMIT_HASH }}-${{ inputs.artifact-suffix }}
path: integration-tests-android_${{ env.COMMIT_HASH }}.zip
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/gradle-run-unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,14 @@ jobs:
zip -r unit-tests-android_${{ env.COMMIT_HASH }}.zip **/build/test-results/**/*.xml **/build/outputs/androidTest-results/**/*.xml

- name: Upload zipped test results
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: unit-tests-android_${{ env.COMMIT_HASH }}-${{ inputs.artifact-suffix }}
path: unit-tests-android_${{ env.COMMIT_HASH }}.zip

# Uploads test results as GitHub artifacts, so publish-test-results can find them later.
- name: Upload Test Results
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
if: always()
with:
name: test-results-${{ inputs.artifact-suffix }}
Expand All @@ -72,7 +72,7 @@ jobs:
**/build/outputs/androidTest-results/**/*.xml

- name: Generate report
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: report-${{ inputs.artifact-suffix }}
path: app/build/reports/kover
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import com.wire.android.util.UserAgentProvider
import com.wire.kalium.logic.CoreLogic
import com.wire.kalium.logic.data.id.QualifiedIdMapper
import com.wire.kalium.logic.data.id.QualifiedIdMapperImpl
import com.wire.kalium.logic.feature.asset.AudioNormalizedLoudnessBuilder
import com.wire.kalium.logic.feature.server.ServerConfigForAccountUseCase
import com.wire.kalium.logic.feature.session.GetSessionsUseCase
import com.wire.kalium.logic.feature.session.ObserveSessionsUseCase
Expand Down Expand Up @@ -115,4 +116,8 @@ class TestCoreLogicModule {
@Singleton
@Provides
fun provideWorkManager(@ApplicationContext applicationContext: Context) = WorkManager.getInstance(applicationContext)

@Provides
fun provideAudioNormalizedLoudnessBuilder(@KaliumCoreLogic coreLogic: CoreLogic): AudioNormalizedLoudnessBuilder =
coreLogic.audioNormalizedLoudnessBuilder
}
5 changes: 5 additions & 0 deletions app/src/main/kotlin/com/wire/android/di/CoreLogicModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import com.wire.kalium.logic.data.id.QualifiedIdMapper
import com.wire.kalium.logic.data.id.QualifiedIdMapperImpl
import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.feature.analytics.GetCurrentAnalyticsTrackingIdentifierUseCase
import com.wire.kalium.logic.feature.asset.AudioNormalizedLoudnessBuilder
import com.wire.kalium.logic.feature.auth.AddAuthenticatedUserUseCase
import com.wire.kalium.logic.feature.auth.LogoutUseCase
import com.wire.kalium.logic.feature.auth.sso.ValidateSSOCodeUseCase
Expand Down Expand Up @@ -143,6 +144,10 @@ class CoreLogicModule {
@Singleton
@Provides
fun provideWorkManager(@ApplicationContext applicationContext: Context) = WorkManager.getInstance(applicationContext)

@Provides
fun provideAudioNormalizedLoudnessBuilder(@KaliumCoreLogic coreLogic: CoreLogic): AudioNormalizedLoudnessBuilder =
coreLogic.audioNormalizedLoudnessBuilder
}

@Module
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ import com.wire.kalium.cells.domain.usecase.CreateFolderUseCase
import com.wire.kalium.cells.domain.usecase.DeleteCellAssetUseCase
import com.wire.kalium.cells.domain.usecase.DownloadCellFileUseCase
import com.wire.kalium.cells.domain.usecase.GetAllTagsUseCase
import com.wire.kalium.cells.domain.usecase.GetCellFileUseCase
import com.wire.kalium.cells.domain.usecase.GetFoldersUseCase
import com.wire.kalium.cells.domain.usecase.GetMessageAttachmentUseCase
import com.wire.kalium.cells.domain.usecase.GetPaginatedFilesFlowUseCase
import com.wire.kalium.cells.domain.usecase.GetPaginatedNodesUseCase
import com.wire.kalium.cells.domain.usecase.IsAtLeastOneCellAvailableUseCase
Expand Down Expand Up @@ -164,6 +166,13 @@ class CellsModule {
@Provides
fun provideCellAvailableUseCase(cellsScope: CellsScope): IsAtLeastOneCellAvailableUseCase = cellsScope.isCellAvailable

@ViewModelScoped
@Provides
fun provideGetAttachmentUseCase(cellsScope: CellsScope): GetMessageAttachmentUseCase = cellsScope.getMessageAttachmentUseCase

@Provides
fun provideFileNameResolver(): FileNameResolver = FileNameResolver()

@Provides
fun provideGetCellNodeUseCase(cellsScope: CellsScope): GetCellFileUseCase = cellsScope.getCellFileUseCase
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import com.wire.kalium.logic.feature.conversation.UpdateConversationMemberRoleUs
import com.wire.kalium.logic.feature.conversation.UpdateConversationMutedStatusUseCase
import com.wire.kalium.logic.feature.conversation.UpdateConversationReadDateUseCase
import com.wire.kalium.logic.feature.conversation.UpdateConversationReceiptModeUseCase
import com.wire.kalium.logic.feature.conversation.apps.ChangeAccessForAppsInConversationUseCase
import com.wire.kalium.logic.feature.conversation.createconversation.CreateChannelUseCase
import com.wire.kalium.logic.feature.conversation.createconversation.CreateRegularGroupUseCase
import com.wire.kalium.logic.feature.conversation.delete.MarkConversationAsDeletedLocallyUseCase
Expand Down Expand Up @@ -385,4 +386,11 @@ class ConversationModule {
@KaliumCoreLogic coreLogic: CoreLogic,
@CurrentAccount currentAccount: UserId
): FetchConversationUseCase = coreLogic.getSessionScope(currentAccount).fetchConversationUseCase

@ViewModelScoped
@Provides
fun provideChangeAccessForAppsInConversationUseCase(
conversationScope: ConversationScope
): ChangeAccessForAppsInConversationUseCase =
conversationScope.changeAccessForAppsInConversation
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import com.wire.kalium.logic.feature.asset.GetPaginatedFlowOfAssetMessageByConve
import com.wire.kalium.logic.feature.asset.ObserveAssetStatusesUseCase
import com.wire.kalium.logic.feature.asset.ObservePaginatedAssetImageMessages
import com.wire.kalium.logic.feature.asset.UpdateAssetMessageTransferStatusUseCase
import com.wire.kalium.logic.feature.asset.UpdateAudioMessageNormalizedLoudnessUseCase
import com.wire.kalium.logic.feature.asset.upload.ScheduleNewAssetMessageUseCase
import com.wire.kalium.logic.feature.incallreaction.SendInCallReactionUseCase
import com.wire.kalium.logic.feature.message.DeleteMessageUseCase
Expand Down Expand Up @@ -240,4 +241,9 @@ class MessageModule {
@Provides
fun provideSendMultipartMessageUseCase(messageScope: MessageScope): SendMultipartMessageUseCase =
messageScope.sendMultipartMessage

@ViewModelScoped
@Provides
fun provideUpdateAudioMessageNormalizedLoudnessUseCase(messageScope: MessageScope): UpdateAudioMessageNormalizedLoudnessUseCase =
messageScope.updateAudioMessageNormalizedLoudnessUseCase
}
11 changes: 8 additions & 3 deletions app/src/main/kotlin/com/wire/android/mapper/MessageMapper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import com.wire.android.ui.home.conversations.model.MessageSource
import com.wire.android.ui.home.conversations.model.MessageStatus
import com.wire.android.ui.home.conversations.model.MessageTime
import com.wire.android.ui.home.conversations.model.Reaction
import com.wire.android.ui.home.conversations.model.UIMessage
import com.wire.android.ui.home.conversations.model.UIMessageContent
import com.wire.android.ui.home.conversations.previewAsset
Expand Down Expand Up @@ -85,9 +86,13 @@

val footer = if (message is Message.Regular) {
MessageFooter(
message.id,
message.reactions.totalReactions,
message.reactions.selfUserReactions
messageId = message.id,
reactionMap = message.reactions.reactions.mapValues { (_, reaction) ->
Reaction(
count = reaction.count,
isSelf = reaction.isSelf

Check warning on line 93 in app/src/main/kotlin/com/wire/android/mapper/MessageMapper.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/mapper/MessageMapper.kt#L91-L93

Added lines #L91 - L93 were not covered by tests
)
}
)
} else {
MessageFooter(message.id)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,29 @@
is MessageContent.NewConversationWithCellMessage -> UIMessageContent.SystemMessage.NewConversationWithCellStarted
is MessageContent.NewConversationWithCellSelfDeleteDisabledMessage ->
UIMessageContent.SystemMessage.NewConversationWithCellSelfDeleteDisabled

is MessageContent.ConversationAppsEnabledChanged -> mapConversationConversationAppsAccessChanged(
message.senderUserId,
content,
members

Check warning on line 84 in app/src/main/kotlin/com/wire/android/mapper/SystemMessageContentMapper.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/mapper/SystemMessageContentMapper.kt#L82-L84

Added lines #L82 - L84 were not covered by tests
)
}

private fun mapConversationConversationAppsAccessChanged(
senderUserId: UserId,
content: MessageContent.ConversationAppsEnabledChanged,
members: List<User>
): UIMessageContent.SystemMessage {
val sender = members.findUser(userId = senderUserId)
val authorName = mapMemberName(
user = sender,
type = SelfNameType.ResourceTitleCase

Check warning on line 96 in app/src/main/kotlin/com/wire/android/mapper/SystemMessageContentMapper.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/mapper/SystemMessageContentMapper.kt#L93-L96

Added lines #L93 - L96 were not covered by tests
)
return UIMessageContent.SystemMessage.ConversationAppsEnabledChanged(
author = authorName,
isAuthorSelfUser = sender is SelfUser,
isAccessEnabled = content.isEnabled

Check warning on line 101 in app/src/main/kotlin/com/wire/android/mapper/SystemMessageContentMapper.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/mapper/SystemMessageContentMapper.kt#L98-L101

Added lines #L98 - L101 were not covered by tests
)
}

private fun mapConversationCreated(senderUserId: UserId, date: Instant, userList: List<User>): UIMessageContent.SystemMessage {
Expand Down Expand Up @@ -252,7 +275,7 @@
FailedToAdd.Type.LegalHold -> UIMessageContent.SystemMessage.MemberFailedToAdd.Type.LegalHold
FailedToAdd.Type.Unknown -> UIMessageContent.SystemMessage.MemberFailedToAdd.Type.Unknown
}
)
)

is MemberChange.FederationRemoved -> UIMessageContent.SystemMessage.FederationMemberRemoved(
memberNames = memberNameList
Expand Down Expand Up @@ -289,6 +312,7 @@
is OtherUser -> user.name?.let {
UIText.DynamicString(it)
} ?: UIText.StringResource(messageResourceProvider.memberNameDeleted)

is SelfUser -> when (type) {
SelfNameType.ResourceLowercase -> UIText.StringResource(messageResourceProvider.memberNameYouLowercase)
SelfNameType.ResourceTitleCase -> UIText.StringResource(messageResourceProvider.memberNameYouTitlecase)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class AudioMessageViewModelImpl @Inject constructor(

private fun initWavesMask() {
viewModelScope.launch {
audioMessagePlayer.fetchWavesMask(args.conversationId, args.messageId)
audioMessagePlayer.getOrBuildWavesMask(args.conversationId, args.messageId)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,81 +17,38 @@
*/
package com.wire.android.media.audiomessage

import android.content.Context
import dagger.Reusable
import dagger.hilt.android.qualifiers.ApplicationContext
import linc.com.amplituda.Amplituda
import linc.com.amplituda.Cache
import okio.Path
import java.io.File
import javax.inject.Inject
import kotlin.math.roundToInt

@Reusable
class AudioWavesMaskHelper @Inject constructor(
@ApplicationContext private val appContext: Context,
) {

companion object {
private const val WAVES_AMOUNT = 75
private const val WAVE_MAX = 32
}

@Suppress("TooGenericExceptionCaught")
private val amplituda: Lazy<Amplituda?> = lazy {
try {
Amplituda(appContext)
} catch (e: Throwable) {
null
}
}

@Suppress("TooGenericExceptionCaught")
private fun getAmplituda(): Amplituda? = amplituda.value

fun getWaveMask(decodedAssetPath: Path): List<Int>? = getWaveMask(File(decodedAssetPath.toString()))

fun getWaveMask(file: File): List<Int>? = getAmplituda()
?.processAudio(file, Cache.withParams(Cache.REUSE))
?.get()
?.amplitudesAsList()
?.averageWavesMask()
?.equalizeWavesMask()

private fun List<Double>.equalizeWavesMask(): List<Int> {
if (this.isEmpty()) return listOf()

val divider = max() / (WAVE_MAX - 1)

return if (divider == 0.0) {
map { 1 }
} else {
map { (it / divider).roundToInt() + 1 }
}
}

private fun List<Int>.averageWavesMask(): List<Double> {
val wavesSize = size
val sectionSize = (wavesSize.toFloat() / WAVES_AMOUNT).roundToInt()

if (wavesSize < WAVES_AMOUNT || sectionSize == 1) return map { it.toDouble() }

val averagedWaves = mutableListOf<Double>()
for (i in 0..<(wavesSize / sectionSize)) {
val startIndex = (i * sectionSize)
if (startIndex >= wavesSize) continue
val endIndex = (startIndex + sectionSize).coerceAtMost(wavesSize - 1)
averagedWaves.add(subList(startIndex, endIndex).averageInt())
}
return averagedWaves
}

private fun List<Int>.averageInt(): Double {
if (isEmpty()) return 0.0
return sum().toDouble() / size
const val WAVE_MAX = 32

fun List<Int>.equalizedWavesMask(
newMaxValue: Int = WAVE_MAX,
currentMaxValue: Int = UByte.MAX_VALUE.toInt(), // normalized loudness can be up to 255 (UByte.MAX_VALUE)
startFrom1: Boolean = true, // whether the minimum value should be 1 or 0

Check warning on line 27 in app/src/main/kotlin/com/wire/android/media/audiomessage/AudioWavesMaskHelper.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/media/audiomessage/AudioWavesMaskHelper.kt#L25-L27

Added lines #L25 - L27 were not covered by tests
): List<Int> {
if (this.isEmpty()) return listOf()
val adjustedValue = if (startFrom1) 1 else 0
val divider = currentMaxValue.toDouble() / (newMaxValue - adjustedValue)
return if (divider == 0.0) {
map { adjustedValue }

Check warning on line 33 in app/src/main/kotlin/com/wire/android/media/audiomessage/AudioWavesMaskHelper.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/media/audiomessage/AudioWavesMaskHelper.kt#L33

Added line #L33 was not covered by tests
} else {
map { (it / divider).roundToInt() + adjustedValue }
}
}

fun clear() {
getAmplituda()?.clearCache()
@Suppress("ReturnCount")
fun List<Int>.sampledWavesMask(amount: Int): List<Int> {
if (this.isEmpty() || amount <= 0) return listOf()
if (amount >= this.size) return this
val res = MutableList(size = amount) { 1 }
for (i in 0..amount - 2) {
val index = i * (size - 1) / (amount - 1)
val p = i * (size - 1) % (amount - 1)
res[i] = ((p * this[index + 1]) + (((amount - 1) - p) * this[index])) / (amount - 1)
}
res[amount - 1] = this[size - 1] // done outside of loop to avoid out of bound access (0 * this[size])
return res
}

fun ByteArray.toWavesMask(): List<Int> = this.map { it.toUByte().toInt() }.toList()
fun List<Int>.toNormalizedLoudness(): ByteArray = this.map { it.toUByte().toByte() }.toByteArray()
Loading