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

Add Subbscribe to Content Cards #6

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

followthemoney1
Copy link

@followthemoney1 followthemoney1 commented Dec 15, 2023

Why are we doing this

As part of this ticket, I've added couple functions that should help to use subscribeToContentCards functional that is not been implemented there before

Describe

So the main idea is to subscribe to Content Cards and then have an ability to dismiss it, the subscription itself is working fine but when I've started to combine web implementation with mobile it become harder. That's why we need to hold cards in the plugin as well cuz I didn't find a way how to dismiss Cards by ID. Moreover, there are a couple of card types that we can follow and parse back. Every time when I've tried to parse cards back to JS object it keep saying that this "Object should be a type Card" from braze sdk JS implementation.
As you can see, I've also added a comment which you can follow to try to use converting objects as well.

How to use

  1. call requestContentCardsRefresh - to get a new content cards
  2. subscribeToContentCardsUpdates - subscribe to content cards(you need to look onto this method cuz if cards will be parsed in wrong way there can be an error which also probably better to handle)
  3. logContentCardDismissed - pass content card id to dismiss content card

Examples

On the UI side, if you want to use both mobile and web implementation, you need to create a wrapper that will have this as well. Example

import 'dart:convert';

import 'package:braze_plugin/braze_plugin.dart';
import 'package:data/core/helpers/log_helper.dart';
import 'package:flutter/foundation.dart';

class BrazeContentCardWrapper {
  String id;
  String? title;
  String? description;
  String? url;
  double? aspectRation;
  String? imageUrl;
  DateTime created = DateTime.now();
  DateTime updated = DateTime.now();
  List<dynamic>? categories;
  DateTime expiresAt = DateTime.now();
  String? linkText;
  Map<String, dynamic> extras = {};
  bool? pinned;
  bool? dismissible;
  bool? dismissed;
  bool? clicked;
  bool? isControl;
  bool? test;
  String? ka;
  String? ke;

  /// card tyme is required
  String? type;
  bool? jd;

  BrazeContentCard? _mobileContentCard;

  @protected
  set mobileContentCard(BrazeContentCard card) {
    this._mobileContentCard = card;
  }

  BrazeContentCard get mobileContentCard {
    if (_mobileContentCard != null) return _mobileContentCard!;

    return BrazeContentCard('')
      ..id = id ?? ''
      ..title = title ?? ''
      ..description = description ?? ''
      ..url = url ?? ''
      ..imageAspectRatio = aspectRation?.toDouble() ?? 1.0
      ..image = imageUrl ?? ''
      ..created = created.millisecondsSinceEpoch ~/ 1000
      ..expiresAt = expiresAt.millisecondsSinceEpoch ~/ 1000
      ..linkText = linkText ?? ''
      ..extras = Map.from(extras) // Assuming that extras is a Map<String, String> in BrazeContentCard
      ..pinned = pinned ?? false
      ..dismissable = dismissible ?? false
      ..removed = dismissed ?? false // Assuming dismissed in BrazeContentCardWeb corresponds to removed in
      // BrazeContentCard
      ..clicked = clicked ?? false
      ..isControl = isControl ?? false;
  }

  BrazeContentCardWrapper({
    required this.id,
    this.title,
    this.description,
    this.url,
    this.aspectRation,
    this.imageUrl,
    required this.created,
    required this.updated,
    required this.categories,
    required this.expiresAt,
    this.linkText,
    required this.extras,
    this.pinned,
    this.dismissible,
    this.dismissed,
    this.clicked,
    this.isControl,
    this.test,
    this.ka,
    this.ke,
    this.type,
    this.jd,
  });

  factory BrazeContentCardWrapper.fromJson(Map<String, dynamic> json) {
    final card = BrazeContentCardWrapper(
      id: json['id'] as String? ?? '',
      title: json['title'] as String?,
      description: json['description'] as String?,
      url: json['url'] as String?,
      aspectRation: json['aspectRatio'] as double?,
      imageUrl: json['imageUrl'] as String?,
      created: json['created'] != null ? json['created'] as DateTime : DateTime.now(),
      updated: json['updated'] != null ? json['updated'] as DateTime : DateTime.now(),
      categories: List<dynamic>.from((json['categories'] as List<dynamic>?) ?? []),
      expiresAt: json['expiresAt'] != null ? json['expiresAt'] as DateTime : DateTime.now(),
      linkText: json['linkText'] as String?,
      extras: Map<String, dynamic>.from(json['extras'] as Map<String, dynamic>),
      pinned: json['pinned'] as bool?,
      dismissible: json['dismissible'] as bool?,
      dismissed: json['dismissed'] as bool?,
      clicked: json['clicked'] as bool?,
      isControl: json['isControl'] as bool?,
      test: json['test'] as bool?,
      ka: json['ka'] as String?,
      ke: json['ke'] as String?,
      type: json['Yc'] as String?,
      jd: json['jd'] as bool?,
    );
    return card;
  }

  factory BrazeContentCardWrapper.fromBrazeContentCard(BrazeContentCard originalCard) {
    final card = BrazeContentCardWrapper(
      id: originalCard.id,
      title: originalCard.title,
      description: originalCard.description,
      url: originalCard.url,
      aspectRation: originalCard.imageAspectRatio.toDouble(),
      imageUrl: originalCard.image,
      created: DateTime.fromMillisecondsSinceEpoch(originalCard.created * 1000),
      updated: DateTime.fromMillisecondsSinceEpoch(originalCard.created * 1000),

      /// unimplemented for now
      categories: null,
      expiresAt: DateTime.fromMillisecondsSinceEpoch(originalCard.expiresAt * 1000),
      linkText: originalCard.linkText,
      extras: originalCard.extras,
      pinned: originalCard.pinned,
      dismissible: originalCard.dismissable,

      /// maybe wrong but there is not inaf documentation
      dismissed: originalCard.removed,
      clicked: originalCard.clicked,
      isControl: originalCard.isControl,
    );
    card.mobileContentCard = originalCard;
    return card;
  }

  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'title': title,
      'description': description,
      'url': url,
      'aspectRation': aspectRation,
      'imageUrl': imageUrl,
      'created': created.toIso8601String(),
      'updated': updated.toIso8601String(),
      'categories': categories,
      'expiresAt': expiresAt.toIso8601String(),
      'linkText': linkText,
      'extras': extras,
      'pinned': pinned,
      'dismissible': dismissible,
      'dismissed': dismissed,
      'clicked': clicked,
      'isControl': isControl,
      'test': test,
      'ka': ka,
      'ke': ke,
      'Yc': type,
      'jd': jd,
    };
  }

}

Also if you want to have a braze plugin implementation, you can use something like:
main plugin abstraction:

import 'braze_service_mobile_impl.dart'
    if (dart.library.js) 'braze_service_web_impl.dart';

/// Wrapper on Braze Service implementation for web and mobile
/// TODO: Move this implementation to abstract class if second init of Braze plugin not a problem
abstract class BrazeService {
  static BrazeService _instance = BrazeServiceImpl();

  /// The default instance of [BrazeServiceImpl] to use.
  // ignore: unnecessary_getters_setters
  static BrazeService get instance => _instance;

  /// Platform-specific plugins should set this with their own platform-specific
  /// class that extends [BrazeServiceImpl] when they register themselves.
  @protected
  static set instance(BrazeService instance) {
    _instance = instance;
  }

  StreamSubscription subscribeToContentCards({required Function(List<BrazeContentCardWrapper>) onCards});

  void changeUser({required User? user}) => _instance.changeUser(user: user);

  void subscribeToNotifications({required bool subscribe});

  void subscribeToAllBrazeSubscriptionGroups(
    List<BrazeSubscriptionGroupItem> brazeSubscriptionGroupItems,
  );

  Future<String> getInstallTrackingId();

  /// Mobile function doesen't return a result
  bool? logContentCardDismissed(BrazeContentCardWrapper card);
}

web plugin implementation:

class BrazeServiceImpl extends BrazeService {
  // ignore: non_constant_identifier_names
  final String TAG = 'Br: ';

  BrazeServiceImpl() {
    BrazeClient.instance.initialize(
      
    );
  }

  
  StreamSubscription subscribeToContentCards({required Function(List<BrazeContentCardWrapper>) onCards}) {
    //TODO: braze web ai key different per affiliate should be
    BrazeClient.instance.requestContentCardsRefresh(
      onSuccess: () {
        //TODO: TBD
      },
      onError: () {
        //TODO: TBD
      },
    );

    return BrazeClient.instance.subscribeToContentCardsUpdates().listen((content) {
      final contentCards = content
          .map(
            (value) => BrazeContentCard.fromJson(value),
          )
          .toList();
    });
  }

  @override
  bool? logContentCardDismissed(BrazeContentCardWrapper card) {
    return BrazeClient.instance.logContentCardDismissed(card.id);
  }

  @override
  void changeUser({required User? user}) {
    if (user != null) {
    } else {
    }
  }

  @override
  void subscribeToNotifications({required bool subscribe}) {
    //TODO: implement, for now we do not need to use push notification
    //throw UnimplementedError('Unimplemented yet, need to review');
  }

  @override
  void subscribeToAllBrazeSubscriptionGroups(
    List<BrazeSubscriptionGroupItem> brazeSubscriptionGroupItems,
  ) {
    final List<String> brazeSubscriptionGroupIds =
        brazeSubscriptionGroupItems.map((e) => e.subscriptionGroupId ?? '').toList();
    brazeSubscriptionGroupIds.forEach((groupId) {
      final result = BrazeClient.instance.addToSubscriptionGroup(groupId);
    });
  }

  @override
  Future<String> getInstallTrackingId() {
    //TODO: throw UnimplementedError("This feature isn't implemented on web yet");
    return Future.value('');
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant