Skip to content

Commit a549e1b

Browse files
authored
VIDSOL-651: Release 1.1.0 (#110)
2 parents 9650889 + a4b5834 commit a549e1b

142 files changed

Lines changed: 7634 additions & 1718 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/android.yml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,12 @@ jobs:
5959
run: |
6060
./gradlew clean koverXmlReportDebug detekt --info
6161
62-
- name: Send report to sonar
63-
env:
64-
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
65-
run: |
66-
./gradlew sonar
62+
# Disabled temporally because some enterprise issues
63+
# - name: Send report to sonar
64+
# env:
65+
# SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
66+
# run: |
67+
# ./gradlew sonar
6768

6869
- name: Upload test reports
6970
uses: actions/upload-artifact@v4

.github/workflows/publish-play-store.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ jobs:
6363
6464
- name: Set version from GitHub run number
6565
run: |
66-
VERSION_CODE=$((1000000 + $GITHUB_RUN_NUMBER))
67-
VERSION_NAME="1.0.0-build${GITHUB_RUN_NUMBER}"
66+
VERSION_CODE=$((1000100 + $GITHUB_RUN_NUMBER))
67+
VERSION_NAME="1.1.0-build${GITHUB_RUN_NUMBER}"
6868
sed -i "s/versionCode = [0-9]\+/versionCode = $VERSION_CODE/" app/build.gradle.kts
6969
sed -i "s/versionName = \".*\"/versionName = \"$VERSION_NAME\"/" app/build.gradle.kts
7070
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Notify Slack on Release
2+
3+
on:
4+
release:
5+
types: [published]
6+
7+
permissions:
8+
contents: read
9+
10+
jobs:
11+
notify-release:
12+
runs-on: ubuntu-latest
13+
name: Notify Release
14+
steps:
15+
- name: Send to slack channels
16+
uses: slackapi/slack-github-action@v2.0.0
17+
with:
18+
webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
19+
webhook-type: incoming-webhook
20+
errors: true
21+
payload: |
22+
blocks:
23+
- type: "header"
24+
text:
25+
type: "plain_text"
26+
text: ":tada: Version ${{ github.event.release.name }} of the Vonage Native Android Video Reference App Just Released!"
27+
- type: "section"
28+
text:
29+
type: "mrkdwn"
30+
text: "${{ github.event.release.body }}"
31+
- type: "divider"
32+
- type: "section"
33+
text:
34+
type: "mrkdwn"
35+
text: "You can view the full change log <${{ github.event.release.html_url }}|here>"

.vonage/catalog-info.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,7 @@ spec:
2323
type: library
2424
owner: group:orgdata/vdk
2525
system: video-android-ref-app
26-
lifecycle: production
26+
lifecycle: production
27+
dependsOn:
28+
- component:default/video-react-ref-app
29+
- component:default/video-native-sdks

app/build.gradle.kts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ plugins {
99
alias(libs.plugins.dagger.hilt)
1010
alias(libs.plugins.sonarqube)
1111
alias(libs.plugins.kover)
12-
kotlin("plugin.serialization") version "2.0.21"
12+
alias(libs.plugins.kotlin.serialization)
1313
alias(libs.plugins.play.publisher)
1414
alias(libs.plugins.stability.analyzer)
1515
id("com.vonage.json-config")
@@ -37,12 +37,15 @@ android {
3737
targetSdk = 36
3838
// NOTE: The following versionCode and versionName are placeholders.
3939
// Actual values are set dynamically by the GitHub Actions workflow during CI/CD.
40-
versionCode = 100
41-
versionName = "1.0.0"
40+
versionCode = 110
41+
versionName = "1.1.0"
4242

4343
testInstrumentationRunner = "com.vonage.android.HiltTestRunner"
4444
testInstrumentationRunnerArguments["clearPackageData"] = "true"
4545

46+
// OpenTok SDK version
47+
buildConfigField("String", "OPENTOK_SDK_VERSION", "\"${libs.versions.opentokAndroidSdk.get()}\"")
48+
4649
// Set up base API URL
4750
val baseApiUrl = configProps.getProperty("vonage.baseApiUrl", "")
4851
buildConfigField("String", "BASE_API_URL", "\"$baseApiUrl\"")
@@ -77,6 +80,11 @@ android {
7780
val videoFxProperty = configProps.getProperty("vonage.video.allow_background_effects", "true")
7881
buildConfigField("boolean", "FEATURE_VIDEO_EFFECTS_ENABLED", "$videoFxProperty")
7982
missingDimensionStrategy("videofx", videoFxProperty.toEnabledString())
83+
84+
// Settings feature
85+
val settingsProperty = configProps.getProperty("vonage.meetingRoom.allow_settings", "true")
86+
buildConfigField("boolean", "FEATURE_SETTINGS_ENABLED", "$settingsProperty")
87+
missingDimensionStrategy("settings", settingsProperty.toEnabledString())
8088
}
8189

8290
compileOptions {
@@ -188,6 +196,7 @@ dependencies {
188196
implementation(project(":vonage-feature-reactions"))
189197
implementation(project(":vonage-feature-video-effects"))
190198
implementation(project(":vonage-feature-captions"))
199+
implementation(project(":vonage-feature-settings"))
191200
implementation(project(":vonage-audio-selector"))
192201
implementation(project(":vonage-android-logger"))
193202
implementation(libs.androidx.core.ktx)
@@ -202,11 +211,9 @@ dependencies {
202211
implementation(libs.androidx.navigation.runtime.android)
203212
implementation(libs.androidx.navigation.compose)
204213
implementation(libs.androidx.hilt.navigation.compose)
205-
implementation(libs.androidx.hilt.navigation.fragment)
206214
implementation(libs.retrofit)
207215
implementation(libs.okhttp)
208-
implementation(libs.converter.moshi)
209-
implementation(libs.moshi.kotlin)
216+
implementation(libs.converter.kotlinx.serialization)
210217
implementation(libs.logging.interceptor)
211218
implementation(libs.androidx.adaptive)
212219
implementation(libs.androidx.adaptive.layout)

app/proguard-rules.pro

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,28 +22,29 @@
2222
-keep class com.vonage.webrtc.** { *; }
2323

2424
# ================================================================================================
25-
# Moshi (JSON Serialization)
25+
# kotlinx.serialization
2626
# ================================================================================================
2727

28-
# Keep data classes used with Moshi
29-
-keep class com.vonage.android.data.network.** { *; }
28+
-keepattributes RuntimeVisibleAnnotations,AnnotationDefault
3029

31-
# Keep classes annotated with @JsonClass
32-
-keep @com.squareup.moshi.JsonClass class * { *; }
33-
-keepclassmembers @com.squareup.moshi.JsonClass class * {
34-
<init>(...);
35-
<fields>;
30+
# Keep @Serializable classes and their generated serializers
31+
-if @kotlinx.serialization.Serializable class **
32+
-keepclassmembers class <1> {
33+
static <1>$Companion Companion;
3634
}
37-
38-
# Keep generated JsonAdapter classes
39-
-if @com.squareup.moshi.JsonClass class *
40-
-keep class <1>JsonAdapter {
41-
<init>(...);
42-
<fields>;
35+
-if @kotlinx.serialization.Serializable class ** {
36+
static **$* *;
37+
}
38+
-keepclassmembers class <2>$<3> {
39+
kotlinx.serialization.KSerializer serializer(...);
40+
}
41+
-if @kotlinx.serialization.Serializable class ** {
42+
public static ** INSTANCE;
43+
}
44+
-keepclassmembers class <1> {
45+
public static ** INSTANCE;
46+
kotlinx.serialization.KSerializer serializer(...);
4347
}
44-
45-
# Keep Moshi core classes
46-
-keep class com.squareup.moshi.** { *; }
4748

4849
# ================================================================================================
4950
# Retrofit (HTTP Client)
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package com.vonage.android.audio
2+
3+
import android.content.Context
4+
import android.media.MediaPlayer
5+
import androidx.compose.runtime.Stable
6+
import com.vonage.android.R
7+
import dagger.hilt.android.qualifiers.ApplicationContext
8+
import kotlinx.coroutines.flow.MutableStateFlow
9+
import kotlinx.coroutines.flow.StateFlow
10+
import kotlinx.coroutines.flow.asStateFlow
11+
import javax.inject.Inject
12+
13+
class AudioPlayer @Inject constructor(
14+
@ApplicationContext context: Context
15+
) {
16+
17+
private val _audioPlayerState = MutableStateFlow<AudioPlayerState>(AudioPlayerState.Idle)
18+
val audioPlayerState: StateFlow<AudioPlayerState> = _audioPlayerState.asStateFlow()
19+
20+
private var mediaPlayer: MediaPlayer? = MediaPlayer.create(context, R.raw.sample)
21+
22+
init {
23+
mediaPlayer?.setOnCompletionListener { _ ->
24+
stop()
25+
}
26+
}
27+
28+
fun play() {
29+
mediaPlayer?.let { player ->
30+
player.start()
31+
_audioPlayerState.value = AudioPlayerState.Playing(
32+
durationMs = player.duration
33+
)
34+
}
35+
}
36+
37+
fun stop() {
38+
mediaPlayer?.let { player ->
39+
if (player.isPlaying) {
40+
player.pause()
41+
}
42+
player.seekTo(0)
43+
}
44+
_audioPlayerState.value = AudioPlayerState.Idle
45+
}
46+
47+
fun toggle() {
48+
mediaPlayer?.let { player ->
49+
when (player.isPlaying) {
50+
true -> stop()
51+
false -> play()
52+
}
53+
}
54+
}
55+
56+
fun release() {
57+
mediaPlayer?.release()
58+
mediaPlayer = null
59+
_audioPlayerState.value = AudioPlayerState.Idle
60+
}
61+
}
62+
63+
@Stable
64+
sealed interface AudioPlayerState {
65+
data object Idle : AudioPlayerState
66+
data class Playing(
67+
val durationMs: Int,
68+
) : AudioPlayerState
69+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.vonage.android.audio
2+
3+
import androidx.lifecycle.ViewModel
4+
import dagger.hilt.android.lifecycle.HiltViewModel
5+
import javax.inject.Inject
6+
7+
@HiltViewModel
8+
class AudioPlayerViewModel @Inject constructor(
9+
private val audioPlayer: AudioPlayer,
10+
) : ViewModel() {
11+
12+
val state = audioPlayer.audioPlayerState
13+
14+
fun onSpeakerTestToggle() {
15+
audioPlayer.toggle()
16+
}
17+
18+
fun stop() {
19+
audioPlayer.stop()
20+
}
21+
22+
override fun onCleared() {
23+
audioPlayer.release()
24+
super.onCleared()
25+
}
26+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.vonage.android.core
2+
3+
import kotlinx.coroutines.channels.Channel
4+
import kotlinx.coroutines.flow.MutableStateFlow
5+
import kotlinx.coroutines.flow.update
6+
7+
/**
8+
* A discrete, self-contained unit of business logic that can be [dispatched][BaseViewModel.dispatch]
9+
* by a [BaseViewModel].
10+
*
11+
* Each action encapsulates a single use-case (e.g. "toggle a setting", "observe a flow").
12+
* It receives the screen's [ActionDependencies] and an [ActionScope] to read/update state
13+
* or emit one-time events, keeping the ViewModel thin and the logic easily unit-testable.
14+
*
15+
* @param D The concrete [ActionDependencies] subtype this action requires.
16+
* @param S The [ViewState] type managed by the hosting ViewModel.
17+
* @param E The [ViewEvent] type emitted by the hosting ViewModel.
18+
*/
19+
interface ViewAction<D : ActionDependencies, S : ViewState, E : ViewEvent> {
20+
/**
21+
* Executes this action.
22+
*
23+
* @param dependencies The screen-specific dependencies (repositories, holders, etc.).
24+
* @param actionScope A scope that provides state mutation and event emission capabilities.
25+
*/
26+
suspend fun execute(dependencies: D, actionScope: ActionScope<S, E>)
27+
}
28+
29+
/**
30+
* Scoped environment passed to every [ViewAction.execute] call.
31+
*
32+
* Provides controlled access to the ViewModel's internal state and event channel
33+
* so that actions can:
34+
* - Read the latest state via [currentState].
35+
* - Atomically update state via [setState].
36+
* - Send one-time side-effect events via [sendEvent].
37+
*
38+
* @param S The [ViewState] type.
39+
* @param E The [ViewEvent] type.
40+
*/
41+
class ActionScope<S : ViewState, E : ViewEvent>(
42+
private val stateFlow: MutableStateFlow<S>,
43+
private val eventChannel: Channel<E>
44+
) {
45+
/** Returns the current snapshot of the UI state. */
46+
val currentState: S get() = stateFlow.value
47+
48+
/**
49+
* Atomically updates the state by applying [reducer] to the current value.
50+
*
51+
* @param reducer A function invoked with the current state as receiver,
52+
* returning the new state (typically via `copy()`).
53+
*/
54+
fun setState(reducer: S.() -> S) = stateFlow.update(reducer)
55+
56+
/**
57+
* Sends a one-time [event] to the UI layer (e.g. navigation, snackbar).
58+
* Uses [Channel.trySend] so it never suspends.
59+
*/
60+
fun sendEvent(event: E) = eventChannel.trySend(event)
61+
}

0 commit comments

Comments
 (0)