Skip to content

Commit 6ead75b

Browse files
committed
voice chat tip frequency
1 parent 6a31f0f commit 6ead75b

14 files changed

Lines changed: 440 additions & 47 deletions

Explorer/Assets/DCL/FeatureFlags/FeatureFlagsStrings.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ public static class FeatureFlagsStrings
8282
public const string REPORT_USER = "alfa-report-user";
8383
public const string POINT_AT = "alfa-point-at";
8484
public const string NEARBY_VOICE_CHAT = "alfa-nearby-voice-chat";
85+
public const string NEARBY_VOICE_CHAT_TIP = "alfa-nearby-voice-chat-tip";
86+
public const string NEARBY_VOICE_CHAT_TIP_CONFIG_VARIANT = "config";
8587
public const string AVATAR_CONTEXT_MENU = "alfa-avatar-context-menu";
8688
public const string DOUBLE_CLICK_WALK = "alfa-double-click-walk";
8789
public const string BYTE_WEIGHTED_LOADING_PROGRESS = "alfa-byte-weighted-loading-progress";

Explorer/Assets/DCL/FeatureFlags/FeaturesRegistry.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ public FeaturesRegistry(
8383
SetFeatureState(FeatureId.VoiceChat, IsEnabled(FeatureId.Friends) && IsEnabled(FeatureId.FriendsUserBlocking) && (isEditor || featureFlags.IsEnabled(FeatureFlagsStrings.VOICE_CHAT) || (appArgs.HasDebugFlag() && appArgs.HasFlag(AppArgsFlags.VOICE_CHAT))));
8484
SetFeatureState(FeatureId.CommunityVoiceChat, IsEnabled(FeatureId.VoiceChat));
8585
SetFeatureState(FeatureId.NearbyVoiceChat, IsEnabled(FeatureId.VoiceChat) && appArgs.ResolveFeatureFlagArg(AppArgsFlags.NEARBY_VOICE_CHAT, featureFlags.IsEnabled(FeatureFlagsStrings.NEARBY_VOICE_CHAT) || Application.isEditor));
86+
87+
// The intro tip is a kill switch: unlike the feature itself it stays off until the flag is explicitly enabled.
88+
SetFeatureState(FeatureId.NearbyVoiceChatTip, IsEnabled(FeatureId.NearbyVoiceChat) && featureFlags.IsEnabled(FeatureFlagsStrings.NEARBY_VOICE_CHAT_TIP));
8689
}
8790

8891
/// <summary>
@@ -218,5 +221,6 @@ public enum FeatureId
218221
UseCustomMediaPlayerWindows = 71,
219222
UseCustomMediaPlayerMacSilicon = 72,
220223
UseCustomMediaPlayerMacIntel = 73,
224+
NearbyVoiceChatTip = 74,
221225
}
222226
}

Explorer/Assets/DCL/PluginSystem/Global/VoiceChatPlugin.cs

Lines changed: 5 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
using DCL.VoiceChat.Nearby.Audio;
2020
using DCL.VoiceChat.Nearby.Systems;
2121
using LiveKit.Rooms.Streaming;
22-
using LiveKit.Rooms.Streaming.Audio;
2322
using LiveKit.Rooms;
2423
using System;
2524
using System.Collections.Generic;
@@ -35,7 +34,6 @@
3534
using DCL.SceneBannedUsers;
3635
using DCL.VoiceChat.UI;
3736
using Utility;
38-
using Utility.Multithreading;
3937
using AudioSettings = UnityEngine.AudioSettings;
4038
using RustAudio;
4139

@@ -85,7 +83,6 @@ public class VoiceChatPlugin : IDCLGlobalPlugin<VoiceChatPlugin.Settings>
8583
private NearbyMicrophoneAudioToggleHandler? nearbyMicrophoneAudioToggleHandler;
8684
private NearbyVoiceChatButtonController? nearbyButtonController;
8785
private NearbyVoiceWidgetController? nearbyWidgetController;
88-
private CancellationTokenSource? nearbyTipCts;
8986
private VoiceChatConfiguration voiceChatConfiguration;
9087

