Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Messages feature #61

Merged
merged 6 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
4 changes: 2 additions & 2 deletions example/android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
buildscript {
ext.kotlin_version = '1.6.10'
ext.kotlin_version = '1.9.22'
repositories {
google()
mavenCentral()
Expand All @@ -26,6 +26,6 @@ subprojects {
project.evaluationDependsOn(':app')
}

task clean(type: Delete) {
tasks.register("clean", Delete) {
delete rootProject.buildDir
}
8 changes: 7 additions & 1 deletion example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@ PODS:
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- url_launcher_ios (0.0.1):
- Flutter

DEPENDENCIES:
- Flutter (from `Flutter`)
- nstack (from `.symlinks/plugins/nstack/ios`)
- package_info (from `.symlinks/plugins/package_info/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)

EXTERNAL SOURCES:
Flutter:
Expand All @@ -23,13 +26,16 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/package_info/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"

SPEC CHECKSUMS:
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
nstack: a7ebf31d8c387ec76d4bfe5eb494d3ac81756d92
package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
url_launcher_ios: bf5ce03e0e2088bad9cc378ea97fa0ed5b49673b

PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3

COCOAPODS: 1.12.1
COCOAPODS: 1.14.3
48 changes: 34 additions & 14 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,40 @@ class MainScreen extends StatelessWidget {
final localizationAsset = localization.assets;
final activeLanguage = localization.activeLanguage;

return Scaffold(
appBar: AppBar(
title: Text(localizationAsset.test.testDollarSign),
),
body: Center(
child: MaterialButton(
onPressed: () {
final locale = activeLanguage.locale == 'en-EN'
? const Locale('de-DE')
: const Locale('en-EN');
localization.changeLocalization(locale);
},
child: Text(
'Selected locale: ${activeLanguage.name}',
// Message option for receiving the Message response, will not show the default dialog.
// void onMessage(Message message) {
// if (kDebugMode) {
// print(message.message);
// }
// }
//
// final customNstackMessageOptions =
// CustomNstackMessageOptions(onMessage: onMessage);

// Message option for showing default dialog.
final defaultNstackMessageOptions = DefaultNstackMessageOptions(
okButtonTitle: localizationAsset.test.okButtonTitle,
openUrlButtonTitle: localizationAsset.test.openUrlButtonTitle,
dialogTitle: localizationAsset.test.dialogTitle,
);

return NStackMessageWidget(
messageOptions: defaultNstackMessageOptions,
child: Scaffold(
appBar: AppBar(
title: Text(localizationAsset.test.testDollarSign),
),
body: Center(
child: MaterialButton(
onPressed: () {
final locale = activeLanguage.locale == 'en-EN'
? const Locale('de-AT')
: const Locale('en-EN');
localization.changeLocalization(locale);
},
child: Text(
'Selected locale: ${activeLanguage.name}',
),
),
),
),
Expand Down
227 changes: 221 additions & 6 deletions example/lib/nstack.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@
* To access localization in your UI you can use an extension for BuildContexts:
* `context.localization.assets.yourSection.yourKey`.
*
* 💬 MESSAGES
*
* To use messages, you can use `NStackMessageWidget`,
* and if you want the default dialog set, `shouldShowDefaultDialog` to `true`.
* Or you can set the `shouldShowDefaultDialog` flag to `false` and
* provide an `onMessage` callback to get the `Message` object from the NStack.
*
* 🛠️ IMPORTANT NOTES FOR SDK USERS
*
* The default environment for the NStack SDK is `prod`.
Expand All @@ -30,15 +37,20 @@
// ignore_for_file: unnecessary_cast

import 'dart:async';
import 'dart:io';

import 'package:flutter/widgets.dart';
import 'package:flutter/material.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/partial/section_key_delegate.dart';
import 'package:flutter/cupertino.dart' as cupertino;
import 'package:flutter/material.dart' as material;
import 'package:url_launcher/url_launcher.dart';

export 'package:nstack/models/app_open_platform.dart';

Expand All @@ -54,7 +66,7 @@ NStackSdk createNStackSdk() {
LocalizeIndex(
id: 1216,
url: 'https://nstack.io/api/v2/content/localize/resources/1216',
lastUpdatedAt: DateTime.parse('2021-09-17T18:13:38.000Z'),
lastUpdatedAt: DateTime.parse('2024-01-23T08:16:25.000Z'),
shouldUpdate: true,
language: const Language(
id: 56,
Expand All @@ -68,7 +80,7 @@ NStackSdk createNStackSdk() {
LocalizeIndex(
id: 1270,
url: 'https://nstack.io/api/v2/content/localize/resources/1270',
lastUpdatedAt: DateTime.parse('2021-11-10T11:46:52.000Z'),
lastUpdatedAt: DateTime.parse('2024-01-23T08:16:41.000Z'),
shouldUpdate: true,
language: const Language(
id: 7,
Expand All @@ -83,9 +95,9 @@ NStackSdk createNStackSdk() {

const bundledTranslations = {
'en-EN':
r'''{"data":{"default":{"title":"NStack SDK Demo","test":"test"},"test":{"testDollarSign":"$testing again new","testSingleQuotationMark":"'testing'","testDoubleQuotationMark":"\"testing\"","testMultipleLines":"testing\nmultiple\nlines\nupdated"}},"meta":{"language":{"id":56,"name":"English","locale":"en-EN","direction":"LRM","is_default":false,"is_best_fit":false},"platform":{"id":515,"slug":"mobile"}}}''',
r'''{"data":{"default":{"title":"NStack SDK Demo","test":"test"},"test":{"testDollarSign":"$testing again new","testSingleQuotationMark":"'testing'","testDoubleQuotationMark":"\"testing\"","testMultipleLines":"testing\nmultiple\nlines\nupdated","okButtonTitle":"Done","openUrlButtonTitle":"Open","dialogTitle":"Message"}},"meta":{"language":{"id":56,"name":"English","locale":"en-EN","direction":"LRM","is_default":false,"is_best_fit":false},"platform":{"id":515,"slug":"mobile"}}}''',
'de-AT':
r'''{"data":{"default":{"title":"NStack SDK Demo","test":"test"},"test":{"testDollarSign":"\u00a0","testSingleQuotationMark":"__testSingleQuotationMark","testDoubleQuotationMark":"__testDoubleQuotationMark","testMultipleLines":"__testMultipleLines"}},"meta":{"language":{"id":7,"name":"German (Austria)","locale":"de-AT","direction":"LRM","is_default":false,"is_best_fit":false},"platform":{"id":515,"slug":"mobile"}}}''',
r'''{"data":{"default":{"title":"NStack SDK Demo","test":"pr\u00fcfen"},"test":{"testDollarSign":"$Testen Sie noch einmal neu","testSingleQuotationMark":"'testen'","testDoubleQuotationMark":"\"testen\"","testMultipleLines":"testen\nmehrere\nLinien\nAktualisiert","okButtonTitle":"Erledigt","openUrlButtonTitle":"Offen","dialogTitle":"Nachricht"}},"meta":{"language":{"id":7,"name":"German (Austria)","locale":"de-AT","direction":"LRM","is_default":false,"is_best_fit":false},"platform":{"id":515,"slug":"mobile"}}}''',
};

// Create an instance of NStackLocalization with the predefined values
Expand Down Expand Up @@ -131,6 +143,9 @@ class _Test extends SectionKeyDelegate {
get('testDoubleQuotationMark', '\"testing\"');
String get testMultipleLines =>
get('testMultipleLines', 'testing\nmultiple\nlines\nupdated');
String get okButtonTitle => get('okButtonTitle', 'Done');
String get openUrlButtonTitle => get('openUrlButtonTitle', 'Open');
String get dialogTitle => get('dialogTitle', 'Message');
}

/*
Expand Down Expand Up @@ -220,7 +235,6 @@ class NStackState extends State<NStackWidget> {
@override
void dispose() {
_localeChangedSubscription.cancel();

super.dispose();
}

Expand All @@ -243,6 +257,207 @@ class NStackState extends State<NStackWidget> {
}
}

abstract class NStackMessageOptions {
/// Title of the OK button.
final String? okButtonTitle;

/// Title of the Open URL button.
final String? openUrlButtonTitle;

/// Title of the dialog.
final String? dialogTitle;

/// Callback to customize the message UI.
final void Function(Message message)? onMessage;

NStackMessageOptions({
this.okButtonTitle,
this.openUrlButtonTitle,
this.dialogTitle,
this.onMessage,
});
}

class DefaultNstackMessageOptions extends NStackMessageOptions {
DefaultNstackMessageOptions({
String? okButtonTitle,
String? openUrlButtonTitle,
String? dialogTitle,
}) : super(
okButtonTitle: okButtonTitle,
openUrlButtonTitle: openUrlButtonTitle,
dialogTitle: dialogTitle,
);
}

class CustomNstackMessageOptions extends NStackMessageOptions {
CustomNstackMessageOptions({
required void Function(Message message) onMessage,
}) : super(onMessage: onMessage);
}

class NStackMessageWidget extends StatefulWidget {
const NStackMessageWidget({
super.key,
required this.messageOptions,
this.child,
});

final NStackMessageOptions messageOptions;

final Widget? child;

@override
State<StatefulWidget> createState() => _NStackMessageWidgetSate();
}

class _NStackMessageWidgetSate extends State<NStackMessageWidget> {
late final StreamSubscription _messageSubscription;
@override
void initState() {
super.initState();

WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_messageSubscription =
context.nstack.messages.onMessage.listen(_onMessage);
});
}

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

void _onMessage(Message message) {
if (widget.messageOptions.onMessage != null) {
widget.messageOptions.onMessage?.call(message);
} else {
NStackMessageDialog.show(
context,
message: message,
okButtonTitle: widget.messageOptions.okButtonTitle,
openUrlButtonTitle: widget.messageOptions.openUrlButtonTitle,
dialogTitle: widget.messageOptions.dialogTitle,
);
}
}

@override
Widget build(BuildContext context) {
return widget.child ?? const SizedBox();
}
}

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.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;

/// Displays the dialog.
static Future<void> show(
BuildContext context, {
required Message message,
String? okButtonTitle,
String? openUrlButtonTitle,
String? dialogTitle = _dialogTitleFallback,
}) {
Widget builder(BuildContext context) {
return NStackMessageDialog._(
message: message,
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(() {
context.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 messageUrl = message.url;
final uri = messageUrl != null ? Uri.tryParse(messageUrl) : null;
final isUriValid = uri != null;

final urlLaunchAction = !isUriValid
? null
: () {
launchUrl(uri);
Navigator.of(context).pop();
};

if (Platform.isIOS) {
return cupertino.CupertinoAlertDialog(
title: titleWidget,
content: messageWidget,
actions: [
cupertino.CupertinoDialogAction(
onPressed: Navigator.of(context).pop,
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: Navigator.of(context).pop,
child: okWidget,
),
],
);
}
}

/*
*
* NStack Flutter Extensions
Expand Down
Loading
Loading