Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 20 additions & 16 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ class ExampleApp extends StatelessWidget {
return MaterialApp(
builder: (context, child) {
return NStackWidget(
platformOverride: AppOpenPlatform.android,
child: child!,
);
},
Expand All @@ -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);
},
Comment on lines +33 to +35
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the onMessage can be added directly to NStackWidget, hence no need for an NStackMessageListener.

class ExampleApp extends StatelessWidget {
  const ExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      builder: (context, child) {
        return NStackWidget(
          child: child!,
          onMessage: (message) {
           // ...
          }
        );
      },
      home: const MainScreen(),
    );
  }
}

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}',
),
),
),
),
Expand Down
262 changes: 256 additions & 6 deletions example/lib/nstack.dart
Original file line number Diff line number Diff line change
@@ -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!
*
Expand All @@ -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';

Expand All @@ -42,20 +51,27 @@ export 'package:nstack/models/app_open_platform.dart';
*/

final NStack = NStackSdk<Localization>(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it have to be a global variable?

config: _config,
localization: _nstackLocalization,
repository: _nstackRepository,
isDebug: kDebugMode,
localization: _nstackLocalization,
messages: _nstackMessages,
);

const _nstackRepository = NStackRepository(_config);

final _nstackLocalization = NStackLocalization<Localization>(
config: _config,
repository: _nstackRepository,
translations: const Localization(),
availableLanguages: _languages,
bundledTranslations: _bundledTranslations,
pickedLanguageLocale: '',
isDebug: kDebugMode,
);

final _nstackMessages = NStackMessages(
repository: _nstackRepository,
);

const _config = NStackConfig(
projectId: 'h6wJremI2TGFM88gbLkdyljWQuwf2hxhxvCH',
apiKey: 'zp2S18H32b67eYAbRQh94tVw76ZzaKKXlHjd',
Expand Down Expand Up @@ -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;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not being used I think it's safe to remove, plus storing a widget's state is not a best practice and might produce unpredictable results.

final String checksum;
Expand All @@ -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,
Expand All @@ -185,6 +220,12 @@ class NStackState extends State<NStackWidget> {

late final StreamSubscription _localeChangedSubscription;

/// Gets the NStack Localization feature configured for this project.
NStackLocalization<Localization> get localization => _nstack.localization;

/// Gets the NStack Message feature configured for this project.
NStackMessages get messages => _nstack.messages;

@override
void initState() {
super.initState();
Expand Down Expand Up @@ -240,6 +281,215 @@ class NStackState extends State<NStackWidget> {
}
}

/*
*
* 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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we do factory methods on the main NStack class?

Kind of like NStack.Message()

We could potentially extend that to have NStack.MessageCustom() etc

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@johsoe why?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No other reason than i like that pattern more than exposing multiple NStack* classes upfront which can be a bit messy to figure out

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@johsoe I guess you mean a named constructor, cause a factory on a class T can only return an instance of T, so in this case if we use a factory on NStack we can't return a widget. Named constructors also can't return widgets:

class NStack {
  Widget NStack.Message() {
    return Container();
  }
}

You will get the following errors:

  • Constructors can't have a return type. Try removing the return type.dart(constructor_with_return_type)
  • Constructors can't return values. Try removing the return statement or using a factory

To return a widget you can use a static method:

class NStack {
  static Widget message({Widget? child}) {
    return child ?? Container();
  }
}

// And then call `NStack.message(child: HomeScreen())` 

However, one interesting implementation I came up with is using a mixin:

mixin NStackMessageListener<T extends StatefulWidget> on State<T> {
  StreamSubscription? _subscription;

  void onMessage(Message message) {}

  @override
  void didChangeDependencies() {
    if (_subscription == null) {
      final scope = NStackScope.of(context);
      _subscription = scope.messages.onMessage.listen(onMessage);
    }
    super.didChangeDependencies();
  }

  @override
  void dispose() {
    _subscription?.cancel();
    super.dispose();
  }
}

And in your widget:

class MainScreen extends StatefulWidget {
  const MainScreen({super.key});

  @override
  State<MainScreen> createState() => _MainScreenState();
}

class _MainScreenState extends State<MainScreen> with NStackMessageListener {
  @override
  void onMessage(Message message) {
    NStackMessageDialog.show(context, message: message);
    super.onMessage(message);
  }

  @override
  Widget build(BuildContext context) {
    // ...
  }
}

const NStackMessageListener({
Key? key,
required this.onMessage,
this.child,
}) : super(key: key);

final void Function(Message message) onMessage;
final Widget? child;

@override
State<NStackMessageListener> createState() => _NStackMessageListenerState();
}

class _NStackMessageListenerState extends State<NStackMessageListener> {
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<void> 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
Expand Down
6 changes: 3 additions & 3 deletions lib/models/message.dart
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
1 change: 1 addition & 0 deletions lib/nstack.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export 'package:nstack/sdk/nstack_sdk.dart';
Loading