9188
public VoiceChatPlugin(
@@ -139,7 +136,6 @@ public VoiceChatPlugin(
139136

140137
public void Dispose()
141138
{
142-
nearbyTipCts.SafeCancelAndDispose();
143139
pluginScope.Dispose();
144140

145141
if (voiceChatPluginSettingsAsset.Value != null)
@@ -260,16 +256,12 @@ public async UniTask InitializeAsync(Settings settings, CancellationToken ct)
260256
pluginScope.Add(nearbyWidgetController);
261257

262258
// Intro FLUX
263-
nearbyTipCts = new CancellationTokenSource();
264-
RunNearbyVoiceTipAsync(nearbyVoiceTipView, loadingStatus, nearbyVoiceChatButtonView, nearbyTipCts.Token).Forget();
265-
}
266-
}
259+
NearbyVoiceTipSchedule tipSchedule = FeaturesRegistry.Instance.IsEnabled(FeatureId.NearbyVoiceChatTip)
260+
? NearbyVoiceTipSchedule.FromFeatureFlags(FeatureFlagsConfiguration.Instance)
261+
: NearbyVoiceTipSchedule.Disabled;
267262

268-
private static async UniTaskVoid RunNearbyVoiceTipAsync(NearbyVoiceTipView view, ILoadingStatus loadingStatus,
269-
NearbyVoiceChatButtonView buttonView, CancellationToken ct)
270-
{
271-
if (await NearbyVoiceTipFlow.WaitAndShowAsync(view, loadingStatus, ct))
272-
buttonView.Button.onClick.Invoke();
263+
pluginScope.Add(new NearbyVoiceTipController(nearbyVoiceTipView, nearbyVoiceChatButtonView, stateModel, chatSharedAreaEventBus, loadingStatus, tipSchedule));
264+
}
273265
}
274266

275267
[Serializable]
@@ -283,38 +275,5 @@ public class VoiceChatConfigurationsReference : AssetReferenceT<VoiceChatPluginS
283275
public VoiceChatConfigurationsReference(string guid) : base(guid) { }
284276
}
285277
}
286-
287-
private static class NearbyVoiceTipFlow
288-
{
289-
public static async UniTask<bool> WaitAndShowAsync(NearbyVoiceTipView view, ILoadingStatus loadingStatus, CancellationToken ct)
290-
{
291-
view.Hide();
292-
293-
if (DCLPlayerPrefs.GetBool(DCLPrefKeys.NEARBY_VOICE_TIP_DISMISSED))
294-
return false;
295-
296-
try
297-
{
298-
await UniTask.WaitUntil(
299-
() => loadingStatus.CurrentStage.Value == LoadingStatus.LoadingStage.Completed,
300-
cancellationToken: ct);
301-
302-
view.Show();
303-
304-
int winner = await UniTask.WhenAny(
305-
view.CloseButton.OnClickAsync(ct),
306-
view.TryItNowButton.OnClickAsync(ct));
307-
308-
DCLPlayerPrefs.SetBool(DCLPrefKeys.NEARBY_VOICE_TIP_DISMISSED, true, save: true);
309-
view.Hide();
310-
311-
return winner == 1;
312-
}
313-
catch (OperationCanceledException)
314-
{
315-
return false;
316-
}
317-
}
318-
}
319278
}
320279
}

Explorer/Assets/DCL/Prefs/DCLPlayerPrefs.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,10 @@ private static bool ValidateClearDCLPlayerPrefs() =>
184184
[MenuItem("Decentraland/PlayerPrefs/Reset Nearby Voice Intro Tip")]
185185
private static void ResetNearbyVoiceIntroTip()
186186
{
187-
DeleteKey(DCLPrefKeys.NEARBY_VOICE_TIP_DISMISSED, save: true);
187+
DeleteKey(DCLPrefKeys.NEARBY_VOICE_TIP_DISMISSED);
188+
DeleteKey(DCLPrefKeys.NEARBY_VOICE_TIP_SHOWN_COUNT);
189+
DeleteKey(DCLPrefKeys.NEARBY_VOICE_TIP_LAST_SHOWN_LAUNCH);
190+
DeleteKey(DCLPrefKeys.NEARBY_VOICE_USED, save: true);
188191
Debug.Log("Nearby Voice Intro Tip has been reset.");
189192
}
190193
#endif

Explorer/Assets/DCL/Prefs/DCLPrefKeys.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ public static class DCLPrefKeys
107107
public const string SKYBOX_FIXED_TIME = "Skybox_FixedTime";
108108

