docs(flutter): add dart doc comments to core services and widgets (TODO-085)
Add dart doc comments to poke_notification_service, poke_preferences_service, link_trust_service, voice_settings, snapshot_view covering public API.
This commit is contained in:
@@ -3,11 +3,19 @@ import 'package:flutter/material.dart';
|
|||||||
import '../l10n/generated/app_localizations.dart';
|
import '../l10n/generated/app_localizations.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
/// Manages user-trusted link domains to suppress external-link warnings.
|
||||||
|
///
|
||||||
|
/// Trusted domains are persisted in [SharedPreferences] under
|
||||||
|
/// `'trusted_domains'`. Supports wildcard patterns (e.g. `'*.example.com'`)
|
||||||
|
/// that match any subdomain of the base host.
|
||||||
|
///
|
||||||
|
/// This is a singleton; use [LinkTrustService.instance] to obtain it.
|
||||||
class LinkTrustService extends ChangeNotifier {
|
class LinkTrustService extends ChangeNotifier {
|
||||||
static LinkTrustService? _instance;
|
static LinkTrustService? _instance;
|
||||||
final Set<String> _trusted = {};
|
final Set<String> _trusted = {};
|
||||||
bool _loaded = false;
|
bool _loaded = false;
|
||||||
|
|
||||||
|
/// Returns the singleton [LinkTrustService] instance.
|
||||||
static LinkTrustService get instance {
|
static LinkTrustService get instance {
|
||||||
_instance ??= LinkTrustService._();
|
_instance ??= LinkTrustService._();
|
||||||
return _instance!;
|
return _instance!;
|
||||||
@@ -26,6 +34,10 @@ class LinkTrustService extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if [host] matches any trusted domain pattern.
|
||||||
|
///
|
||||||
|
/// Matching is case-insensitive. Wildcard patterns like `'*.example.com'`
|
||||||
|
/// match both `example.com` and any `*.example.com` subdomain.
|
||||||
bool isTrusted(String host) {
|
bool isTrusted(String host) {
|
||||||
host = host.toLowerCase();
|
host = host.toLowerCase();
|
||||||
for (final pattern in _trusted) {
|
for (final pattern in _trusted) {
|
||||||
@@ -34,6 +46,7 @@ class LinkTrustService extends ChangeNotifier {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Persists [host] as a trusted domain and notifies listeners.
|
||||||
Future<void> addTrustedDomain(String host) async {
|
Future<void> addTrustedDomain(String host) async {
|
||||||
host = host.toLowerCase();
|
host = host.toLowerCase();
|
||||||
_trusted.add(host);
|
_trusted.add(host);
|
||||||
@@ -51,6 +64,10 @@ class LinkTrustService extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shows a dialog asking the user whether to open an external link.
|
||||||
|
///
|
||||||
|
/// Returns `true` if the user chose to open and checked "remember this domain",
|
||||||
|
/// `false` if the user chose to open without remembering, or `null` if cancelled.
|
||||||
Future<bool?> showLinkTrustDialog(BuildContext context, String domain) async {
|
Future<bool?> showLinkTrustDialog(BuildContext context, String domain) async {
|
||||||
bool remember = false;
|
bool remember = false;
|
||||||
return showDialog<bool>(
|
return showDialog<bool>(
|
||||||
|
|||||||
@@ -3,7 +3,20 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
|||||||
|
|
||||||
import '../src/rust/api.dart' as rust;
|
import '../src/rust/api.dart' as rust;
|
||||||
|
|
||||||
|
/// Manages local push notifications for TeamSpeak poke events.
|
||||||
|
///
|
||||||
|
/// Handles platform-specific notification configuration across Android,
|
||||||
|
/// iOS, macOS, Linux, and Windows. Notification sound is intentionally
|
||||||
|
/// delegated to [EventSoundService] (tracked: TODO-event-sounds); this
|
||||||
|
/// service only manages the visual notification surface.
|
||||||
|
///
|
||||||
|
/// Poke strength maps to platform-appropriate urgency levels:
|
||||||
|
/// - [BridgePokeStrength.strong] → high-priority / time-sensitive
|
||||||
|
/// - [BridgePokeStrength.suppressed] → normal priority
|
||||||
|
/// - [BridgePokeStrength.suppressedOverflow] → passive / low priority
|
||||||
class PokeNotificationService {
|
class PokeNotificationService {
|
||||||
|
/// Creates a [PokeNotificationService] with an optional
|
||||||
|
/// [FlutterLocalNotificationsPlugin] for testing.
|
||||||
PokeNotificationService({FlutterLocalNotificationsPlugin? notifications})
|
PokeNotificationService({FlutterLocalNotificationsPlugin? notifications})
|
||||||
: _notifications = notifications ?? FlutterLocalNotificationsPlugin();
|
: _notifications = notifications ?? FlutterLocalNotificationsPlugin();
|
||||||
|
|
||||||
@@ -22,6 +35,9 @@ class PokeNotificationService {
|
|||||||
final FlutterLocalNotificationsPlugin _notifications;
|
final FlutterLocalNotificationsPlugin _notifications;
|
||||||
bool _initialized = false;
|
bool _initialized = false;
|
||||||
|
|
||||||
|
/// Initializes the notification plugin with platform-specific settings.
|
||||||
|
///
|
||||||
|
/// Safe to call multiple times; subsequent calls are no-ops.
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
if (_initialized) return;
|
if (_initialized) return;
|
||||||
await _notifications.initialize(
|
await _notifications.initialize(
|
||||||
@@ -56,6 +72,10 @@ class PokeNotificationService {
|
|||||||
_initialized = true;
|
_initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Requests notification permission from the user on the current platform.
|
||||||
|
///
|
||||||
|
/// Returns `true` on platforms where permission is not required (web,
|
||||||
|
/// Linux, Windows) or when the user grants permission.
|
||||||
Future<bool> requestPermission() async {
|
Future<bool> requestPermission() async {
|
||||||
await init();
|
await init();
|
||||||
if (kIsWeb) return true;
|
if (kIsWeb) return true;
|
||||||
@@ -87,6 +107,8 @@ class PokeNotificationService {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Displays a poke notification from [senderName] with [strength]-based
|
||||||
|
/// urgency. Silently returns if the user has denied notification permission.
|
||||||
Future<void> show({
|
Future<void> show({
|
||||||
required String senderName,
|
required String senderName,
|
||||||
required String message,
|
required String message,
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
/// Persists user preferences for TeamSpeak poke notifications.
|
||||||
|
///
|
||||||
|
/// Stores two settings:
|
||||||
|
/// - Whether pokes are globally enabled
|
||||||
|
/// - A set of muted sender client IDs
|
||||||
|
///
|
||||||
|
/// Preferences are written to [SharedPreferences] and observable
|
||||||
|
/// through [ValueListenable] so UI widgets can rebuild reactively.
|
||||||
class PokePreferencesService {
|
class PokePreferencesService {
|
||||||
static const _enabledKey = 'pokes.enabled';
|
static const _enabledKey = 'pokes.enabled';
|
||||||
static const _mutedSendersKey = 'pokes.muted_senders';
|
static const _mutedSendersKey = 'pokes.muted_senders';
|
||||||
@@ -10,9 +18,13 @@ class PokePreferencesService {
|
|||||||
const <BigInt>{},
|
const <BigInt>{},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// Whether poke notifications are globally enabled.
|
||||||
ValueListenable<bool> get pokesEnabled => _pokesEnabled;
|
ValueListenable<bool> get pokesEnabled => _pokesEnabled;
|
||||||
|
|
||||||
|
/// Set of client IDs whose pokes are muted.
|
||||||
ValueListenable<Set<BigInt>> get mutedSenders => _mutedSenders;
|
ValueListenable<Set<BigInt>> get mutedSenders => _mutedSenders;
|
||||||
|
|
||||||
|
/// Loads persisted preferences from [SharedPreferences].
|
||||||
Future<void> load() async {
|
Future<void> load() async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
_pokesEnabled.value = prefs.getBool(_enabledKey) ?? true;
|
_pokesEnabled.value = prefs.getBool(_enabledKey) ?? true;
|
||||||
@@ -21,18 +33,21 @@ class PokePreferencesService {
|
|||||||
.toSet();
|
.toSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Enables or disables poke notifications globally.
|
||||||
Future<void> setPokesEnabled(bool enabled) async {
|
Future<void> setPokesEnabled(bool enabled) async {
|
||||||
_pokesEnabled.value = enabled;
|
_pokesEnabled.value = enabled;
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
await prefs.setBool(_enabledKey, enabled);
|
await prefs.setBool(_enabledKey, enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds [senderId] to the muted senders set.
|
||||||
Future<void> muteSender(BigInt senderId) async {
|
Future<void> muteSender(BigInt senderId) async {
|
||||||
if (_mutedSenders.value.contains(senderId)) return;
|
if (_mutedSenders.value.contains(senderId)) return;
|
||||||
_mutedSenders.value = {..._mutedSenders.value, senderId};
|
_mutedSenders.value = {..._mutedSenders.value, senderId};
|
||||||
await _saveMutedSenders();
|
await _saveMutedSenders();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removes [senderId] from the muted senders set.
|
||||||
Future<void> unmuteSender(BigInt senderId) async {
|
Future<void> unmuteSender(BigInt senderId) async {
|
||||||
if (!_mutedSenders.value.contains(senderId)) return;
|
if (!_mutedSenders.value.contains(senderId)) return;
|
||||||
_mutedSenders.value = _mutedSenders.value
|
_mutedSenders.value = _mutedSenders.value
|
||||||
@@ -41,6 +56,7 @@ class PokePreferencesService {
|
|||||||
await _saveMutedSenders();
|
await _saveMutedSenders();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if [senderId] is in the muted senders set.
|
||||||
bool isMuted(BigInt senderId) => _mutedSenders.value.contains(senderId);
|
bool isMuted(BigInt senderId) => _mutedSenders.value.contains(senderId);
|
||||||
|
|
||||||
Future<void> _saveMutedSenders() async {
|
Future<void> _saveMutedSenders() async {
|
||||||
@@ -51,6 +67,7 @@ class PokePreferencesService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Releases [ValueNotifier] resources.
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_pokesEnabled.dispose();
|
_pokesEnabled.dispose();
|
||||||
_mutedSenders.dispose();
|
_mutedSenders.dispose();
|
||||||
|
|||||||
@@ -11,7 +11,14 @@ import '../src/rust/api.dart' as rust;
|
|||||||
import 'bbcode_text.dart';
|
import 'bbcode_text.dart';
|
||||||
import 'talk_power_warning.dart';
|
import 'talk_power_warning.dart';
|
||||||
|
|
||||||
/// Connected-server snapshot with welcome text, channels, and clients.
|
/// Connected-server snapshot displaying welcome text, channel tree, and clients.
|
||||||
|
///
|
||||||
|
/// Renders the full channel hierarchy from a [BridgeSnapshot] with expandable
|
||||||
|
/// channel nodes, client voice-status indicators, unread-message badges, and
|
||||||
|
/// context menus for client actions (info, chat, poke, volume).
|
||||||
|
///
|
||||||
|
/// Channel join is triggered by tapping an unlocked channel row; password-
|
||||||
|
/// protected channels invoke [onJoinChannelWithPassword] instead.
|
||||||
class SnapshotView extends StatefulWidget {
|
class SnapshotView extends StatefulWidget {
|
||||||
/// Construct a snapshot view.
|
/// Construct a snapshot view.
|
||||||
const SnapshotView({
|
const SnapshotView({
|
||||||
@@ -1149,7 +1156,7 @@ class _ClientVolumeSheetState extends State<_ClientVolumeSheet> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Context menu for channel tiles. Shows a "Chat" option on right-click or
|
/// Context menu for channel tiles offering "Chat" on right-click or
|
||||||
/// long-press. Primary tap passes through to the child for voice join.
|
/// long-press. Primary tap passes through to the child for voice join.
|
||||||
class _ChannelContextMenu extends StatelessWidget {
|
class _ChannelContextMenu extends StatelessWidget {
|
||||||
const _ChannelContextMenu({
|
const _ChannelContextMenu({
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ import 'voice_platform.dart';
|
|||||||
import 'voice_settings_controls.dart';
|
import 'voice_settings_controls.dart';
|
||||||
import '../src/rust/api.dart' as rust;
|
import '../src/rust/api.dart' as rust;
|
||||||
|
|
||||||
/// Result returned by [VoiceSettingsDialog].
|
/// Result returned by [VoiceSettingsDialog] when the user saves or
|
||||||
|
/// requests a key bind.
|
||||||
class VoiceSettingsResult {
|
class VoiceSettingsResult {
|
||||||
const VoiceSettingsResult({
|
const VoiceSettingsResult({
|
||||||
required this.mode,
|
required this.mode,
|
||||||
@@ -34,7 +35,15 @@ class VoiceSettingsResult {
|
|||||||
final rust.BridgeAudioProcessingConfig audioConfig;
|
final rust.BridgeAudioProcessingConfig audioConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Voice + audio processing settings dialog.
|
/// Dialog for configuring voice transmission and audio processing settings.
|
||||||
|
///
|
||||||
|
/// Surfaces transmit mode selection (continuous, PTT, voice-activity),
|
||||||
|
/// PTT release-tail slider, key-bind request, and a full audio processing
|
||||||
|
/// panel covering noise suppression, echo cancellation, AGC, HPF, and VAD
|
||||||
|
/// backend selection.
|
||||||
|
///
|
||||||
|
/// On mobile hosts, the voice-activity segment is hidden when no
|
||||||
|
/// Chanora-owned VAD pipeline is available (iOS, macOS, web).
|
||||||
class VoiceSettingsDialog extends StatefulWidget {
|
class VoiceSettingsDialog extends StatefulWidget {
|
||||||
const VoiceSettingsDialog({
|
const VoiceSettingsDialog({
|
||||||
super.key,
|
super.key,
|
||||||
|
|||||||
Reference in New Issue
Block a user