From 99f91c044956e950f44a08ebcd479b0232847df8 Mon Sep 17 00:00:00 2001 From: Pritam Biswas Date: Mon, 13 May 2024 18:02:41 +0200 Subject: [PATCH 1/4] - Rate Reminder implementation - Both default and custom configuration strategies - Documentation for Rate Reminder usage - Example for Rate Reminder - Some clean-up in Message and Version-Control - Fix the template for enabling the test mode --- example/lib/nstack.dart | 50 +++++ example/lib/routes/routes.dart | 1 + example/lib/routes/routes.gr.dart | 65 ++++--- example/lib/screens/main_screen.dart | 8 + .../lib/screens/rate_reminder_example.dart | 54 ++++++ lib/src/models/app_open_data.dart | 6 - .../models/nstack_rate_reminder_answer.dart | 5 + lib/src/models/rate_reminder.dart | 60 ++++-- lib/src/repository/nstack_repository.dart | 114 +++++++---- lib/src/sdk/nstack_features.dart | 29 +++ lib/src/sdk/nstack_sdk.dart | 9 + .../rate_reminders/nstack_rate_reminders.dart | 64 +++++++ .../nstack_feature_handler_widget.dart | 7 + .../sdk/widgets/nstack_message_widget.dart | 8 - .../widgets/nstack_rate_reminder_widget.dart | 177 ++++++++++++++++++ .../nstack_version_control_widget.dart | 8 - lib/templates/nstack_template.txt | 54 +++++- 17 files changed, 622 insertions(+), 97 deletions(-) create mode 100644 example/lib/screens/rate_reminder_example.dart create mode 100644 lib/src/models/nstack_rate_reminder_answer.dart create mode 100644 lib/src/sdk/rate_reminders/nstack_rate_reminders.dart create mode 100644 lib/src/sdk/widgets/nstack_rate_reminder_widget.dart diff --git a/example/lib/nstack.dart b/example/lib/nstack.dart index 0f1fafe..90f6974 100644 --- a/example/lib/nstack.dart +++ b/example/lib/nstack.dart @@ -127,6 +127,55 @@ * `NStackFeatureHandlerWidget` with the `NStackVersionUpdateHandler` config, * and you will get the version update data only once for an app life cycle. * + * ⭐ Rate Reminder + * + * Rate reminders ask the user to rate the app on the platform-specific store once the amount of points from events reaches a certain threshold. + * Use `NStackFeatureHandlerWidget` with `NStackRateReminderHandler` config to integrate the rate reminder feature within your Flutter app. + * And use `postRateReminderEvent`API from `NStackRateReminders` to post points to the backend. + * This setup facilitates different strategies for handling the Rate Reminder + * + * - Default Adaptive Dialog: + * By default, `NStackRateReminderHandler.config()` with `void Function(NStackRateReminderAnswer)` callback is designed to automatically handle rate reminder. + * The callback will provide the user's answer. + * Example: + * + * ```dart + * NStackRateReminderHandler.config( + * onRateReminderAnswered: (rateReminderAnswer) { + * + * }, + * ) + * ``` + * + * - Custom Handling: + * By passing a `void Function(NStackRateReminder)` callback to `NStackRateReminderHandler.config()`, + * you gain control over how the rate reminder alert is presented to the user. In this case, passing the + * `void Function(NStackRateReminderAnswer)` callback is unnecessary. + * Example: + * + * ```dart + * NStackRateReminderHandler.config( + * onRateReminder: (rateReminderInfo) { + * + * }, + * ) + * ``` + * + * Or, if you don't want to use the `NStackFeatureHandlerWidget`, + * you can use the `getRateReminderInfo`, `postRateReminderEvent` and `postRateReminderAnswer` from `NStackRateReminders`. + * Example: + * + * ```dart + * WidgetsBinding.instance.addPostFrameCallback( + * (timeStamp) async { + * final rateReminderInfo = + * await context.nstack.rateReminders.getRateReminderInfo( + * defaultLocale: Localizations.localeOf(context), + * ); + * }, + * ); + * ``` + * * 🛠️ IMPORTANT NOTES FOR SDK USERS * * The default environment for the NStack SDK is `prod`. @@ -151,6 +200,7 @@ import 'package:nstack/src/sdk/localization/section_key_delegate.dart'; import 'package:nstack/src/sdk/widgets/nstack_base_widget.dart'; export 'package:nstack/src/models/app_open_platform.dart'; +export 'package:nstack/src/models/nstack_rate_reminder_answer.dart'; export 'package:nstack/src/models/nstack_version_update_view_request.dart' show NStackVersionUpdateViewAnswer, NStackVersionUpdateViewType; export 'package:nstack/src/sdk/extensions/nstack_widget_extension.dart'; diff --git a/example/lib/routes/routes.dart b/example/lib/routes/routes.dart index 11a08ba..5b46aed 100644 --- a/example/lib/routes/routes.dart +++ b/example/lib/routes/routes.dart @@ -16,5 +16,6 @@ class AppRouter extends $AppRouter { AutoRoute(page: MessageExampleRoute.page), AutoRoute(page: VersionUpdateExampleRoute.page), AutoRoute(page: CombineExampleRoute.page), + AutoRoute(page: RateReminderExampleRoute.page), ]; } diff --git a/example/lib/routes/routes.gr.dart b/example/lib/routes/routes.gr.dart index 0bbef2d..1d88cb3 100644 --- a/example/lib/routes/routes.gr.dart +++ b/example/lib/routes/routes.gr.dart @@ -8,39 +8,46 @@ // coverage:ignore-file // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:auto_route/auto_route.dart' as _i5; +import 'package:auto_route/auto_route.dart' as _i6; import 'package:example/screens/combine_example_screen.dart' as _i1; import 'package:example/screens/main_screen.dart' as _i2; import 'package:example/screens/message_example_screen.dart' as _i3; -import 'package:example/screens/version_update_example_screen.dart' as _i4; +import 'package:example/screens/rate_reminder_example.dart' as _i4; +import 'package:example/screens/version_update_example_screen.dart' as _i5; -abstract class $AppRouter extends _i5.RootStackRouter { +abstract class $AppRouter extends _i6.RootStackRouter { $AppRouter({super.navigatorKey}); @override - final Map pagesMap = { + final Map pagesMap = { CombineExampleRoute.name: (routeData) { - return _i5.AutoRoutePage( + return _i6.AutoRoutePage( routeData: routeData, child: const _i1.CombineExampleScreen(), ); }, MainRoute.name: (routeData) { - return _i5.AutoRoutePage( + return _i6.AutoRoutePage( routeData: routeData, child: const _i2.MainScreen(), ); }, MessageExampleRoute.name: (routeData) { - return _i5.AutoRoutePage( + return _i6.AutoRoutePage( routeData: routeData, child: const _i3.MessageExampleScreen(), ); }, + RateReminderExampleRoute.name: (routeData) { + return _i6.AutoRoutePage( + routeData: routeData, + child: const _i4.RateReminderExampleScreen(), + ); + }, VersionUpdateExampleRoute.name: (routeData) { - return _i5.AutoRoutePage( + return _i6.AutoRoutePage( routeData: routeData, - child: const _i4.VersionUpdateExampleScreen(), + child: const _i5.VersionUpdateExampleScreen(), ); }, }; @@ -48,8 +55,8 @@ abstract class $AppRouter extends _i5.RootStackRouter { /// generated route for /// [_i1.CombineExampleScreen] -class CombineExampleRoute extends _i5.PageRouteInfo { - const CombineExampleRoute({List<_i5.PageRouteInfo>? children}) +class CombineExampleRoute extends _i6.PageRouteInfo { + const CombineExampleRoute({List<_i6.PageRouteInfo>? children}) : super( CombineExampleRoute.name, initialChildren: children, @@ -57,13 +64,13 @@ class CombineExampleRoute extends _i5.PageRouteInfo { static const String name = 'CombineExampleRoute'; - static const _i5.PageInfo page = _i5.PageInfo(name); + static const _i6.PageInfo page = _i6.PageInfo(name); } /// generated route for /// [_i2.MainScreen] -class MainRoute extends _i5.PageRouteInfo { - const MainRoute({List<_i5.PageRouteInfo>? children}) +class MainRoute extends _i6.PageRouteInfo { + const MainRoute({List<_i6.PageRouteInfo>? children}) : super( MainRoute.name, initialChildren: children, @@ -71,13 +78,13 @@ class MainRoute extends _i5.PageRouteInfo { static const String name = 'MainRoute'; - static const _i5.PageInfo page = _i5.PageInfo(name); + static const _i6.PageInfo page = _i6.PageInfo(name); } /// generated route for /// [_i3.MessageExampleScreen] -class MessageExampleRoute extends _i5.PageRouteInfo { - const MessageExampleRoute({List<_i5.PageRouteInfo>? children}) +class MessageExampleRoute extends _i6.PageRouteInfo { + const MessageExampleRoute({List<_i6.PageRouteInfo>? children}) : super( MessageExampleRoute.name, initialChildren: children, @@ -85,13 +92,27 @@ class MessageExampleRoute extends _i5.PageRouteInfo { static const String name = 'MessageExampleRoute'; - static const _i5.PageInfo page = _i5.PageInfo(name); + static const _i6.PageInfo page = _i6.PageInfo(name); +} + +/// generated route for +/// [_i4.RateReminderExampleScreen] +class RateReminderExampleRoute extends _i6.PageRouteInfo { + const RateReminderExampleRoute({List<_i6.PageRouteInfo>? children}) + : super( + RateReminderExampleRoute.name, + initialChildren: children, + ); + + static const String name = 'RateReminderExampleRoute'; + + static const _i6.PageInfo page = _i6.PageInfo(name); } /// generated route for -/// [_i4.VersionUpdateExampleScreen] -class VersionUpdateExampleRoute extends _i5.PageRouteInfo { - const VersionUpdateExampleRoute({List<_i5.PageRouteInfo>? children}) +/// [_i5.VersionUpdateExampleScreen] +class VersionUpdateExampleRoute extends _i6.PageRouteInfo { + const VersionUpdateExampleRoute({List<_i6.PageRouteInfo>? children}) : super( VersionUpdateExampleRoute.name, initialChildren: children, @@ -99,5 +120,5 @@ class VersionUpdateExampleRoute extends _i5.PageRouteInfo { static const String name = 'VersionUpdateExampleRoute'; - static const _i5.PageInfo page = _i5.PageInfo(name); + static const _i6.PageInfo page = _i6.PageInfo(name); } diff --git a/example/lib/screens/main_screen.dart b/example/lib/screens/main_screen.dart index 76dee6f..3d2feff 100644 --- a/example/lib/screens/main_screen.dart +++ b/example/lib/screens/main_screen.dart @@ -54,6 +54,14 @@ class MainScreen extends StatelessWidget { 'Combine Example', ), ), + MaterialButton( + onPressed: () => context.pushRoute( + const RateReminderExampleRoute(), + ), + child: const Text( + 'Rate Reminder Example', + ), + ), ], ), ), diff --git a/example/lib/screens/rate_reminder_example.dart b/example/lib/screens/rate_reminder_example.dart new file mode 100644 index 0000000..8096f26 --- /dev/null +++ b/example/lib/screens/rate_reminder_example.dart @@ -0,0 +1,54 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:example/nstack.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +@RoutePage() +class RateReminderExampleScreen extends StatefulWidget { + const RateReminderExampleScreen({super.key}); + + @override + State createState() => + _RateReminderExampleScreenState(); +} + +class _RateReminderExampleScreenState extends State { + @override + Widget build(BuildContext context) { + final localizationAsset = context.localizationAssest; + + return NStackFeatureHandlerWidget( + features: [ + NStackRateReminderHandler.config( + onRateReminderAnswered: (rateReminderAnswer) { + if (kDebugMode) { + print(rateReminderAnswer); + } + }, + ), + ], + child: Scaffold( + appBar: AppBar( + title: Text(localizationAsset.test.testDollarSign), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('Rate Reminder Example'), + MaterialButton( + onPressed: () async { + await context.nstack.rateReminders + .postRateReminderEvent(action: 'button-tapped'); + }, + child: const Text( + 'Trigger Points', + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/src/models/app_open_data.dart b/lib/src/models/app_open_data.dart index 286c31e..f4be3c2 100644 --- a/lib/src/models/app_open_data.dart +++ b/lib/src/models/app_open_data.dart @@ -1,7 +1,6 @@ import 'package:nstack/src/models/localize_index.dart'; import 'package:nstack/src/models/nstack_message.dart'; import 'package:nstack/src/models/nstack_version_update.dart'; -import 'package:nstack/src/models/rate_reminder.dart'; import 'package:nstack/src/models/terms.dart'; import 'package:nstack/src/other/extensions.dart'; @@ -13,7 +12,6 @@ class AppOpenData { final DateTime? createdAt; final DateTime? updatedAt; final NStackMessage? message; - final RateReminder? rateReminder; final List? terms; AppOpenData({ @@ -24,7 +22,6 @@ class AppOpenData { required this.createdAt, required this.updatedAt, required this.message, - required this.rateReminder, required this.terms, }); @@ -48,9 +45,6 @@ class AppOpenData { DateTime.parse, ), message: (json['message'] as Map?)?.let(NStackMessage.fromJson), - rateReminder: json['rateReminder']?.let( - RateReminder.fromJson, - ), terms: json['terms']?.let((item) => item), ); } diff --git a/lib/src/models/nstack_rate_reminder_answer.dart b/lib/src/models/nstack_rate_reminder_answer.dart new file mode 100644 index 0000000..0d138b1 --- /dev/null +++ b/lib/src/models/nstack_rate_reminder_answer.dart @@ -0,0 +1,5 @@ +enum NStackRateReminderAnswer { + positive, + negative, + skip, +} diff --git a/lib/src/models/rate_reminder.dart b/lib/src/models/rate_reminder.dart index b0a53cf..dfdc8eb 100644 --- a/lib/src/models/rate_reminder.dart +++ b/lib/src/models/rate_reminder.dart @@ -1,28 +1,52 @@ -class RateReminder { - final String? title; - final String? body; - final String? yesButton; - final String? laterButton; - final String? noButton; - final String? link; +class NStackRateReminder { + final int id; + final int points_to_trigger; + final int days_delay_on_skip; + final Localization? localization; + final int points; - RateReminder({ + NStackRateReminder({ + required this.id, + required this.points_to_trigger, + required this.days_delay_on_skip, + required this.localization, + required this.points, + }); + + factory NStackRateReminder.fromJson(Map json) { + return NStackRateReminder( + id: json['id'], + points_to_trigger: json['points_to_trigger'], + days_delay_on_skip: json['days_delay_on_skip'], + localization: + Localization.fromJson(json['localization'] as Map), + points: json['points'], + ); + } +} + +class Localization { + final String title; + final String body; + final String yesBtn; + final String laterBtn; + final String noBtn; + + Localization({ required this.title, required this.body, - required this.yesButton, - required this.laterButton, - required this.noButton, - required this.link, + required this.yesBtn, + required this.laterBtn, + required this.noBtn, }); - factory RateReminder.fromJson(Map json) { - return RateReminder( + factory Localization.fromJson(Map json) { + return Localization( title: json['title'], body: json['body'], - yesButton: json['yesButton'], - laterButton: json['laterButton'], - noButton: json['noButton'], - link: json['link'], + yesBtn: json['yesBtn'], + laterBtn: json['laterBtn'], + noBtn: json['noBtn'], ); } } diff --git a/lib/src/repository/nstack_repository.dart b/lib/src/repository/nstack_repository.dart index 626452f..46c38b0 100644 --- a/lib/src/repository/nstack_repository.dart +++ b/lib/src/repository/nstack_repository.dart @@ -7,9 +7,9 @@ import 'package:nstack/src/models/app_open_platform.dart'; import 'package:nstack/src/models/localize_index.dart'; import 'package:nstack/src/models/nstack_app_info.dart'; import 'package:nstack/src/models/nstack_config.dart'; -import 'package:nstack/src/models/nstack_message.dart'; import 'package:nstack/src/models/nstack_version_update.dart'; import 'package:nstack/src/models/nstack_version_update_view_request.dart'; +import 'package:nstack/src/models/rate_reminder.dart'; import 'package:nstack/src/utils/log_util.dart'; class NStackRepository { @@ -101,39 +101,6 @@ class NStackRepository { return response.body; } - Future getMessage({ - required String acceptHeader, - required NStackAppInfo appInfoData, - required bool devMode, - required bool testMode, - }) async { - var mutableHeaders = {..._headers}; - mutableHeaders['Accept-Language'] = acceptHeader; - - final requestBody = { - 'platform': appInfoData.platform.slug, - 'guid': appInfoData.guid, - 'version': appInfoData.version, - 'old_version': appInfoData.oldVersion, - 'last_updated': appInfoData.lastUpdated, - }; - - try { - final appOpenResponse = await http.post( - Uri.parse('$_baseUrl/open?dev=$devMode&test=$testMode'), - headers: mutableHeaders, - body: requestBody, - ); - - final result = json.decode(appOpenResponse.body); - final appOpen = AppOpen.fromJson(result); - return appOpen.data.message; - } catch (e) { - LogUtil.log(e); - return null; - } - } - Future postMessageSeen({ required NStackAppInfo appInfoData, required int messageId, @@ -203,4 +170,83 @@ class NStackRepository { throw NStackException.updateFailed('Failed to update change log seen.'); } } + + Future getRateReminderInfo({ + required String acceptHeader, + required NStackAppInfo appInfoData, + }) async { + var mutableHeaders = {..._headers}; + mutableHeaders['Accept-Language'] = acceptHeader; + try { + final rateReminderResponse = await http.get( + Uri.parse( + '$_baseUrl/notify/rate_reminder_v2?guid=${appInfoData.guid}', + ), + headers: mutableHeaders, + ); + + final result = json.decode(rateReminderResponse.body); + + if (rateReminderResponse.statusCode == 445) { + LogUtil.log('Rate Reminder message: ${result['message']}'); + return null; + } + + final rateReminde = NStackRateReminder.fromJson(result['data']); + return rateReminde; + } catch (e) { + LogUtil.log(e); + return null; + } + } + + Future postRateReminderEvent({ + required NStackAppInfo appInfoData, + required String action, + }) async { + final url = Uri.parse('$_baseUrl/notify/rate_reminder_v2/events'); + + final requestBody = { + 'guid': appInfoData.guid, + 'action': action, + }; + + final response = await http.post( + url, + headers: _headers, + body: requestBody, + ); + + if (response.statusCode != 201) { + throw NStackException.updateFailed( + 'Failed to update rate reminder action.', + ); + } + } + + Future postRateReminderAnswer({ + required NStackAppInfo appInfoData, + required String answer, + required int rateReminderId, + }) async { + final url = + Uri.parse('$_baseUrl/notify/rate_reminder_v2/$rateReminderId/answers'); + + final requestBody = { + 'guid': appInfoData.guid, + 'answer': answer, + }; + + final response = await http.post( + url, + headers: _headers, + body: requestBody, + ); + + if (response.statusCode != 201) { + throw NStackException.updateFailed( + 'Failed to update rate reminder answer.', + ); + } + } } diff --git a/lib/src/sdk/nstack_features.dart b/lib/src/sdk/nstack_features.dart index 217aa4b..583034b 100644 --- a/lib/src/sdk/nstack_features.dart +++ b/lib/src/sdk/nstack_features.dart @@ -1,5 +1,7 @@ import 'package:nstack/src/models/nstack_message.dart'; +import 'package:nstack/src/models/nstack_rate_reminder_answer.dart'; import 'package:nstack/src/models/nstack_version_update.dart'; +import 'package:nstack/src/models/rate_reminder.dart'; sealed class NStackFeatureHandler {} @@ -8,6 +10,14 @@ typedef OnVersionUpdateNotification = void Function( NStackVersionUpdate? updateInfo, )?; +typedef OnRateReminder = void Function( + NStackRateReminder? rateReminderInfo, +)?; + +typedef OnRateReminderAnswered = void Function( + NStackRateReminderAnswer? rateReminderAnswer, +)?; + final class NStackMessageHandler implements NStackFeatureHandler { NStackMessageHandler._({this.onMessage}); factory NStackMessageHandler.config({OnMessage? onMessage}) => @@ -27,3 +37,22 @@ final class NStackVersionUpdateHandler implements NStackFeatureHandler { final OnVersionUpdateNotification? onVersionUpdateNotification; } + +final class NStackRateReminderHandler implements NStackFeatureHandler { + NStackRateReminderHandler._({ + this.onRateReminder, + this.onRateReminderAnswered, + }); + + factory NStackRateReminderHandler.config({ + OnRateReminder? onRateReminder, + OnRateReminderAnswered? onRateReminderAnswered, + }) => + NStackRateReminderHandler._( + onRateReminder: onRateReminder, + onRateReminderAnswered: onRateReminderAnswered, + ); + + final OnRateReminder? onRateReminder; + final OnRateReminderAnswered? onRateReminderAnswered; +} diff --git a/lib/src/sdk/nstack_sdk.dart b/lib/src/sdk/nstack_sdk.dart index 9510ae9..35fb29d 100644 --- a/lib/src/sdk/nstack_sdk.dart +++ b/lib/src/sdk/nstack_sdk.dart @@ -10,6 +10,7 @@ import 'package:nstack/src/repository/nstack_localization_repository.dart'; import 'package:nstack/src/repository/nstack_repository.dart'; import 'package:nstack/src/sdk/localization/nstack_localization.dart'; import 'package:nstack/src/sdk/messages/nstack_messages.dart'; +import 'package:nstack/src/sdk/rate_reminders/nstack_rate_reminders.dart'; import 'package:nstack/src/sdk/version_control/nstack_version_control.dart'; import 'package:nstack/src/utils/log_util.dart'; import 'package:package_info/package_info.dart'; @@ -40,6 +41,7 @@ class NStackSdk { final NStackLocalization localization; late final NStackMessages messages; late final NStackVersionControl appVersionControl; + late final NStackRateReminders rateReminders; var _appOpenCalled = false; @@ -57,6 +59,13 @@ class NStackSdk { ); final localizationInit = await localization.init(); + + rateReminders = NStackRateReminders( + repository: _repository, + appInfoData: _appInfoData, + localization: localization, + ); + return localizationInit; } diff --git a/lib/src/sdk/rate_reminders/nstack_rate_reminders.dart b/lib/src/sdk/rate_reminders/nstack_rate_reminders.dart new file mode 100644 index 0000000..6baacd0 --- /dev/null +++ b/lib/src/sdk/rate_reminders/nstack_rate_reminders.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; +import 'package:nstack/src/models/nstack_app_info.dart'; +import 'package:nstack/src/models/nstack_rate_reminder_answer.dart'; +import 'package:nstack/src/models/rate_reminder.dart'; +import 'package:nstack/src/repository/nstack_repository.dart'; +import 'package:nstack/src/sdk/localization/nstack_localization.dart'; +import 'package:nstack/src/utils/log_util.dart'; + +class NStackRateReminders { + NStackRateReminders({ + required NStackRepository repository, + required NStackAppInfo appInfoData, + required NStackLocalization localization, + }) : _repository = repository, + _appInfoData = appInfoData, + _localization = localization; + + final NStackRepository _repository; + final NStackAppInfo _appInfoData; + final NStackLocalization _localization; + + Future getRateReminderInfo({ + required Locale defaultLocale, + }) async { + final selectedLanguageTag = + await _localization.getUserSelectedLanguageTag() ?? + defaultLocale.toLanguageTag(); + + final result = _repository.getRateReminderInfo( + acceptHeader: selectedLanguageTag, + appInfoData: _appInfoData, + ); + + return result; + } + + Future postRateReminderEvent({ + required String action, + }) async { + try { + await _repository.postRateReminderEvent( + appInfoData: _appInfoData, + action: action, + ); + } catch (e) { + LogUtil.log('Could not post rate reminder event.'); + } + } + + Future postRateReminderAnswer({ + required NStackRateReminderAnswer answer, + required int rateReminderId, + }) async { + try { + await _repository.postRateReminderAnswer( + appInfoData: _appInfoData, + answer: answer.name, + rateReminderId: rateReminderId, + ); + } catch (e) { + LogUtil.log('Could not post rate reminder answer.'); + } + } +} diff --git a/lib/src/sdk/widgets/nstack_feature_handler_widget.dart b/lib/src/sdk/widgets/nstack_feature_handler_widget.dart index e31a6b2..540c1a6 100644 --- a/lib/src/sdk/widgets/nstack_feature_handler_widget.dart +++ b/lib/src/sdk/widgets/nstack_feature_handler_widget.dart @@ -2,6 +2,7 @@ import 'package:flutter/widgets.dart'; import 'package:nstack/src/sdk/nstack_features.dart'; import 'package:nstack/src/sdk/widgets/nstack_base_widget.dart'; import 'package:nstack/src/sdk/widgets/nstack_message_widget.dart'; +import 'package:nstack/src/sdk/widgets/nstack_rate_reminder_widget.dart'; import 'package:nstack/src/sdk/widgets/nstack_version_control_widget.dart'; class NStackFeatureHandlerWidget extends StatefulWidget { @@ -43,6 +44,12 @@ class _NStackFeatureHandlerState extends State { onVersionUpdateNotification: feature.onVersionUpdateNotification, child: child, ); + case NStackRateReminderHandler(): + child = NStackRateReminderWidget( + onRateReminder: feature.onRateReminder, + onAnswered: feature.onRateReminderAnswered, + child: child, + ); } } return child; diff --git a/lib/src/sdk/widgets/nstack_message_widget.dart b/lib/src/sdk/widgets/nstack_message_widget.dart index dca65b4..918e1a3 100644 --- a/lib/src/sdk/widgets/nstack_message_widget.dart +++ b/lib/src/sdk/widgets/nstack_message_widget.dart @@ -28,8 +28,6 @@ class NStackMessageWidget extends StatefulWidget { } class _NStackMessageWidgetSate extends State { - StreamSubscription? _messageSubscription; - @override void didChangeDependencies() { super.didChangeDependencies(); @@ -42,12 +40,6 @@ class _NStackMessageWidgetSate extends State { ); } - @override - void dispose() { - _messageSubscription?.cancel(); - super.dispose(); - } - void _onMessage(NStackMessage? message) { if (widget.onMessage != null) { widget.onMessage!(message); diff --git a/lib/src/sdk/widgets/nstack_rate_reminder_widget.dart b/lib/src/sdk/widgets/nstack_rate_reminder_widget.dart new file mode 100644 index 0000000..e12a608 --- /dev/null +++ b/lib/src/sdk/widgets/nstack_rate_reminder_widget.dart @@ -0,0 +1,177 @@ +import 'package:flutter/material.dart'; +import 'package:nstack/src/models/nstack_rate_reminder_answer.dart'; +import 'package:nstack/src/models/rate_reminder.dart'; +import 'package:nstack/src/sdk/extensions/nstack_widget_extension.dart'; +import 'package:nstack/src/sdk/widgets/adaptive_dialog_action_widget.dart'; +import 'package:nstack/src/utils/log_util.dart'; + +class NStackRateReminderWidget extends StatefulWidget { + const NStackRateReminderWidget({ + super.key, + this.child, + this.onRateReminder, + this.onAnswered, + }) : assert( + onRateReminder != null || onAnswered != null, + 'If onRateReminder is null, then onAnswered must not be null.', + ); + + final Widget? child; + final void Function(NStackRateReminder?)? onRateReminder; + final void Function(NStackRateReminderAnswer)? onAnswered; + + @override + State createState() => _NStackRateReminderWidgetSate(); +} + +class _NStackRateReminderWidgetSate extends State { + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + WidgetsBinding.instance.addPostFrameCallback( + (timeStamp) async { + final rateReminderInfo = + await context.nstack.rateReminders.getRateReminderInfo( + defaultLocale: Localizations.localeOf(context), + ); + _onRateReminder(rateReminderInfo); + }, + ); + } + + void _onRateReminder(NStackRateReminder? rateReminder) { + if (widget.onRateReminder != null) { + widget.onRateReminder!(rateReminder); + return; + } + if (rateReminder != null && widget.onAnswered != null) { + _NStackRateReminderDialog.show( + context, + rateReminder: rateReminder, + onAnswered: widget.onAnswered!, + ); + } + } + + @override + Widget build(BuildContext context) { + return widget.child ?? const SizedBox(); + } +} + +class _NStackRateReminderDialog extends StatelessWidget { + const _NStackRateReminderDialog._({ + Key? key, + required this.rateReminder, + required this.onAnswered, + }) : super(key: key); + + /// Fallback titles + final _titleFallback = 'Enjoying the App?'; + final _bodyFallback = + 'We hope you are loving our app! If you have a moment, please rate us.'; + final _yesButtonTitleFallback = 'Yes'; + final _noButtonTitleFallback = 'No'; + final _laterButtonTitleFallback = 'Later'; + + /// Rate Reminder info that was received. + final NStackRateReminder rateReminder; + + /// Callback for the answer + final void Function(NStackRateReminderAnswer)? onAnswered; + + /// Displays the dialog. + static Future show( + BuildContext context, { + required NStackRateReminder rateReminder, + required Function(NStackRateReminderAnswer) onAnswered, + }) { + Widget builder(BuildContext context) { + return _NStackRateReminderDialog._( + rateReminder: rateReminder, + onAnswered: onAnswered, + ); + } + + return showAdaptiveDialog( + context: context, + builder: builder, + ); + } + + @override + Widget build(BuildContext context) { + final messageTitleWidget = + Text(rateReminder.localization?.title ?? _titleFallback); + + final messageBodyWidget = + Text(rateReminder.localization?.body ?? _bodyFallback); + + final yesButtonTitleWidget = Text( + rateReminder.localization?.yesBtn ?? _yesButtonTitleFallback, + ); + final noButtonTitleWidget = Text( + rateReminder.localization?.noBtn ?? _noButtonTitleFallback, + ); + + final laterButtonTitleWidget = Text( + rateReminder.localization?.laterBtn ?? _laterButtonTitleFallback, + ); + + Future buttonAction( + NStackRateReminderAnswer answer, + int rateReminderId, + ) async { + try { + await context.nstack.rateReminders.postRateReminderAnswer( + answer: answer, + rateReminderId: rateReminderId, + ); + } catch (e) { + LogUtil.log( + 'Filed to post answer', + 'NStackRateReminder', + ); + } + + if (context.mounted) { + Navigator.of(context).pop(); + } + } + + return AlertDialog.adaptive( + title: messageTitleWidget, + content: messageBodyWidget, + actions: [ + AdaptiveDialogAction( + onPressed: () async { + await buttonAction( + NStackRateReminderAnswer.positive, + rateReminder.id, + ); + }, + child: yesButtonTitleWidget, + ), + AdaptiveDialogAction( + onPressed: () async { + await buttonAction( + NStackRateReminderAnswer.negative, + rateReminder.id, + ); + }, + child: noButtonTitleWidget, + ), + AdaptiveDialogAction( + onPressed: () async { + await buttonAction( + NStackRateReminderAnswer.skip, + rateReminder.id, + ); + }, + child: laterButtonTitleWidget, + ), + ], + ); + } +} diff --git a/lib/src/sdk/widgets/nstack_version_control_widget.dart b/lib/src/sdk/widgets/nstack_version_control_widget.dart index 7b80e94..ac26789 100644 --- a/lib/src/sdk/widgets/nstack_version_control_widget.dart +++ b/lib/src/sdk/widgets/nstack_version_control_widget.dart @@ -30,8 +30,6 @@ class NStackVersionControlWidget extends StatefulWidget { class _NStackVersionControlWidgetSate extends State { - StreamSubscription? _versionInfoSubscription; - @override void didChangeDependencies() { super.didChangeDependencies(); @@ -44,12 +42,6 @@ class _NStackVersionControlWidgetSate ); } - @override - void dispose() { - _versionInfoSubscription?.cancel(); - super.dispose(); - } - void _onVersionUpdateNotification(NStackVersionUpdate? updateInfo) { if (widget.onVersionUpdateNotification != null) { widget.onVersionUpdateNotification!(updateInfo); diff --git a/lib/templates/nstack_template.txt b/lib/templates/nstack_template.txt index 8d5a5bf..940a205 100644 --- a/lib/templates/nstack_template.txt +++ b/lib/templates/nstack_template.txt @@ -127,6 +127,55 @@ * `NStackFeatureHandlerWidget` with the `NStackVersionUpdateHandler` config, * and you will get the version update data only once for an app life cycle. * + * ⭐ Rate Reminder + * + * Rate reminders ask the user to rate the app on the platform-specific store once the amount of points from events reaches a certain threshold. + * Use `NStackFeatureHandlerWidget` with `NStackRateReminderHandler` config to integrate the rate reminder feature within your Flutter app. + * And use `postRateReminderEvent`API from `NStackRateReminders` to post points to the backend. + * This setup facilitates different strategies for handling the Rate Reminder + * + * - Default Adaptive Dialog: + * By default, `NStackRateReminderHandler.config()` with `void Function(NStackRateReminderAnswer)` callback is designed to automatically handle rate reminder. + * The callback will provide the user's answer. + * Example: + * + * ```dart + * NStackRateReminderHandler.config( + * onRateReminderAnswered: (rateReminderAnswer) { + * + * }, + * ) + * ``` + * + * - Custom Handling: + * By passing a `void Function(NStackRateReminder)` callback to `NStackRateReminderHandler.config()`, + * you gain control over how the rate reminder alert is presented to the user. In this case, passing the + * `void Function(NStackRateReminderAnswer)` callback is unnecessary. + * Example: + * + * ```dart + * NStackRateReminderHandler.config( + * onRateReminder: (rateReminderInfo) { + * + * }, + * ) + * ``` + * + * Or, if you don't want to use the `NStackFeatureHandlerWidget`, + * you can use the `getRateReminderInfo`, `postRateReminderEvent` and `postRateReminderAnswer` from `NStackRateReminders`. + * Example: + * + * ```dart + * WidgetsBinding.instance.addPostFrameCallback( + * (timeStamp) async { + * final rateReminderInfo = + * await context.nstack.rateReminders.getRateReminderInfo( + * defaultLocale: Localizations.localeOf(context), + * ); + * }, + * ); + * ``` + * * 🛠️ IMPORTANT NOTES FOR SDK USERS * * The default environment for the NStack SDK is `prod`. @@ -151,6 +200,7 @@ import 'package:nstack/src/sdk/localization/section_key_delegate.dart'; import 'package:nstack/src/sdk/widgets/nstack_base_widget.dart'; export 'package:nstack/src/models/app_open_platform.dart'; +export 'package:nstack/src/models/nstack_rate_reminder_answer.dart'; export 'package:nstack/src/models/nstack_version_update_view_request.dart' show NStackVersionUpdateViewAnswer, NStackVersionUpdateViewType; export 'package:nstack/src/sdk/extensions/nstack_widget_extension.dart'; @@ -199,18 +249,20 @@ class NStackLocalizationAsset { * */ - class NStackWidget extends NStackBaseWidget { +class NStackWidget extends NStackBaseWidget { NStackWidget({ Key? key, required Widget child, AppOpenPlatform? platformOverride, VoidCallback? onComplete, + bool? testMode, }) : super( key: key, child: child, platformOverride: platformOverride, onComplete: onComplete, config: config, + testMode: testMode ?? false, localization: NStackLocalization( config: config, bundledLocalization: BundledLocalizationImpl.data(), From 65f75b1620e8eeaa492bb4f94061707da1603ce7 Mon Sep 17 00:00:00 2001 From: Pritam Biswas Date: Tue, 14 May 2024 14:36:06 +0200 Subject: [PATCH 2/4] - snake case to camel case - Remane Localization for Rate Reminder - method in the type def made not nullable --- lib/src/models/rate_reminder.dart | 27 ++++++++++--------- lib/src/sdk/nstack_features.dart | 10 +++---- .../widgets/nstack_rate_reminder_widget.dart | 3 +++ 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/lib/src/models/rate_reminder.dart b/lib/src/models/rate_reminder.dart index dfdc8eb..c3ad851 100644 --- a/lib/src/models/rate_reminder.dart +++ b/lib/src/models/rate_reminder.dart @@ -1,14 +1,14 @@ class NStackRateReminder { final int id; - final int points_to_trigger; - final int days_delay_on_skip; - final Localization? localization; + final int pointsToTrigger; + final int daysDelayOnSkip; + final RateReminderLocalization? localization; final int points; NStackRateReminder({ required this.id, - required this.points_to_trigger, - required this.days_delay_on_skip, + required this.pointsToTrigger, + required this.daysDelayOnSkip, required this.localization, required this.points, }); @@ -16,23 +16,24 @@ class NStackRateReminder { factory NStackRateReminder.fromJson(Map json) { return NStackRateReminder( id: json['id'], - points_to_trigger: json['points_to_trigger'], - days_delay_on_skip: json['days_delay_on_skip'], - localization: - Localization.fromJson(json['localization'] as Map), + pointsToTrigger: json['points_to_trigger'], + daysDelayOnSkip: json['days_delay_on_skip'], + localization: RateReminderLocalization.fromJson( + json['localization'] as Map, + ), points: json['points'], ); } } -class Localization { +class RateReminderLocalization { final String title; final String body; final String yesBtn; final String laterBtn; final String noBtn; - Localization({ + RateReminderLocalization({ required this.title, required this.body, required this.yesBtn, @@ -40,8 +41,8 @@ class Localization { required this.noBtn, }); - factory Localization.fromJson(Map json) { - return Localization( + factory RateReminderLocalization.fromJson(Map json) { + return RateReminderLocalization( title: json['title'], body: json['body'], yesBtn: json['yesBtn'], diff --git a/lib/src/sdk/nstack_features.dart b/lib/src/sdk/nstack_features.dart index 583034b..307f24a 100644 --- a/lib/src/sdk/nstack_features.dart +++ b/lib/src/sdk/nstack_features.dart @@ -5,18 +5,18 @@ import 'package:nstack/src/models/rate_reminder.dart'; sealed class NStackFeatureHandler {} -typedef OnMessage = void Function(NStackMessage? message)?; +typedef OnMessage = void Function(NStackMessage? message); typedef OnVersionUpdateNotification = void Function( NStackVersionUpdate? updateInfo, -)?; +); typedef OnRateReminder = void Function( NStackRateReminder? rateReminderInfo, -)?; +); typedef OnRateReminderAnswered = void Function( - NStackRateReminderAnswer? rateReminderAnswer, -)?; + NStackRateReminderAnswer rateReminderAnswer, +); final class NStackMessageHandler implements NStackFeatureHandler { NStackMessageHandler._({this.onMessage}); diff --git a/lib/src/sdk/widgets/nstack_rate_reminder_widget.dart b/lib/src/sdk/widgets/nstack_rate_reminder_widget.dart index e12a608..14063ef 100644 --- a/lib/src/sdk/widgets/nstack_rate_reminder_widget.dart +++ b/lib/src/sdk/widgets/nstack_rate_reminder_widget.dart @@ -128,6 +128,9 @@ class _NStackRateReminderDialog extends StatelessWidget { answer: answer, rateReminderId: rateReminderId, ); + if (onAnswered != null) { + onAnswered!(answer); + } } catch (e) { LogUtil.log( 'Filed to post answer', From 1f53818fcd837540e8ebda0ea8aad53d166f6b5b Mon Sep 17 00:00:00 2001 From: Pritam Biswas Date: Fri, 17 May 2024 12:30:56 +0200 Subject: [PATCH 3/4] - Clean up to follow Flutter convention --- example/lib/nstack.dart | 4 +- lib/src/repository/nstack_repository.dart | 2 + lib/src/sdk/nstack_features.dart | 28 +++---- .../sdk/widgets/nstack_message_widget.dart | 5 +- .../widgets/nstack_rate_reminder_widget.dart | 84 ++++++++++--------- .../nstack_version_control_widget.dart | 5 +- lib/templates/nstack_template.txt | 4 +- 7 files changed, 70 insertions(+), 62 deletions(-) diff --git a/example/lib/nstack.dart b/example/lib/nstack.dart index 90f6974..d4ca9af 100644 --- a/example/lib/nstack.dart +++ b/example/lib/nstack.dart @@ -303,14 +303,14 @@ class NStackWidget extends NStackBaseWidget { required Widget child, AppOpenPlatform? platformOverride, VoidCallback? onComplete, - bool? testMode, + bool testMode = false, }) : super( key: key, child: child, platformOverride: platformOverride, onComplete: onComplete, config: config, - testMode: testMode ?? false, + testMode: testMode, localization: NStackLocalization( config: config, bundledLocalization: BundledLocalizationImpl.data(), diff --git a/lib/src/repository/nstack_repository.dart b/lib/src/repository/nstack_repository.dart index 46c38b0..4e74e4d 100644 --- a/lib/src/repository/nstack_repository.dart +++ b/lib/src/repository/nstack_repository.dart @@ -187,6 +187,7 @@ class NStackRepository { final result = json.decode(rateReminderResponse.body); + // Log rate reminder skip message if (rateReminderResponse.statusCode == 445) { LogUtil.log('Rate Reminder message: ${result['message']}'); return null; @@ -195,6 +196,7 @@ class NStackRepository { final rateReminde = NStackRateReminder.fromJson(result['data']); return rateReminde; } catch (e) { + // Log rest of the rate reminder errors LogUtil.log(e); return null; } diff --git a/lib/src/sdk/nstack_features.dart b/lib/src/sdk/nstack_features.dart index 307f24a..9a33191 100644 --- a/lib/src/sdk/nstack_features.dart +++ b/lib/src/sdk/nstack_features.dart @@ -5,37 +5,35 @@ import 'package:nstack/src/models/rate_reminder.dart'; sealed class NStackFeatureHandler {} -typedef OnMessage = void Function(NStackMessage? message); -typedef OnVersionUpdateNotification = void Function( +typedef MessageCallback = void Function(NStackMessage? message); +typedef VersionUpdateCallback = void Function( NStackVersionUpdate? updateInfo, ); - -typedef OnRateReminder = void Function( - NStackRateReminder? rateReminderInfo, +typedef RateReminderCallback = void Function( + NStackRateReminder rateReminderInfo, ); - -typedef OnRateReminderAnswered = void Function( +typedef RateReminderAnswereCallback = void Function( NStackRateReminderAnswer rateReminderAnswer, ); final class NStackMessageHandler implements NStackFeatureHandler { NStackMessageHandler._({this.onMessage}); - factory NStackMessageHandler.config({OnMessage? onMessage}) => + factory NStackMessageHandler.config({MessageCallback? onMessage}) => NStackMessageHandler._(onMessage: onMessage); - final OnMessage? onMessage; + final MessageCallback? onMessage; } final class NStackVersionUpdateHandler implements NStackFeatureHandler { NStackVersionUpdateHandler._({this.onVersionUpdateNotification}); factory NStackVersionUpdateHandler.config({ - OnVersionUpdateNotification? onVersionUpdateNotification, + VersionUpdateCallback? onVersionUpdateNotification, }) => NStackVersionUpdateHandler._( onVersionUpdateNotification: onVersionUpdateNotification, ); - final OnVersionUpdateNotification? onVersionUpdateNotification; + final VersionUpdateCallback? onVersionUpdateNotification; } final class NStackRateReminderHandler implements NStackFeatureHandler { @@ -45,14 +43,14 @@ final class NStackRateReminderHandler implements NStackFeatureHandler { }); factory NStackRateReminderHandler.config({ - OnRateReminder? onRateReminder, - OnRateReminderAnswered? onRateReminderAnswered, + RateReminderCallback? onRateReminder, + RateReminderAnswereCallback? onRateReminderAnswered, }) => NStackRateReminderHandler._( onRateReminder: onRateReminder, onRateReminderAnswered: onRateReminderAnswered, ); - final OnRateReminder? onRateReminder; - final OnRateReminderAnswered? onRateReminderAnswered; + final RateReminderCallback? onRateReminder; + final RateReminderAnswereCallback? onRateReminderAnswered; } diff --git a/lib/src/sdk/widgets/nstack_message_widget.dart b/lib/src/sdk/widgets/nstack_message_widget.dart index 918e1a3..f85ca5b 100644 --- a/lib/src/sdk/widgets/nstack_message_widget.dart +++ b/lib/src/sdk/widgets/nstack_message_widget.dart @@ -9,6 +9,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:nstack/src/models/nstack_message.dart'; import 'package:nstack/src/sdk/extensions/nstack_widget_extension.dart'; +import 'package:nstack/src/sdk/nstack_features.dart'; import 'package:nstack/src/sdk/widgets/adaptive_dialog_action_widget.dart'; import 'package:nstack/src/utils/log_util.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -16,12 +17,12 @@ import 'package:url_launcher/url_launcher.dart'; class NStackMessageWidget extends StatefulWidget { const NStackMessageWidget({ super.key, - this.child, this.onMessage, + this.child, }); final Widget? child; - final void Function(NStackMessage?)? onMessage; + final MessageCallback? onMessage; @override State createState() => _NStackMessageWidgetSate(); diff --git a/lib/src/sdk/widgets/nstack_rate_reminder_widget.dart b/lib/src/sdk/widgets/nstack_rate_reminder_widget.dart index 14063ef..47036be 100644 --- a/lib/src/sdk/widgets/nstack_rate_reminder_widget.dart +++ b/lib/src/sdk/widgets/nstack_rate_reminder_widget.dart @@ -2,50 +2,52 @@ import 'package:flutter/material.dart'; import 'package:nstack/src/models/nstack_rate_reminder_answer.dart'; import 'package:nstack/src/models/rate_reminder.dart'; import 'package:nstack/src/sdk/extensions/nstack_widget_extension.dart'; +import 'package:nstack/src/sdk/nstack_features.dart'; import 'package:nstack/src/sdk/widgets/adaptive_dialog_action_widget.dart'; import 'package:nstack/src/utils/log_util.dart'; class NStackRateReminderWidget extends StatefulWidget { const NStackRateReminderWidget({ super.key, - this.child, this.onRateReminder, this.onAnswered, + this.child, }) : assert( onRateReminder != null || onAnswered != null, - 'If onRateReminder is null, then onAnswered must not be null.', + 'Either onRateReminder or onAnswered must be provided.', ); final Widget? child; - final void Function(NStackRateReminder?)? onRateReminder; - final void Function(NStackRateReminderAnswer)? onAnswered; + final RateReminderCallback? onRateReminder; + final RateReminderAnswereCallback? onAnswered; @override - State createState() => _NStackRateReminderWidgetSate(); + State createState() => _NStackRateReminderWidgetState(); } -class _NStackRateReminderWidgetSate extends State { +class _NStackRateReminderWidgetState extends State { @override - void didChangeDependencies() { - super.didChangeDependencies(); - + void initState() { + super.initState(); WidgetsBinding.instance.addPostFrameCallback( (timeStamp) async { final rateReminderInfo = await context.nstack.rateReminders.getRateReminderInfo( defaultLocale: Localizations.localeOf(context), ); - _onRateReminder(rateReminderInfo); + if (rateReminderInfo != null) { + _onRateReminder(rateReminderInfo); + } }, ); } - void _onRateReminder(NStackRateReminder? rateReminder) { + void _onRateReminder(NStackRateReminder rateReminder) { if (widget.onRateReminder != null) { widget.onRateReminder!(rateReminder); return; } - if (rateReminder != null && widget.onAnswered != null) { + if (widget.onAnswered != null) { _NStackRateReminderDialog.show( context, rateReminder: rateReminder, @@ -119,37 +121,14 @@ class _NStackRateReminderDialog extends StatelessWidget { rateReminder.localization?.laterBtn ?? _laterButtonTitleFallback, ); - Future buttonAction( - NStackRateReminderAnswer answer, - int rateReminderId, - ) async { - try { - await context.nstack.rateReminders.postRateReminderAnswer( - answer: answer, - rateReminderId: rateReminderId, - ); - if (onAnswered != null) { - onAnswered!(answer); - } - } catch (e) { - LogUtil.log( - 'Filed to post answer', - 'NStackRateReminder', - ); - } - - if (context.mounted) { - Navigator.of(context).pop(); - } - } - return AlertDialog.adaptive( title: messageTitleWidget, content: messageBodyWidget, actions: [ AdaptiveDialogAction( onPressed: () async { - await buttonAction( + await _buttonAction( + context, NStackRateReminderAnswer.positive, rateReminder.id, ); @@ -158,7 +137,8 @@ class _NStackRateReminderDialog extends StatelessWidget { ), AdaptiveDialogAction( onPressed: () async { - await buttonAction( + await _buttonAction( + context, NStackRateReminderAnswer.negative, rateReminder.id, ); @@ -167,7 +147,8 @@ class _NStackRateReminderDialog extends StatelessWidget { ), AdaptiveDialogAction( onPressed: () async { - await buttonAction( + await _buttonAction( + context, NStackRateReminderAnswer.skip, rateReminder.id, ); @@ -177,4 +158,29 @@ class _NStackRateReminderDialog extends StatelessWidget { ], ); } + + Future _buttonAction( + BuildContext context, + NStackRateReminderAnswer answer, + int rateReminderId, + ) async { + try { + await context.nstack.rateReminders.postRateReminderAnswer( + answer: answer, + rateReminderId: rateReminderId, + ); + if (onAnswered != null) { + onAnswered!(answer); + } + } catch (e) { + LogUtil.log( + 'Filed to post answer', + 'NStackRateReminder', + ); + } + + if (context.mounted) { + Navigator.of(context).pop(); + } + } } diff --git a/lib/src/sdk/widgets/nstack_version_control_widget.dart b/lib/src/sdk/widgets/nstack_version_control_widget.dart index ac26789..dcda577 100644 --- a/lib/src/sdk/widgets/nstack_version_control_widget.dart +++ b/lib/src/sdk/widgets/nstack_version_control_widget.dart @@ -10,6 +10,7 @@ import 'package:flutter/material.dart'; import 'package:nstack/src/models/nstack_version_update.dart'; import 'package:nstack/src/models/nstack_version_update_view_request.dart'; import 'package:nstack/src/sdk/extensions/nstack_widget_extension.dart'; +import 'package:nstack/src/sdk/nstack_features.dart'; import 'package:nstack/src/sdk/widgets/adaptive_dialog_action_widget.dart'; import 'package:nstack/src/utils/log_util.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -17,12 +18,12 @@ import 'package:url_launcher/url_launcher.dart'; class NStackVersionControlWidget extends StatefulWidget { const NStackVersionControlWidget({ super.key, - this.child, this.onVersionUpdateNotification, + this.child, }); final Widget? child; - final void Function(NStackVersionUpdate?)? onVersionUpdateNotification; + final VersionUpdateCallback? onVersionUpdateNotification; @override State createState() => _NStackVersionControlWidgetSate(); diff --git a/lib/templates/nstack_template.txt b/lib/templates/nstack_template.txt index 940a205..e5f1beb 100644 --- a/lib/templates/nstack_template.txt +++ b/lib/templates/nstack_template.txt @@ -255,14 +255,14 @@ class NStackWidget extends NStackBaseWidget { required Widget child, AppOpenPlatform? platformOverride, VoidCallback? onComplete, - bool? testMode, + bool testMode = false, }) : super( key: key, child: child, platformOverride: platformOverride, onComplete: onComplete, config: config, - testMode: testMode ?? false, + testMode: testMode, localization: NStackLocalization( config: config, bundledLocalization: BundledLocalizationImpl.data(), From a62d6b6d0ce7746ce2e62c26ae0c09159a1122af Mon Sep 17 00:00:00 2001 From: Pritam Biswas Date: Fri, 17 May 2024 12:48:53 +0200 Subject: [PATCH 4/4] - sort child properties last --- lib/src/sdk/widgets/nstack_message_widget.dart | 2 +- lib/src/sdk/widgets/nstack_rate_reminder_widget.dart | 2 +- lib/src/sdk/widgets/nstack_version_control_widget.dart | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/sdk/widgets/nstack_message_widget.dart b/lib/src/sdk/widgets/nstack_message_widget.dart index f85ca5b..70596a3 100644 --- a/lib/src/sdk/widgets/nstack_message_widget.dart +++ b/lib/src/sdk/widgets/nstack_message_widget.dart @@ -21,8 +21,8 @@ class NStackMessageWidget extends StatefulWidget { this.child, }); - final Widget? child; final MessageCallback? onMessage; + final Widget? child; @override State createState() => _NStackMessageWidgetSate(); diff --git a/lib/src/sdk/widgets/nstack_rate_reminder_widget.dart b/lib/src/sdk/widgets/nstack_rate_reminder_widget.dart index 47036be..82626b7 100644 --- a/lib/src/sdk/widgets/nstack_rate_reminder_widget.dart +++ b/lib/src/sdk/widgets/nstack_rate_reminder_widget.dart @@ -17,9 +17,9 @@ class NStackRateReminderWidget extends StatefulWidget { 'Either onRateReminder or onAnswered must be provided.', ); - final Widget? child; final RateReminderCallback? onRateReminder; final RateReminderAnswereCallback? onAnswered; + final Widget? child; @override State createState() => _NStackRateReminderWidgetState(); diff --git a/lib/src/sdk/widgets/nstack_version_control_widget.dart b/lib/src/sdk/widgets/nstack_version_control_widget.dart index dcda577..2985de0 100644 --- a/lib/src/sdk/widgets/nstack_version_control_widget.dart +++ b/lib/src/sdk/widgets/nstack_version_control_widget.dart @@ -22,8 +22,8 @@ class NStackVersionControlWidget extends StatefulWidget { this.child, }); - final Widget? child; final VersionUpdateCallback? onVersionUpdateNotification; + final Widget? child; @override State createState() => _NStackVersionControlWidgetSate();