109109
public const string NEARBY_VOICE_TIP_DISMISSED = "NearbyVoice_TipDismissed";
110+
public const string NEARBY_VOICE_TIP_SHOWN_COUNT = "NearbyVoice_TipShownCount";
111+
public const string NEARBY_VOICE_TIP_LAST_SHOWN_LAUNCH = "NearbyVoice_TipLastShownLaunch";
112+
public const string NEARBY_VOICE_USED = "NearbyVoice_Used";
110113
public const string NEARBY_VOICE_CHAT_DISABLED = "NearbyVoice_Disabled";
111114
public const string SETTINGS_MUTE_MIC_IN_BACKGROUND = "Settings_MuteMicInBackground";
112115

Explorer/Assets/DCL/Tests/PlayMode/PerformanceTests/EventsStateServiceLookupPerformanceTest.cs.meta

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Explorer/Assets/DCL/Tests/PlayMode/PerformanceTests/PlacesStateServiceLookupPerformanceTest.cs.meta

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
using DCL.VoiceChat.UI;
2+
using NUnit.Framework;
3+
4+
namespace DCL.VoiceChat.NearbyVoiceChat.Tests.EditMode
5+
{
6+
/// <summary>
7+
/// Documents when the Nearby Voice Chat intro tip is due.
8+
///
9+
/// With the shipped defaults (every 5 sessions, at most 2 displays) a fresh user sees the tip on launch 5 and
10+
/// again on launch 10. The gap is measured from the last display, not from launch 0, so a returning user who
11+
/// is already past every threshold gets one display now and the next one 5 launches later — never two in a row.
12+
/// </summary>
13+
public class NearbyVoiceTipScheduleShould
14+
{
15+
private const int SHOW_EVERY_SESSIONS = 5;
16+
private const int MAX_TIMES_SHOWN = 2;
17+
private const int NEVER_SHOWN = 0;
18+
19+
private NearbyVoiceTipSchedule schedule;
20+
21+
[SetUp]
22+
public void Setup()
23+
{
24+
schedule = new NearbyVoiceTipSchedule(SHOW_EVERY_SESSIONS, MAX_TIMES_SHOWN);
25+
}
26+
27+
[TestCase(1)]
28+
[TestCase(2)]
29+
[TestCase(3)]
30+
[TestCase(4)]
31+
public void NotShowBeforeTheFirstThreshold(int launchCount)
32+
{
33+
//Act
34+
bool shouldShow = schedule.ShouldShow(launchCount, timesShown: 0, lastShownAtLaunch: NEVER_SHOWN, hasUsedNearbyVoice: false);
35+
36+
//Assert
37+
Assert.IsFalse(shouldShow);
38+
}
39+
40+
[Test]
41+
public void ShowAtTheFirstThreshold()
42+
{
43+
//Act
44+
bool shouldShow = schedule.ShouldShow(launchCount: 5, timesShown: 0, lastShownAtLaunch: NEVER_SHOWN, hasUsedNearbyVoice: false);
45+
46+
//Assert
47+
Assert.IsTrue(shouldShow);
48+
}
49+
50+
[TestCase(6)]
51+
[TestCase(7)]
52+
[TestCase(8)]
53+
[TestCase(9)]
54+
public void NotShowBetweenThresholds(int launchCount)
55+
{
56+
//Act
57+
bool shouldShow = schedule.ShouldShow(launchCount, timesShown: 1, lastShownAtLaunch: 5, hasUsedNearbyVoice: false);
58+
59+
//Assert
60+
Assert.IsFalse(shouldShow);
61+
}
62+
63+
[Test]
64+
public void ShowAtTheSecondThreshold()
65+
{
66+
//Act
67+
bool shouldShow = schedule.ShouldShow(launchCount: 10, timesShown: 1, lastShownAtLaunch: 5, hasUsedNearbyVoice: false);
68+
69+
//Assert
70+
Assert.IsTrue(shouldShow);
71+
}
72+
73+
[TestCase(10)]
74+
[TestCase(11)]
75+
[TestCase(100)]
76+
public void NotShowOnceTheDisplayCapIsReached(int launchCount)
77+
{
78+
//Act
79+
bool shouldShow = schedule.ShouldShow(launchCount, timesShown: MAX_TIMES_SHOWN, lastShownAtLaunch: 10, hasUsedNearbyVoice: false);
80+
81+
//Assert
82+
Assert.IsFalse(shouldShow);
83+
}
84+
85+
[TestCase(0)]
86+
[TestCase(1)]
87+
public void NotShowWhenTheUserAlreadyUsedNearbyVoice(int timesShown)
88+
{
89+
//Act
90+
bool shouldShow = schedule.ShouldShow(launchCount: 100, timesShown, lastShownAtLaunch: NEVER_SHOWN, hasUsedNearbyVoice: true);
91+
92+
//Assert
93+
Assert.IsFalse(shouldShow);
94+
}
95+
96+
[Test]
97+
public void ShowImmediatelyToReturningUsersWhoNeverSawIt()
98+
{
99+
//Act
100+
bool shouldShow = schedule.ShouldShow(launchCount: 42, timesShown: 0, lastShownAtLaunch: NEVER_SHOWN, hasUsedNearbyVoice: false);
101+
102+
//Assert
103+
Assert.IsTrue(shouldShow);
104+
}
105+
106+
[TestCase(43)]
107+
[TestCase(44)]
108+
[TestCase(46)]
109+
public void NotShowAgainRightAfterAReturningUsersFirstDisplay(int launchCount)
110+
{
111+
//Arrange
112+
const int SHOWN_AT = 42;
113+
114+
//Act
115+
bool shouldShow = schedule.ShouldShow(launchCount, timesShown: 1, lastShownAtLaunch: SHOWN_AT, hasUsedNearbyVoice: false);
116+
117+
//Assert
118+
Assert.IsFalse(shouldShow);
119+
}
120+
121+
[Test]
122+
public void ShowTheSecondTimeAFullPeriodAfterAReturningUsersFirstDisplay()
123+
{
124+
//Act
125+
bool shouldShow = schedule.ShouldShow(launchCount: 47, timesShown: 1, lastShownAtLaunch: 42, hasUsedNearbyVoice: false);
126+
127+
//Assert
128+
Assert.IsTrue(shouldShow);
129+
}
130+
131+
[TestCase(3, 0, 0, ExpectedResult = true)]
132+
[TestCase(5, 1, 3, ExpectedResult = false)]
133+
[TestCase(6, 1, 3, ExpectedResult = true)]
134+
[TestCase(12, 3, 9, ExpectedResult = true)]
135+
[TestCase(12, 4, 9, ExpectedResult = false)]
136+
public bool FollowTheConfiguredFrequency(int launchCount, int timesShown, int lastShownAtLaunch)
137+
{
138+
//Arrange
139+
var customSchedule = new NearbyVoiceTipSchedule(showEverySessions: 3, maxTimesShown: 4);
140+
141+
//Act
142+
return customSchedule.ShouldShow(launchCount, timesShown, lastShownAtLaunch, hasUsedNearbyVoice: false);
143+
}
144+
145+
[Test]
146+
public void NeverShowWhenDisabled()
147+
{
148+
//Act
149+
bool shouldShow = NearbyVoiceTipSchedule.Disabled.ShouldShow(launchCount: 100, timesShown: 0, lastShownAtLaunch: NEVER_SHOWN, hasUsedNearbyVoice: false);
150+
151+
//Assert
152+
Assert.IsFalse(shouldShow);
153+
}
154+
155+
[Test]
156+
public void ClampANonPositivePeriodSoTheTipIsNotDueEveryLaunch()
157+
{
158+
//Arrange
159+
var degenerateSchedule = new NearbyVoiceTipSchedule(showEverySessions: 0, maxTimesShown: 2);
160+
161+
//Act
162+
bool shouldShow = degenerateSchedule.ShouldShow(launchCount: 5, timesShown: 1, lastShownAtLaunch: 5, hasUsedNearbyVoice: false);
163+
164+
//Assert
165+
Assert.AreEqual(1, degenerateSchedule.ShowEverySessions);
166+
Assert.IsFalse(shouldShow);
167+
}
168+
}
169+
}

Explorer/Assets/DCL/VoiceChat/NearbyVoiceChat/Tests/EditMode/NearbyVoiceTipScheduleShould.cs.meta

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)