From 091f5cb6c2533f68fed9aded0f98c71406afa1a3 Mon Sep 17 00:00:00 2001 From: Fadi George Date: Fri, 16 Oct 2026 16:52:10 -0700 Subject: [PATCH 1/6] chore: [SDK-5526] add sticky log view to demo app Co-Authored-By: Claude Opus 5.5 --- examples/demo/lib/main.dart | 24 +-- examples/demo/lib/screens/home_screen.dart | 141 ++++++++------ examples/demo/lib/services/log_service.dart | 38 ++++ examples/demo/lib/theme.dart | 8 +- .../demo/lib/viewmodels/app_viewmodel.dart | 11 +- examples/demo/lib/widgets/log_view.dart | 181 ++++++++++++++++++ 6 files changed, 321 insertions(+), 82 deletions(-) create mode 100644 examples/demo/lib/services/log_service.dart create mode 100644 examples/demo/lib/widgets/log_view.dart diff --git a/examples/demo/lib/main.dart b/examples/demo/lib/main.dart index 8fdfefa2..5eaffe1f 100644 --- a/examples/demo/lib/main.dart +++ b/examples/demo/lib/main.dart @@ -6,6 +6,7 @@ import 'package:provider/provider.dart'; import 'screens/home_screen.dart'; import 'services/onesignal_api_service.dart'; +import 'services/log_service.dart'; import 'services/preferences_service.dart'; import 'services/tooltip_helper.dart'; import 'theme.dart'; @@ -48,35 +49,36 @@ Future main() async { // Register IAM listeners OneSignal.InAppMessages.addWillDisplayListener((event) { - debugPrint('[OneSignal] IAM willDisplay: ${event.message.messageId}'); + LogService.log('IAM willDisplay: ${event.message.messageId}'); }); OneSignal.InAppMessages.addDidDisplayListener((event) { - debugPrint('[OneSignal] IAM didDisplay: ${event.message.messageId}'); + LogService.log('IAM didDisplay: ${event.message.messageId}'); }); OneSignal.InAppMessages.addWillDismissListener((event) { - debugPrint('[OneSignal] IAM willDismiss: ${event.message.messageId}'); + LogService.log('IAM willDismiss: ${event.message.messageId}'); }); OneSignal.InAppMessages.addDidDismissListener((event) { - debugPrint('[OneSignal] IAM didDismiss: ${event.message.messageId}'); + LogService.log('IAM didDismiss: ${event.message.messageId}'); }); OneSignal.InAppMessages.addClickListener((event) { - debugPrint('[OneSignal] IAM click: ${event.message.messageId}'); + LogService.log('IAM click: ${event.message.messageId}'); }); // Register notification listeners OneSignal.Notifications.addClickListener((event) { - debugPrint( - '[OneSignal] Notification click: ${event.notification.title ?? ''}', + LogService.log( + 'Notification click: ${event.notification.notificationId} ' + '${event.notification.title ?? ''}', ); // Uncomment to see the full event object. // debugPrint('[OneSignal] click event: ${event.jsonRepresentation()}'); }); - + OneSignal.Notifications.addForegroundWillDisplayListener((event) { - debugPrint( - '[OneSignal] Notification foregroundWillDisplay: ' - '${event.notification.title ?? ''}', + LogService.log( + 'Notification foregroundWillDisplay: ' + '${event.notification.notificationId} ${event.notification.title ?? ''}', ); // Uncomment to see the full event object. diff --git a/examples/demo/lib/screens/home_screen.dart b/examples/demo/lib/screens/home_screen.dart index 20e8894d..fac1ed40 100644 --- a/examples/demo/lib/screens/home_screen.dart +++ b/examples/demo/lib/screens/home_screen.dart @@ -7,6 +7,7 @@ import '../theme.dart'; import '../services/tooltip_helper.dart'; import '../viewmodels/app_viewmodel.dart'; import '../widgets/dialogs.dart'; +import '../widgets/log_view.dart'; import '../widgets/sections/aliases_section.dart'; import '../widgets/sections/app_section.dart'; import '../widgets/sections/user_section.dart'; @@ -77,68 +78,88 @@ class _HomeScreenState extends State { ], ), ), - body: Semantics( - identifier: 'main_scroll_view', - child: ListView( - padding: const EdgeInsets.only(bottom: 24), - children: [ - const AppSection(), - const UserSection(), - PushSection(onInfoTap: () => _showTooltipDialog(context, 'push')), - SendPushSection( - onInfoTap: - () => _showTooltipDialog(context, 'sendPushNotification'), - ), - InAppSection( - onInfoTap: () => _showTooltipDialog(context, 'inAppMessaging'), - ), - SendIamSection( - onInfoTap: () => _showTooltipDialog(context, 'sendInAppMessage'), - ), - AliasesSection( - onInfoTap: () => _showTooltipDialog(context, 'aliases'), - ), - EmailsSection( - onInfoTap: () => _showTooltipDialog(context, 'emails'), - ), - SmsSection(onInfoTap: () => _showTooltipDialog(context, 'sms')), - TagsSection(onInfoTap: () => _showTooltipDialog(context, 'tags')), - OutcomesSection( - onInfoTap: () => _showTooltipDialog(context, 'outcomes'), - ), - TriggersSection( - onInfoTap: () => _showTooltipDialog(context, 'triggers'), - ), - CustomEventsSection( - onInfoTap: () => _showTooltipDialog(context, 'customEvents'), - ), - LocationSection( - onInfoTap: () => _showTooltipDialog(context, 'location'), - ), - if (defaultTargetPlatform == TargetPlatform.iOS) - LiveActivitiesSection( - onInfoTap: () => _showTooltipDialog(context, 'liveActivities'), - ), - const SizedBox(height: 8), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: ElevatedButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute(builder: (_) => const SecondaryScreen()), - ); - }, - style: ElevatedButton.styleFrom( - backgroundColor: AppColors.osPrimary, - foregroundColor: Colors.white, - ), - child: const Text('NEXT SCREEN'), + body: Column( + children: [ + const LogView(), + Expanded( + child: Semantics( + identifier: 'main_scroll_view', + child: ListView( + padding: const EdgeInsets.only(bottom: 24), + children: [ + const AppSection(), + const UserSection(), + PushSection( + onInfoTap: () => _showTooltipDialog(context, 'push'), + ), + SendPushSection( + onInfoTap: + () => + _showTooltipDialog(context, 'sendPushNotification'), + ), + InAppSection( + onInfoTap: + () => _showTooltipDialog(context, 'inAppMessaging'), + ), + SendIamSection( + onInfoTap: + () => _showTooltipDialog(context, 'sendInAppMessage'), + ), + AliasesSection( + onInfoTap: () => _showTooltipDialog(context, 'aliases'), + ), + EmailsSection( + onInfoTap: () => _showTooltipDialog(context, 'emails'), + ), + SmsSection( + onInfoTap: () => _showTooltipDialog(context, 'sms'), + ), + TagsSection( + onInfoTap: () => _showTooltipDialog(context, 'tags'), + ), + OutcomesSection( + onInfoTap: () => _showTooltipDialog(context, 'outcomes'), + ), + TriggersSection( + onInfoTap: () => _showTooltipDialog(context, 'triggers'), + ), + CustomEventsSection( + onInfoTap: + () => _showTooltipDialog(context, 'customEvents'), + ), + LocationSection( + onInfoTap: () => _showTooltipDialog(context, 'location'), + ), + if (defaultTargetPlatform == TargetPlatform.iOS) + LiveActivitiesSection( + onInfoTap: + () => _showTooltipDialog(context, 'liveActivities'), + ), + const SizedBox(height: 8), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: ElevatedButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => const SecondaryScreen(), + ), + ); + }, + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.osPrimary, + foregroundColor: Colors.white, + ), + child: const Text('NEXT SCREEN'), + ), + ), + const SizedBox(height: 16), + ], ), ), - const SizedBox(height: 16), - ], - ), + ), + ], ), ); } diff --git a/examples/demo/lib/services/log_service.dart b/examples/demo/lib/services/log_service.dart new file mode 100644 index 00000000..367b9c2a --- /dev/null +++ b/examples/demo/lib/services/log_service.dart @@ -0,0 +1,38 @@ +import 'package:flutter/foundation.dart'; + +class LogEntry { + const LogEntry(this.time, this.message); + + final String time; + final String message; +} + +class LogService { + static const _maxEntries = 100; + + /// Kept in memory only, so each launch starts empty. + static final ValueNotifier> entries = ValueNotifier(const []); + + /// Write to the on-screen log view and the console. + static void log(String message) { + debugPrint('[OneSignal] $message'); + final next = [ + ...entries.value, + LogEntry(_formatTime(DateTime.now()), message), + ]; + entries.value = + next.length > _maxEntries + ? next.sublist(next.length - _maxEntries) + : next; + } + + static void clear() => entries.value = const []; + + static String _formatTime(DateTime date) => [ + date.hour, + date.minute, + date.second, + ].map((part) => part.toString().padLeft(2, '0')).join(':'); + + LogService._(); +} diff --git a/examples/demo/lib/theme.dart b/examples/demo/lib/theme.dart index 60d10b72..5ff97b8e 100644 --- a/examples/demo/lib/theme.dart +++ b/examples/demo/lib/theme.dart @@ -20,12 +20,8 @@ class AppColors { static const osDivider = Color(0xFFE8EAED); static const osWarningBackground = Color(0xFFFFF8E1); static const osBackdrop = Color(0x8A000000); - static const osLogBackground = Color(0xFF1A1B1E); - static const osLogDebug = Color(0xFF82AAFF); - static const osLogInfo = Color(0xFFC3E88D); - static const osLogWarn = Color(0xFFFFCB6B); - static const osLogError = Color(0xFFFF5370); - static const osLogTimestamp = Color(0xFF676E7B); + static const osLogBackground = Color(0xFF1E1E1E); + static const osLogText = Color(0xFFD4D4D4); AppColors._(); } diff --git a/examples/demo/lib/viewmodels/app_viewmodel.dart b/examples/demo/lib/viewmodels/app_viewmodel.dart index e2d69e96..ffbc67b0 100644 --- a/examples/demo/lib/viewmodels/app_viewmodel.dart +++ b/examples/demo/lib/viewmodels/app_viewmodel.dart @@ -3,6 +3,7 @@ import 'package:onesignal_flutter/onesignal_flutter.dart'; import '../models/in_app_message_type.dart'; import '../models/notification_type.dart'; +import '../services/log_service.dart'; import '../services/onesignal_api_service.dart'; import '../services/preferences_service.dart'; @@ -168,8 +169,8 @@ class AppViewModel extends ChangeNotifier { return t.length > 8 ? '${t.substring(0, 8)}…' : t; } - debugPrint( - '[OneSignal] Push subscription changed: ' + LogService.log( + 'Push subscription changed: ' 'id=${state.previous.id ?? 'null'} → ${state.current.id ?? 'null'}, ' 'optedIn=${state.previous.optedIn} → ${state.current.optedIn}, ' 'token=${fmtToken(state.previous.token)} → ${fmtToken(state.current.token)}', @@ -179,14 +180,14 @@ class AppViewModel extends ChangeNotifier { OneSignal.Notifications.addPermissionObserver((permission) { _hasNotificationPermission = permission; - debugPrint('[OneSignal] Permission changed: $permission'); + LogService.log('Permission changed: $permission'); notifyListeners(); }); OneSignal.User.addObserver((state) { final nextOnesignalId = state.current.onesignalId; - debugPrint( - '[OneSignal] User changed: onesignalId=${nextOnesignalId ?? 'null'}, ' + LogService.log( + 'User changed: onesignalId=${nextOnesignalId ?? 'null'}, ' 'externalId=${state.current.externalId ?? 'null'}', ); diff --git a/examples/demo/lib/widgets/log_view.dart b/examples/demo/lib/widgets/log_view.dart new file mode 100644 index 00000000..58039ea2 --- /dev/null +++ b/examples/demo/lib/widgets/log_view.dart @@ -0,0 +1,181 @@ +import 'package:flutter/material.dart'; + +import '../services/log_service.dart'; +import '../theme.dart'; + +const _monospace = 'monospace'; +const _entryFontSize = 8.0; + +class LogView extends StatefulWidget { + const LogView({super.key}); + + @override + State createState() => _LogViewState(); +} + +class _LogViewState extends State { + final _scrollController = ScrollController(); + bool _collapsed = false; + + @override + void initState() { + super.initState(); + LogService.entries.addListener(_scrollToEnd); + } + + @override + void dispose() { + LogService.entries.removeListener(_scrollToEnd); + _scrollController.dispose(); + super.dispose(); + } + + void _scrollToEnd() { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!_scrollController.hasClients) return; + _scrollController.jumpTo(_scrollController.position.maxScrollExtent); + }); + } + + void _toggle() { + setState(() => _collapsed = !_collapsed); + if (!_collapsed) _scrollToEnd(); + } + + @override + Widget build(BuildContext context) { + return Semantics( + identifier: 'log_view', + container: true, + child: ColoredBox( + color: AppColors.osLogBackground, + child: ValueListenableBuilder>( + valueListenable: LogService.entries, + builder: + (context, logs, _) => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _buildHeader(logs.length), + if (!_collapsed) _buildBody(logs), + ], + ), + ), + ), + ); + } + + Widget _buildHeader(int count) { + return SizedBox( + height: 24, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + children: [ + Expanded( + child: Text( + 'LOGS ($count)', + style: const TextStyle( + color: AppColors.osGrey500, + fontFamily: _monospace, + fontSize: 11, + fontWeight: FontWeight.bold, + letterSpacing: 0.5, + ), + ), + ), + _HeaderButton( + identifier: 'log_clear_button', + label: 'Clear logs', + icon: Icons.delete, + size: 14, + onTap: LogService.clear, + ), + const SizedBox(width: 12), + _HeaderButton( + identifier: 'log_toggle_button', + label: _collapsed ? 'Expand logs' : 'Collapse logs', + icon: _collapsed ? Icons.expand_more : Icons.expand_less, + size: 18, + onTap: _toggle, + ), + ], + ), + ), + ); + } + + Widget _buildBody(List logs) { + const entryStyle = TextStyle( + color: AppColors.osLogText, + fontFamily: _monospace, + fontSize: _entryFontSize, + height: 1.4, + ); + const mutedStyle = TextStyle(color: AppColors.osGrey600); + + return SizedBox( + height: 100, + child: SingleChildScrollView( + controller: _scrollController, + padding: const EdgeInsets.fromLTRB(16, 0, 16, 6), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (logs.isEmpty) + Text('No logs yet', style: entryStyle.merge(mutedStyle)), + for (final (index, entry) in logs.indexed) + Semantics( + identifier: 'log_entry_$index', + container: true, + child: Text.rich( + TextSpan( + children: [ + TextSpan(text: entry.time, style: mutedStyle), + TextSpan(text: ' ${entry.message}'), + ], + ), + style: entryStyle, + ), + ), + ], + ), + ), + ); + } +} + +class _HeaderButton extends StatelessWidget { + const _HeaderButton({ + required this.identifier, + required this.label, + required this.icon, + required this.size, + required this.onTap, + }); + + final String identifier; + final String label; + final IconData icon; + final double size; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return Semantics( + identifier: identifier, + container: true, + button: true, + label: label, + excludeSemantics: true, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: onTap, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 2), + child: Icon(icon, size: size, color: AppColors.osGrey500), + ), + ), + ); + } +} From 6ea3ed5fd038512c60e3c2ac90b2f22bb1465831 Mon Sep 17 00:00:00 2001 From: Fadi George Date: Fri, 16 Oct 2026 17:08:10 -0700 Subject: [PATCH 2/6] fix(demo): [SDK-5526] address log view review feedback Co-Authored-By: Claude Opus 5.5 --- examples/demo/lib/main.dart | 4 ++-- examples/demo/lib/widgets/log_view.dart | 24 +++++++++++++++++------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/examples/demo/lib/main.dart b/examples/demo/lib/main.dart index 5eaffe1f..7542c678 100644 --- a/examples/demo/lib/main.dart +++ b/examples/demo/lib/main.dart @@ -108,7 +108,7 @@ Future main() async { try { apiKey = dotenv.env['ONESIGNAL_API_KEY'] ?? ''; } catch (_) { - debugPrint('[OneSignal] API key not found, continuing without it'); + LogService.log('API key not found, continuing without it'); } final apiService = OneSignalApiService() @@ -118,7 +118,7 @@ Future main() async { // Fetch tooltips in background TooltipHelper().init(); - debugPrint('[OneSignal] Initialized with app ID: $appId'); + LogService.log('Initialized with app ID: $appId'); runApp( ChangeNotifierProvider( diff --git a/examples/demo/lib/widgets/log_view.dart b/examples/demo/lib/widgets/log_view.dart index 58039ea2..3891a192 100644 --- a/examples/demo/lib/widgets/log_view.dart +++ b/examples/demo/lib/widgets/log_view.dart @@ -20,19 +20,27 @@ class _LogViewState extends State { @override void initState() { super.initState(); - LogService.entries.addListener(_scrollToEnd); + LogService.entries.addListener(_onEntriesChanged); } @override void dispose() { - LogService.entries.removeListener(_scrollToEnd); + LogService.entries.removeListener(_onEntriesChanged); _scrollController.dispose(); super.dispose(); } + // Keep following new entries only while the reader is already at the end. + void _onEntriesChanged() { + final atEnd = + !_scrollController.hasClients || + _scrollController.position.extentAfter < _entryFontSize * 2; + if (atEnd) _scrollToEnd(); + } + void _scrollToEnd() { WidgetsBinding.instance.addPostFrameCallback((_) { - if (!_scrollController.hasClients) return; + if (!mounted || !_scrollController.hasClients) return; _scrollController.jumpTo(_scrollController.position.maxScrollExtent); }); } @@ -69,7 +77,7 @@ class _LogViewState extends State { return SizedBox( height: 24, child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), + padding: const EdgeInsets.only(left: 16, right: 9), child: Row( children: [ Expanded( @@ -91,7 +99,6 @@ class _LogViewState extends State { size: 14, onTap: LogService.clear, ), - const SizedBox(width: 12), _HeaderButton( identifier: 'log_toggle_button', label: _collapsed ? 'Expand logs' : 'Collapse logs', @@ -168,11 +175,14 @@ class _HeaderButton extends StatelessWidget { button: true, label: label, excludeSemantics: true, + onTap: onTap, child: GestureDetector( behavior: HitTestBehavior.opaque, onTap: onTap, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 2), + // The header is only 24 tall, so widen the target instead. + child: SizedBox( + width: 32, + height: double.infinity, child: Icon(icon, size: size, color: AppColors.osGrey500), ), ), From 11bc0540c318d97914e1ab7aa93e15ab7299c129 Mon Sep 17 00:00:00 2001 From: Fadi George Date: Fri, 16 Oct 2026 17:08:58 -0700 Subject: [PATCH 3/6] chore(demo): [SDK-5526] keep init messages console-only Co-Authored-By: Claude Opus 5.5 --- examples/demo/lib/main.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/demo/lib/main.dart b/examples/demo/lib/main.dart index 7542c678..5eaffe1f 100644 --- a/examples/demo/lib/main.dart +++ b/examples/demo/lib/main.dart @@ -108,7 +108,7 @@ Future main() async { try { apiKey = dotenv.env['ONESIGNAL_API_KEY'] ?? ''; } catch (_) { - LogService.log('API key not found, continuing without it'); + debugPrint('[OneSignal] API key not found, continuing without it'); } final apiService = OneSignalApiService() @@ -118,7 +118,7 @@ Future main() async { // Fetch tooltips in background TooltipHelper().init(); - LogService.log('Initialized with app ID: $appId'); + debugPrint('[OneSignal] Initialized with app ID: $appId'); runApp( ChangeNotifierProvider( From b5e8d1fdc8e8dbf6bd21ef0ea7913e1ff0666993 Mon Sep 17 00:00:00 2001 From: Fadi George Date: Tue, 20 Oct 2026 12:01:11 -0700 Subject: [PATCH 4/6] chore(demo): [SDK-5526] add log levels to demo LogService Co-Authored-By: Claude Opus 5.5 --- examples/demo/lib/main.dart | 14 ++-- examples/demo/lib/services/log_service.dart | 20 ++++- .../demo/lib/viewmodels/app_viewmodel.dart | 82 +++++++++---------- 3 files changed, 64 insertions(+), 52 deletions(-) diff --git a/examples/demo/lib/main.dart b/examples/demo/lib/main.dart index 5eaffe1f..38bffa95 100644 --- a/examples/demo/lib/main.dart +++ b/examples/demo/lib/main.dart @@ -49,24 +49,24 @@ Future main() async { // Register IAM listeners OneSignal.InAppMessages.addWillDisplayListener((event) { - LogService.log('IAM willDisplay: ${event.message.messageId}'); + LogService.show('IAM willDisplay: ${event.message.messageId}'); }); OneSignal.InAppMessages.addDidDisplayListener((event) { - LogService.log('IAM didDisplay: ${event.message.messageId}'); + LogService.show('IAM didDisplay: ${event.message.messageId}'); }); OneSignal.InAppMessages.addWillDismissListener((event) { - LogService.log('IAM willDismiss: ${event.message.messageId}'); + LogService.show('IAM willDismiss: ${event.message.messageId}'); }); OneSignal.InAppMessages.addDidDismissListener((event) { - LogService.log('IAM didDismiss: ${event.message.messageId}'); + LogService.show('IAM didDismiss: ${event.message.messageId}'); }); OneSignal.InAppMessages.addClickListener((event) { - LogService.log('IAM click: ${event.message.messageId}'); + LogService.show('IAM click: ${event.message.messageId}'); }); // Register notification listeners OneSignal.Notifications.addClickListener((event) { - LogService.log( + LogService.show( 'Notification click: ${event.notification.notificationId} ' '${event.notification.title ?? ''}', ); @@ -76,7 +76,7 @@ Future main() async { }); OneSignal.Notifications.addForegroundWillDisplayListener((event) { - LogService.log( + LogService.show( 'Notification foregroundWillDisplay: ' '${event.notification.notificationId} ${event.notification.title ?? ''}', ); diff --git a/examples/demo/lib/services/log_service.dart b/examples/demo/lib/services/log_service.dart index 367b9c2a..256686dd 100644 --- a/examples/demo/lib/services/log_service.dart +++ b/examples/demo/lib/services/log_service.dart @@ -10,12 +10,24 @@ class LogEntry { class LogService { static const _maxEntries = 100; - /// Kept in memory only, so each launch starts empty. + /// Log view entries are kept in memory only, so each launch starts empty. static final ValueNotifier> entries = ValueNotifier(const []); - /// Write to the on-screen log view and the console. - static void log(String message) { - debugPrint('[OneSignal] $message'); + static void info(String message) => _write(message); + + static void show(String message) { + _write(message); + _addToView(message); + } + + static void error(String message) { + _write('ERROR: $message'); + _addToView(message); + } + + static void _write(String message) => debugPrint('[OneSignal] $message'); + + static void _addToView(String message) { final next = [ ...entries.value, LogEntry(_formatTime(DateTime.now()), message), diff --git a/examples/demo/lib/viewmodels/app_viewmodel.dart b/examples/demo/lib/viewmodels/app_viewmodel.dart index ffbc67b0..588bef0f 100644 --- a/examples/demo/lib/viewmodels/app_viewmodel.dart +++ b/examples/demo/lib/viewmodels/app_viewmodel.dart @@ -169,7 +169,7 @@ class AppViewModel extends ChangeNotifier { return t.length > 8 ? '${t.substring(0, 8)}…' : t; } - LogService.log( + LogService.show( 'Push subscription changed: ' 'id=${state.previous.id ?? 'null'} → ${state.current.id ?? 'null'}, ' 'optedIn=${state.previous.optedIn} → ${state.current.optedIn}, ' @@ -180,13 +180,13 @@ class AppViewModel extends ChangeNotifier { OneSignal.Notifications.addPermissionObserver((permission) { _hasNotificationPermission = permission; - LogService.log('Permission changed: $permission'); + LogService.show('Permission changed: $permission'); notifyListeners(); }); OneSignal.User.addObserver((state) { final nextOnesignalId = state.current.onesignalId; - LogService.log( + LogService.show( 'User changed: onesignalId=${nextOnesignalId ?? 'null'}, ' 'externalId=${state.current.externalId ?? 'null'}', ); @@ -252,11 +252,11 @@ class AppViewModel extends ChangeNotifier { try { await OneSignal.login(externalUserId); await _prefs.setExternalUserId(externalUserId); - debugPrint('Logged in as: $externalUserId'); + LogService.info('Logged in as: $externalUserId'); // The User observer runs fetchUserDataFromApi once the new onesignalId // is assigned; that call clears _isLoading in its finally. } catch (e) { - debugPrint('Login error: $e'); + LogService.error('Login error: $e'); _isLoading = false; notifyListeners(); } @@ -274,9 +274,9 @@ class AppViewModel extends ChangeNotifier { try { await OneSignal.logout(); await _prefs.setExternalUserId(null); - debugPrint('Logged out'); + LogService.info('Logged out'); } catch (e) { - debugPrint('Logout error: $e'); + LogService.error('Logout error: $e'); } } @@ -308,7 +308,7 @@ class AppViewModel extends ChangeNotifier { } _pushEnabled = enabled; notifyListeners(); - debugPrint('Push ${enabled ? "enabled" : "disabled"}'); + LogService.info('Push ${enabled ? "enabled" : "disabled"}'); } Future promptPush() => OneSignal.Notifications.requestPermission(true); @@ -317,21 +317,21 @@ class AppViewModel extends ChangeNotifier { Future sendNotification(NotificationType type) async { final subscriptionId = OneSignal.User.pushSubscription.id; if (subscriptionId == null) { - debugPrint('No subscription ID for notification'); + LogService.info('No subscription ID for notification'); return; } final success = await _apiService.sendNotification(type, subscriptionId); if (success) { - debugPrint('Notification sent: ${type.name}'); + LogService.info('Notification sent: ${type.name}'); } else { - debugPrint('Failed to send notification'); + LogService.info('Failed to send notification'); } } Future sendCustomNotification(String title, String body) async { final subscriptionId = OneSignal.User.pushSubscription.id; if (subscriptionId == null) { - debugPrint('No subscription ID for custom notification'); + LogService.info('No subscription ID for custom notification'); return; } final success = await _apiService.sendCustomNotification( @@ -340,15 +340,15 @@ class AppViewModel extends ChangeNotifier { subscriptionId, ); if (success) { - debugPrint('Custom notification sent'); + LogService.info('Custom notification sent'); } else { - debugPrint('Failed to send notification'); + LogService.info('Failed to send notification'); } } void clearAllNotifications() { OneSignal.Notifications.clearAll(); - debugPrint('All notifications cleared'); + LogService.info('All notifications cleared'); } // IAM @@ -363,7 +363,7 @@ class AppViewModel extends ChangeNotifier { OneSignal.InAppMessages.addTrigger('iam_type', type.triggerValue); _triggersList = _mergePairs(_triggersList, {'iam_type': type.triggerValue}); notifyListeners(); - debugPrint('Sent In-App Message: ${type.label}'); + LogService.info('Sent In-App Message: ${type.label}'); } // Aliases @@ -371,14 +371,14 @@ class AppViewModel extends ChangeNotifier { OneSignal.User.addAlias(label, id); _aliasesList = _mergePairs(_aliasesList, {label: id}); notifyListeners(); - debugPrint('Alias added: $label'); + LogService.info('Alias added: $label'); } void addAliases(Map aliases) { OneSignal.User.addAliases(aliases); _aliasesList = _mergePairs(_aliasesList, aliases); notifyListeners(); - debugPrint('${aliases.length} alias(es) added'); + LogService.info('${aliases.length} alias(es) added'); } // Emails @@ -386,14 +386,14 @@ class AppViewModel extends ChangeNotifier { OneSignal.User.addEmail(email); _emailsList = _mergeUnique(_emailsList, [email]); notifyListeners(); - debugPrint('Email added: $email'); + LogService.info('Email added: $email'); } void removeEmail(String email) { OneSignal.User.removeEmail(email); _emailsList = List.from(_emailsList)..remove(email); notifyListeners(); - debugPrint('Email removed: $email'); + LogService.info('Email removed: $email'); } // SMS @@ -401,14 +401,14 @@ class AppViewModel extends ChangeNotifier { OneSignal.User.addSms(smsNumber); _smsNumbersList = _mergeUnique(_smsNumbersList, [smsNumber]); notifyListeners(); - debugPrint('SMS added: $smsNumber'); + LogService.info('SMS added: $smsNumber'); } void removeSms(String smsNumber) { OneSignal.User.removeSms(smsNumber); _smsNumbersList = List.from(_smsNumbersList)..remove(smsNumber); notifyListeners(); - debugPrint('SMS removed: $smsNumber'); + LogService.info('SMS removed: $smsNumber'); } // Tags @@ -416,28 +416,28 @@ class AppViewModel extends ChangeNotifier { OneSignal.User.addTagWithKey(key, value); _tagsList = _mergePairs(_tagsList, {key: value}); notifyListeners(); - debugPrint('Tag added: $key'); + LogService.info('Tag added: $key'); } void addTags(Map tags) { OneSignal.User.addTags(tags); _tagsList = _mergePairs(_tagsList, tags); notifyListeners(); - debugPrint('${tags.length} tag(s) added'); + LogService.info('${tags.length} tag(s) added'); } void removeTag(String key) { OneSignal.User.removeTag(key); _tagsList = List.from(_tagsList)..removeWhere((e) => e.key == key); notifyListeners(); - debugPrint('Tag removed: $key'); + LogService.info('Tag removed: $key'); } void removeSelectedTags(List keys) { OneSignal.User.removeTags(keys); _tagsList = List.from(_tagsList)..removeWhere((e) => keys.contains(e.key)); notifyListeners(); - debugPrint('${keys.length} tag(s) removed'); + LogService.info('${keys.length} tag(s) removed'); } // Triggers (in-memory only) @@ -445,21 +445,21 @@ class AppViewModel extends ChangeNotifier { OneSignal.InAppMessages.addTrigger(key, value); _triggersList = _mergePairs(_triggersList, {key: value}); notifyListeners(); - debugPrint('Trigger added: $key'); + LogService.info('Trigger added: $key'); } void addTriggers(Map triggers) { OneSignal.InAppMessages.addTriggers(triggers); _triggersList = _mergePairs(_triggersList, triggers); notifyListeners(); - debugPrint('${triggers.length} trigger(s) added'); + LogService.info('${triggers.length} trigger(s) added'); } void removeTrigger(String key) { OneSignal.InAppMessages.removeTrigger(key); _triggersList = List.from(_triggersList)..removeWhere((e) => e.key == key); notifyListeners(); - debugPrint('Trigger removed: $key'); + LogService.info('Trigger removed: $key'); } void removeSelectedTriggers(List keys) { @@ -467,36 +467,36 @@ class AppViewModel extends ChangeNotifier { _triggersList = List.from(_triggersList) ..removeWhere((e) => keys.contains(e.key)); notifyListeners(); - debugPrint('${keys.length} trigger(s) removed'); + LogService.info('${keys.length} trigger(s) removed'); } void clearAllTriggers() { OneSignal.InAppMessages.clearTriggers(); _triggersList = []; notifyListeners(); - debugPrint('All triggers cleared'); + LogService.info('All triggers cleared'); } // Outcomes void sendOutcome(String name) { OneSignal.Session.addOutcome(name); - debugPrint('Outcome sent: $name'); + LogService.info('Outcome sent: $name'); } void sendUniqueOutcome(String name) { OneSignal.Session.addUniqueOutcome(name); - debugPrint('Unique outcome sent: $name'); + LogService.info('Unique outcome sent: $name'); } void sendOutcomeWithValue(String name, double value) { OneSignal.Session.addOutcomeWithValue(name, value); - debugPrint('Outcome sent: $name = $value'); + LogService.info('Outcome sent: $name = $value'); } // Custom Events void trackEvent(String name, Map? properties) { OneSignal.User.trackEvent(name, properties); - debugPrint('Event tracked: $name'); + LogService.info('Event tracked: $name'); } // Live Activities @@ -520,7 +520,7 @@ class AppViewModel extends ChangeNotifier { content, ); notifyListeners(); - debugPrint('Started Live Activity: $_activityId'); + LogService.info('Started Live Activity: $_activityId'); } Future updateLiveActivity() async { @@ -538,9 +538,9 @@ class AppViewModel extends ChangeNotifier { _isLaUpdating = false; if (success) { _statusIndex = nextIndex; - debugPrint('Updated Live Activity: $_activityId'); + LogService.info('Updated Live Activity: $_activityId'); } else { - debugPrint('Failed to update Live Activity'); + LogService.info('Failed to update Live Activity'); } notifyListeners(); } @@ -549,9 +549,9 @@ class AppViewModel extends ChangeNotifier { final success = await _apiService.endLiveActivity(_activityId); if (success) { _statusIndex = 0; - debugPrint('Ended Live Activity: $_activityId'); + LogService.info('Ended Live Activity: $_activityId'); } else { - debugPrint('Failed to end Live Activity'); + LogService.info('Failed to end Live Activity'); } notifyListeners(); } @@ -562,7 +562,7 @@ class AppViewModel extends ChangeNotifier { OneSignal.Location.setShared(shared); await _prefs.setLocationShared(shared); notifyListeners(); - debugPrint('Location sharing ${shared ? "enabled" : "disabled"}'); + LogService.info('Location sharing ${shared ? "enabled" : "disabled"}'); } void promptLocation() { From dbeb6015771131967df91e549332298b8a623c73 Mon Sep 17 00:00:00 2001 From: Fadi George Date: Tue, 20 Oct 2026 12:48:38 -0700 Subject: [PATCH 5/6] ci: pin Java 17 for spotless format check ubuntu-latest now defaults to a JDK that Gradle 8.7 cannot run. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fcdde444..e887d3c9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,6 +22,12 @@ jobs: - name: Static Analysis run: flutter analyze + - name: Set up Java + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: '17' + - name: Setup Clang uses: OneSignal/sdk-shared/.github/actions/setup-clang@main From 907fd23e0766c9125e8e5ec6012b7046c25fa22f Mon Sep 17 00:00:00 2001 From: Fadi George Date: Tue, 20 Oct 2026 14:04:26 -0700 Subject: [PATCH 6/6] chore: exclude build/android/ios from analysis --- analysis_options.yaml | 3 +++ examples/demo/analysis_options.yaml | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/analysis_options.yaml b/analysis_options.yaml index e4ac80b9..f02a9360 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -5,3 +5,6 @@ analyzer: - example_pod/** - example_spm/** - examples/** + - build/** + - android/** + - ios/** diff --git a/examples/demo/analysis_options.yaml b/examples/demo/analysis_options.yaml index 0d290213..0d87f43e 100644 --- a/examples/demo/analysis_options.yaml +++ b/examples/demo/analysis_options.yaml @@ -7,6 +7,11 @@ # The following line activates a set of recommended lints for Flutter apps, # packages, and plugins designed to encourage good coding practices. +analyzer: + exclude: + - build/** + - android/** + - ios/** include: package:flutter_lints/flutter.yaml linter: