diff --git a/example/lib/main.dart b/example/lib/main.dart index 5e98e97..35db08b 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -14,7 +14,6 @@ class ExampleApp extends StatelessWidget { return MaterialApp( builder: (context, child) { return NStackWidget( - platformOverride: AppOpenPlatform.android, child: child!, ); }, @@ -30,21 +29,26 @@ class MainScreen extends StatelessWidget { Widget build(BuildContext context) { final activeLanguage = context.nstack.localization.activeLanguage; - return Scaffold( - appBar: AppBar( - title: Text(context.localization.test.testDollarSign), - ), - body: Center( - child: MaterialButton( - onPressed: () { - final locale = activeLanguage.locale == 'en-EN' - ? const Locale('de-DE') - : const Locale('en-EN'); - - NStack.localization.changeLocalization(locale); - }, - child: Text( - 'Selected locale: ${activeLanguage.name}', + return NStackMessageListener( + onMessage: (message) { + NStackMessageDialog.show(context, message: message); + }, + child: Scaffold( + appBar: AppBar( + title: Text(context.localization.test.testDollarSign), + ), + body: Center( + child: MaterialButton( + onPressed: () { + final locale = activeLanguage.locale == 'en-EN' + ? const Locale('de-DE') + : const Locale('en-EN'); + + NStack.localization.changeLocalization(locale); + }, + child: Text( + 'Selected locale: ${activeLanguage.name}', + ), ), ), ), diff --git a/example/lib/nstack.dart b/example/lib/nstack.dart index 52eb62a..a944725 100644 --- a/example/lib/nstack.dart +++ b/example/lib/nstack.dart @@ -1,3 +1,5 @@ +// ignore_for_file: implementation_imports, non_constant_identifier_names, depend_on_referenced_packages + /* * ❌ GENERATED BY NSTACK, DO NOT MODIFY THIS FILE BY HAND! * @@ -22,16 +24,23 @@ */ import 'dart:async'; +import 'dart:io'; +import 'package:flutter/cupertino.dart' as cupertino; import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart' as material; import 'package:flutter/widgets.dart'; import 'package:nstack/models/app_open_platform.dart'; import 'package:nstack/models/language.dart'; import 'package:nstack/models/localize_index.dart'; +import 'package:nstack/models/message.dart'; import 'package:nstack/models/nstack_config.dart'; -import 'package:nstack/sdk/nstack_sdk.dart'; -import 'package:nstack/sdk/localization/nstack_localization.dart'; +import 'package:nstack/nstack.dart'; import 'package:nstack/partial/section_key_delegate.dart'; +import 'package:nstack/sdk/localization/nstack_localization.dart'; +import 'package:nstack/sdk/messages/nstack_messages.dart'; +import 'package:nstack/src/nstack_repository.dart'; +import 'package:url_launcher/url_launcher.dart'; export 'package:nstack/models/app_open_platform.dart'; @@ -42,13 +51,16 @@ export 'package:nstack/models/app_open_platform.dart'; */ final NStack = NStackSdk( - config: _config, - localization: _nstackLocalization, + repository: _nstackRepository, isDebug: kDebugMode, + localization: _nstackLocalization, + messages: _nstackMessages, ); +const _nstackRepository = NStackRepository(_config); + final _nstackLocalization = NStackLocalization( - config: _config, + repository: _nstackRepository, translations: const Localization(), availableLanguages: _languages, bundledTranslations: _bundledTranslations, @@ -56,6 +68,10 @@ final _nstackLocalization = NStackLocalization( isDebug: kDebugMode, ); +final _nstackMessages = NStackMessages( + repository: _nstackRepository, +); + const _config = NStackConfig( projectId: 'h6wJremI2TGFM88gbLkdyljWQuwf2hxhxvCH', apiKey: 'zp2S18H32b67eYAbRQh94tVw76ZzaKKXlHjd', @@ -142,6 +158,8 @@ class _Test extends SectionKeyDelegate { * NStack Flutter Widgets * */ + +/// Allows to access NStack features via a `BuildContext`. class NStackScope extends InheritedWidget { final NStackState state; final String checksum; @@ -161,10 +179,27 @@ class NStackScope extends InheritedWidget { checksum != oldWidget.checksum; } +/// Widget that is used for accessing NStack features from the widget tree +/// & listening for localization changes. +/// +/// Is required for all the children widgets like [NStackMessageListener] +/// +/// In your app, use the `builder` property like this: +/// ```dart +/// MaterialApp( +/// ... +/// builder: (context, child) { +/// return NStackWidget( +/// child: child!, +/// ); +/// }, +/// ... +/// ); +/// ``` class NStackWidget extends StatefulWidget { - final Widget child; final AppOpenPlatform? platformOverride; final VoidCallback? onComplete; + final Widget child; const NStackWidget({ Key? key, @@ -185,6 +220,12 @@ class NStackState extends State { late final StreamSubscription _localeChangedSubscription; + /// Gets the NStack Localization feature configured for this project. + NStackLocalization get localization => _nstack.localization; + + /// Gets the NStack Message feature configured for this project. + NStackMessages get messages => _nstack.messages; + @override void initState() { super.initState(); @@ -240,6 +281,215 @@ class NStackState extends State { } } +/* + * + * NStack Messages Feature + * + */ + +/// Listens for new messages from the NStack Messages feature. +/// +/// In where you want to use it, add this widget: +/// ```dart +/// Widget build(BuildContext context) { +/// return NStackMessageListener( +/// onMessage: (Message message) { +/// // Do whatever you want with the received message. +/// // For example, use NStackMessageDialog to display the message. +/// showDialog( +/// context: context, +/// builder: (context) { +/// return NStackMessageDialog(message: message); +/// }, +/// }, +/// child: Scaffold(...), +/// ); +/// } +/// ``` +class NStackMessageListener extends StatefulWidget { + const NStackMessageListener({ + Key? key, + required this.onMessage, + this.child, + }) : super(key: key); + + final void Function(Message message) onMessage; + final Widget? child; + + @override + State createState() => _NStackMessageListenerState(); +} + +class _NStackMessageListenerState extends State { + late final StreamSubscription _subscription; + + @override + void initState() { + super.initState(); + + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + final scope = NStackScope.of(context); + _subscription = scope.messages.onMessage.listen(widget.onMessage); + }); + } + + @override + void dispose() { + _subscription.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return widget.child ?? const SizedBox(); + } +} + +/// Default Message Alert Dialog that adapts to the platform and renders the: +/// - Message body +/// - OK Button +/// - Open URL button (if [Message.url] is provided) +/// +/// When the dialog is dismissed, the alert reports that the message is viewed. +/// +/// Use it like this: +/// ```dart +/// NStackMessageDialog.show( +/// context, +/// message: message, +/// /* Other params if needed */ +/// ); +/// ``` +class NStackMessageDialog extends StatelessWidget { + static const _okButtonTitleFallback = 'OK'; + static const _openUrlButtonTitleFallback = 'Open URL'; + static const _dialogTitleFallback = 'Message'; + + const NStackMessageDialog._({ + Key? key, + required this.message, + this.onOkPressed, + this.onOpenUrlPressed, + this.okButtonTitle = _okButtonTitleFallback, + this.openUrlButtonTitle = _openUrlButtonTitleFallback, + this.dialogTitle = _dialogTitleFallback, + }) : super(key: key); + + /// Message that was received. + final Message message; + + /// Title of the OK button. + final String okButtonTitle; + + /// Title of the Open URL button. + final String openUrlButtonTitle; + + /// Title of the dialog. + final String? dialogTitle; + + /// Optional callback when a user presses the OK button. + /// + /// By default, it closes the dialog and reports that the message was viewed. + final VoidCallback? onOkPressed; + + /// Optional callback when a user presses Open URL button. + /// + /// By default, it closes the dialog, reports that the message was viewed + /// and opens the URL. + final void Function(Uri uri)? onOpenUrlPressed; + + /// Displays the dialog. + static Future show( + BuildContext context, { + required Message message, + VoidCallback? onOkPressed, + void Function(Uri uri)? onOpenUrlPressed, + String? okButtonTitle, + String? openUrlButtonTitle, + String? dialogTitle = _dialogTitleFallback, + }) { + Widget builder(BuildContext context) { + return NStackMessageDialog._( + message: message, + onOkPressed: onOkPressed, + onOpenUrlPressed: onOpenUrlPressed, + okButtonTitle: okButtonTitle ?? + message.localization?.okBtn ?? + _okButtonTitleFallback, + openUrlButtonTitle: openUrlButtonTitle ?? + message.localization?.urlBtn ?? + _openUrlButtonTitleFallback, + dialogTitle: dialogTitle, + ); + } + + final showDialog = Platform.isIOS + ? cupertino.showCupertinoDialog(context: context, builder: builder) + : material.showDialog(context: context, builder: builder); + + return showDialog.whenComplete(() { + NStack.messages.setMessageViewed(message.id); + }); + } + + @override + Widget build(BuildContext context) { + final titleWidget = dialogTitle != null ? Text(dialogTitle!) : null; + final messageWidget = Text(message.message); + + final okWidget = Text(okButtonTitle); + final okAction = onOkPressed ?? Navigator.of(context).pop; + + final messageUrl = message.url; + final uri = messageUrl != null ? Uri.tryParse(messageUrl) : null; + + final isUriValid = uri != null; + + final urlLaunchAction = !isUriValid + ? null + : (onOpenUrlPressed != null ? () => onOpenUrlPressed!(uri!) : null) ?? + () { + launchUrl(uri!); + Navigator.of(context).pop(); + }; + + if (Platform.isIOS) { + return cupertino.CupertinoAlertDialog( + title: titleWidget, + content: messageWidget, + actions: [ + cupertino.CupertinoDialogAction( + onPressed: okAction, + child: okWidget, + ), + if (isUriValid) + cupertino.CupertinoDialogAction( + isDefaultAction: true, + onPressed: urlLaunchAction, + child: Text(openUrlButtonTitle), + ), + ], + ); + } + + return material.AlertDialog( + title: titleWidget, + content: messageWidget, + actions: [ + if (isUriValid) + material.TextButton( + onPressed: urlLaunchAction, + child: Text(openUrlButtonTitle), + ), + material.TextButton( + onPressed: okAction, + child: okWidget, + ), + ], + ); + } +} + /* * * NStack Flutter Extensions diff --git a/lib/models/message.dart b/lib/models/message.dart index 303145d..ee6ea42 100644 --- a/lib/models/message.dart +++ b/lib/models/message.dart @@ -1,11 +1,11 @@ import 'package:nstack/other/extensions.dart'; class Message { - final int? id; - final int? applicationId; + final int id; + final int applicationId; final MessageShowSetting? showSetting; final int? viewCount; - final String? message; + final String message; final String? url; final DateTime? createdAt; final DateTime? updatedAt; diff --git a/lib/nstack.dart b/lib/nstack.dart new file mode 100644 index 0000000..e93f93c --- /dev/null +++ b/lib/nstack.dart @@ -0,0 +1 @@ +export 'package:nstack/sdk/nstack_sdk.dart'; diff --git a/lib/sdk/localization/nstack_localization.dart b/lib/sdk/localization/nstack_localization.dart index 65f58dd..3da668c 100644 --- a/lib/sdk/localization/nstack_localization.dart +++ b/lib/sdk/localization/nstack_localization.dart @@ -6,7 +6,6 @@ import 'package:nstack/models/app_open.dart'; import 'package:nstack/models/language.dart'; import 'package:nstack/models/language_response.dart'; import 'package:nstack/models/localize_index.dart'; -import 'package:nstack/models/nstack_config.dart'; import 'package:nstack/src/nstack_repository.dart'; import 'package:nstack/src/repository.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -18,9 +17,8 @@ class NStackLocalization { final _onLocaleChanged = StreamController.broadcast(); - final NStackConfig config; - final TLocalization translations; final NStackRepository _repository; + final TLocalization translations; late List supportedLocales; final bool isDebug; @@ -39,13 +37,13 @@ class NStackLocalization { Locale? clientLocale; NStackLocalization({ - required this.config, + required NStackRepository repository, required this.translations, required List availableLanguages, required Map bundledTranslations, required String pickedLanguageLocale, required this.isDebug, - }) : _repository = NStackRepository(config) { + }) : _repository = repository { supportedLocales = availableLanguages .map((e) => Locale(e.language?.locale?.split("-")[0] ?? "en", e.language?.locale?.split("-")[1].toUpperCase() ?? "US")) diff --git a/lib/sdk/messages/nstack_messages.dart b/lib/sdk/messages/nstack_messages.dart new file mode 100644 index 0000000..ec0ee5f --- /dev/null +++ b/lib/sdk/messages/nstack_messages.dart @@ -0,0 +1,47 @@ +import 'dart:async'; + +import 'package:nstack/models/app_open.dart'; +import 'package:nstack/models/message.dart'; +import 'package:nstack/models/nstack_appopen_data.dart'; +import 'package:nstack/src/nstack_repository.dart'; + +class NStackMessages { + final NStackRepository _repository; + late final NStackAppOpenData _appOpenData; + + // There's no dispose / close method + // ignore: close_sinks + final _onMessage = StreamController.broadcast(); + + Stream get onMessage => _onMessage.stream; + + NStackMessages({ + required NStackRepository repository, + }) : _repository = repository; + + Future setMessageViewed(int messageId) { + return _repository + .postMessageSeen( + appOpenData: _appOpenData, + messageId: messageId, + ) + .catchError((e, s) { + print( + 'NStack --> Couldnt post message seen because of: $e \n $s', + ); + }); + } + + void onAppOpen( + AppOpen appOpen, + NStackAppOpenData appOpenData, + ) { + _appOpenData = appOpenData; + + final message = appOpen.data.message; + + if (message != null) { + _onMessage.add(message); + } + } +} diff --git a/lib/sdk/nstack_sdk.dart b/lib/sdk/nstack_sdk.dart index 4b2a285..043fc8d 100644 --- a/lib/sdk/nstack_sdk.dart +++ b/lib/sdk/nstack_sdk.dart @@ -5,8 +5,8 @@ import 'package:flutter/widgets.dart'; import 'package:nstack/models/app_open.dart'; import 'package:nstack/models/app_open_platform.dart'; import 'package:nstack/models/nstack_appopen_data.dart'; -import 'package:nstack/models/nstack_config.dart'; import 'package:nstack/sdk/localization/nstack_localization.dart'; +import 'package:nstack/sdk/messages/nstack_messages.dart'; import 'package:nstack/src/nstack_repository.dart'; import 'package:nstack/src/repository.dart'; import 'package:package_info/package_info.dart'; @@ -15,8 +15,6 @@ import 'package:uuid/uuid.dart'; /// The core class containing all the NStack features. class NStackSdk { - final NStackConfig config; - final String _prefsKeyLastUpdated = "nstack_last_updated"; final String _prefsKeyGuid = "nstack_guid"; @@ -25,14 +23,16 @@ class NStackSdk { final bool isDebug; final NStackLocalization localization; + final NStackMessages messages; var _appOpenCalled = false; NStackSdk({ - required this.config, + required NStackRepository repository, required this.isDebug, required this.localization, - }) : _repository = NStackRepository(config); + required this.messages, + }) : _repository = repository; Future _setupAppOpenData(AppOpenPlatform? platformOverride) async { WidgetsFlutterBinding.ensureInitialized(); @@ -119,6 +119,11 @@ class NStackSdk { await localization.updateOnAppOpen(appOpen); _log('NStack --> Updated localization.'); + messages.onAppOpen( + appOpen, + _appOpenData, + ); + _appOpenCalled = true; return AppOpenResult.success; diff --git a/lib/src/nstack_builder.dart b/lib/src/nstack_builder.dart index ceda1ff..45875eb 100644 --- a/lib/src/nstack_builder.dart +++ b/lib/src/nstack_builder.dart @@ -92,6 +92,8 @@ class NstackBuilder implements Builder { void _writeHeader(StringBuffer output) { output.writeln( ''' +// ignore_for_file: implementation_imports, non_constant_identifier_names, depend_on_referenced_packages + /* * ❌ GENERATED BY NSTACK, DO NOT MODIFY THIS FILE BY HAND! * @@ -116,16 +118,23 @@ class NstackBuilder implements Builder { */ import 'dart:async'; +import 'dart:io'; +import 'package:flutter/cupertino.dart' as cupertino; import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart' as material; import 'package:flutter/widgets.dart'; import 'package:nstack/models/app_open_platform.dart'; import 'package:nstack/models/language.dart'; import 'package:nstack/models/localize_index.dart'; +import 'package:nstack/models/message.dart'; import 'package:nstack/models/nstack_config.dart'; -import 'package:nstack/sdk/nstack_sdk.dart'; -import 'package:nstack/sdk/localization/nstack_localization.dart'; +import 'package:nstack/nstack.dart'; import 'package:nstack/partial/section_key_delegate.dart'; +import 'package:nstack/sdk/localization/nstack_localization.dart'; +import 'package:nstack/sdk/messages/nstack_messages.dart'; +import 'package:nstack/src/nstack_repository.dart'; +import 'package:url_launcher/url_launcher.dart'; export 'package:nstack/models/app_open_platform.dart'; ''', @@ -142,19 +151,26 @@ export 'package:nstack/models/app_open_platform.dart'; */ final NStack = NStackSdk( - config: _config, - localization: _nstackLocalization, + repository: _nstackRepository, isDebug: kDebugMode, + localization: _nstackLocalization, + messages: _nstackMessages, ); +const _nstackRepository = NStackRepository(_config); + final _nstackLocalization = NStackLocalization( - config: _config, + repository: _nstackRepository, translations: const Localization(), availableLanguages: _languages, bundledTranslations: _bundledTranslations, pickedLanguageLocale: '', isDebug: kDebugMode, ); + +final _nstackMessages = NStackMessages( + repository: _nstackRepository, +); '''); } @@ -297,6 +313,8 @@ const _bundledTranslations = {'''); * NStack Flutter Widgets * */ + +/// Allows to access NStack features via a `BuildContext`. class NStackScope extends InheritedWidget { final NStackState state; final String checksum; @@ -312,10 +330,27 @@ class NStackScope extends InheritedWidget { checksum != oldWidget.checksum; } +/// Widget that is used for accessing NStack features from the widget tree +/// & listening for localization changes. +/// +/// Is required for all the children widgets like [NStackMessageListener] +/// +/// In your app, use the `builder` property like this: +/// ```dart +/// MaterialApp( +/// ... +/// builder: (context, child) { +/// return NStackWidget( +/// child: child!, +/// ); +/// }, +/// ... +/// ); +/// ``` class NStackWidget extends StatefulWidget { - final Widget child; final AppOpenPlatform? platformOverride; final VoidCallback? onComplete; + final Widget child; const NStackWidget({Key? key, required this.child, this.platformOverride, this.onComplete,}) : super(key: key); @@ -332,6 +367,12 @@ class NStackState extends State { late final StreamSubscription _localeChangedSubscription; + /// Gets the NStack Localization feature configured for this project. + NStackLocalization get localization => _nstack.localization; + + /// Gets the NStack Message feature configured for this project. + NStackMessages get messages => _nstack.messages; + @override void initState() { super.initState(); @@ -383,6 +424,221 @@ class NStackState extends State { } } +/* + * + * NStack Messages Feature + * + */ + +/// Listens for new messages from the NStack Messages feature. +/// +/// In where you want to use it, add this widget: +/// ```dart +/// Widget build(BuildContext context) { +/// return NStackMessageListener( +/// onMessage: (Message message) { +/// // Do whatever you want with the received message. +/// // For example, use NStackMessageDialog to display the message. +/// showDialog( +/// context: context, +/// builder: (context) { +/// return NStackMessageDialog(message: message); +/// }, +/// }, +/// child: Scaffold(...), +/// ); +/// } +/// ``` +class NStackMessageListener extends StatefulWidget { + const NStackMessageListener({ + Key? key, + required this.onMessage, + this.child, + }) : super(key: key); + + final void Function(Message message) onMessage; + final Widget? child; + + @override + State createState() => _NStackMessageListenerState(); +} + +class _NStackMessageListenerState extends State { + late final StreamSubscription _subscription; + + @override + void initState() { + super.initState(); + + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + final scope = NStackScope.of(context); + _subscription = scope.messages.onMessage.listen(widget.onMessage); + }); + } + + @override + void dispose() { + _subscription.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return widget.child ?? const SizedBox(); + } +} + + /// Default Message Alert Dialog that adapts to the platform and renders the: + /// - Message body + /// - OK Button + /// - Open URL button (if [Message.url] is provided) + /// + /// When the dialog is dismissed, the alert reports that the message is viewed. + /// + /// Use it like this: + /// ```dart + /// NStackMessageDialog.show( + /// context, + /// message: message, + /// /* Other params if needed */ + /// ); + /// ``` +class NStackMessageDialog extends StatelessWidget { + static const _okButtonTitleFallback = 'OK'; + static const _openUrlButtonTitleFallback = 'Open URL'; + static const _dialogTitleFallback = 'Message'; + + const NStackMessageDialog._({ + Key? key, + required this.message, + this.onOkPressed, + this.onOpenUrlPressed, + this.okButtonTitle = _okButtonTitleFallback, + this.openUrlButtonTitle = _openUrlButtonTitleFallback, + this.dialogTitle = _dialogTitleFallback, + }) : super(key: key); + + /// Message that was received. + final Message message; + + /// Title of the OK button. + final String okButtonTitle; + + /// Title of the Open URL button. + final String openUrlButtonTitle; + + /// Title of the dialog. + final String? dialogTitle; + + /// Optional callback when a user presses the OK button. + /// + /// By default, it closes the dialog and reports that the message was viewed. + final VoidCallback? onOkPressed; + + /// Optional callback when a user presses Open URL button. + /// + /// By default, it closes the dialog, reports that the message was viewed + /// and opens the URL. + final void Function(Uri uri)? onOpenUrlPressed; + + /// Displays the dialog. + static Future show( + BuildContext context, { + required Message message, + VoidCallback? onOkPressed, + void Function(Uri uri)? onOpenUrlPressed, + String? okButtonTitle, + String? openUrlButtonTitle, + String? dialogTitle = _dialogTitleFallback, + }) { + Widget builder(BuildContext context) { + return NStackMessageDialog._( + message: message, + onOkPressed: onOkPressed, + onOpenUrlPressed: onOpenUrlPressed, + okButtonTitle: okButtonTitle ?? + message.localization?.okBtn ?? + _okButtonTitleFallback, + openUrlButtonTitle: openUrlButtonTitle ?? + message.localization?.urlBtn ?? + _openUrlButtonTitleFallback, + dialogTitle: dialogTitle, + ); + } + + final showDialog = Platform.isIOS + ? cupertino.showCupertinoDialog(context: context, builder: builder) + : material.showDialog(context: context, builder: builder); + + return showDialog.whenComplete(() { + NStack.messages.setMessageViewed(message.id); + }); + } + + @override + Widget build(BuildContext context) { + final titleWidget = dialogTitle != null ? Text(dialogTitle!) : null; + final messageWidget = Text(message.message); + + final okWidget = Text(okButtonTitle); + final okAction = onOkPressed ?? Navigator.of(context).pop; + + final messageUrl = message.url; + final Uri? uri; + + if (messageUrl != null) { + uri = Uri.tryParse(messageUrl); + } else { + uri = null; + } + + final isUriValid = uri != null; + + final urlLaunchAction = !isUriValid + ? null + : (onOpenUrlPressed != null ? () => onOpenUrlPressed!(uri!) : null) ?? + () { + launchUrl(uri!); + Navigator.of(context).pop(); + }; + + if (Platform.isIOS) { + return cupertino.CupertinoAlertDialog( + title: titleWidget, + content: messageWidget, + actions: [ + cupertino.CupertinoDialogAction( + onPressed: okAction, + child: okWidget, + ), + if (isUriValid) + cupertino.CupertinoDialogAction( + isDefaultAction: true, + onPressed: urlLaunchAction, + child: Text(openUrlButtonTitle), + ), + ], + ); + } + + return material.AlertDialog( + title: titleWidget, + content: messageWidget, + actions: [ + if (isUriValid) + material.TextButton( + onPressed: urlLaunchAction, + child: Text(openUrlButtonTitle), + ), + material.TextButton( + onPressed: okAction, + child: okWidget, + ), + ], + ); + } +} + /* * * NStack Flutter Extensions diff --git a/lib/src/nstack_repository.dart b/lib/src/nstack_repository.dart index b4653e1..aa05ecd 100644 --- a/lib/src/nstack_repository.dart +++ b/lib/src/nstack_repository.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:http/http.dart' as http; +import 'package:http/http.dart'; import 'package:nstack/models/app_open_platform.dart'; import 'package:nstack/models/localize_index.dart'; import 'package:nstack/models/nstack_appopen_data.dart'; @@ -88,4 +89,32 @@ class NStackRepository { ); return response.body; } + + Future postMessageSeen({ + required NStackAppOpenData appOpenData, + required int messageId, + }) async { + final requestBody = { + 'guid': appOpenData.guid, + 'message_id': messageId.toString(), + }; + + final url = Uri.parse('$_baseUrl/notify/messages/views'); + + final response = await http.post( + url, + headers: _headers, + body: requestBody, + ); + + if (response.statusCode != 200) { + throw NStackApiException(response); + } + } +} + +class NStackApiException { + final Response response; + + NStackApiException(this.response); } diff --git a/pubspec.yaml b/pubspec.yaml index 863dd53..34c1bb2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,6 +19,7 @@ dependencies: uuid: ^3.0.4 dart_style: ^2.0.2 collection: ^1.15.0 + url_launcher: ^6.0.10 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec