Skip to content
Merged
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 🛠️ ht_shared

![coverage: percentage](https://img.shields.io/badge/coverage-96-green)
![coverage: percentage](https://img.shields.io/badge/coverage-81-green)
[![style: very good analysis](https://img.shields.io/badge/style-very_good_analysis-B22C89.svg)](https://pub.dev/packages/very_good_analysis)
[![License: PolyForm Free Trial](https://img.shields.io/badge/License-PolyForm%20Free%20Trial-blue)](https://polyformproject.org/licenses/free-trial/1.0.0)

Expand Down
43 changes: 40 additions & 3 deletions lib/src/models/auth/user.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ class User extends Equatable {
/// Requires a unique [id] and a [role].
/// The [email] is optional and typically present only for users
/// who have verified their email address.
const User({required this.id, required this.role, this.email});
const User({
required this.id,
required this.role,
this.email,
this.createdAt,
});

/// Creates a User from JSON data.
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Expand All @@ -31,14 +36,46 @@ class User extends Equatable {
/// The role of the user.
final UserRole role;

/// The date and time the user account was created.
/// This is typically set on the backend upon user creation.
@JsonKey(fromJson: _dateTimeFromJson, toJson: _dateTimeToJson)
final DateTime? createdAt;

/// Converts this User instance to JSON data.
Map<String, dynamic> toJson() => _$UserToJson(this);

@override
List<Object?> get props => [id, email, role];
List<Object?> get props => [id, email, role, createdAt];

@override
String toString() {
return 'User(id: $id, email: $email, role: $role)';
return 'User(id: $id, email: $email, role: $role, createdAt: $createdAt)';
}

/// Creates a copy of this [User] but with the given fields replaced with
/// the new values.
User copyWith({
String? id,
String? email,
UserRole? role,
DateTime? createdAt,
}) {
return User(
id: id ?? this.id,
email: email ?? this.email,
role: role ?? this.role,
createdAt: createdAt ?? this.createdAt,
);
}
}

// Helper function for parsing DateTime, returning null on error
DateTime? _dateTimeFromJson(String? dateString) {
if (dateString == null) return null;
return DateTime.tryParse(dateString);
}

// Helper function for serializing DateTime to ISO 8601 string
String? _dateTimeToJson(DateTime? dateTime) {
return dateTime?.toIso8601String();
}
3 changes: 3 additions & 0 deletions lib/src/models/auth/user.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 42 additions & 0 deletions lib/src/models/feed_extras/engagement_content_template.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import 'package:equatable/equatable.dart';
import 'package:ht_shared/src/models/feed_extras/feed_template_types.dart';
import 'package:json_annotation/json_annotation.dart';

part 'engagement_content_template.g.dart';

/// {@template engagement_content_template}
/// Defines the static content for an engagement prompt.
/// The 'type' of an instance should match an [EngagementTemplateType] value.
/// {@endtemplate}
@JsonSerializable(explicitToJson: true)
class EngagementContentTemplate extends Equatable {
/// {@macro engagement_content_template}
const EngagementContentTemplate({
required this.type,
required this.title,
this.description,
this.callToActionText,
});

/// Creates an [EngagementContentTemplate] from JSON data.
factory EngagementContentTemplate.fromJson(Map<String, dynamic> json) =>
_$EngagementContentTemplateFromJson(json);

/// The type of engagement template, matching an [EngagementTemplateType] value.
final EngagementTemplateType type;

/// The main title or heading for the engagement content.
final String title;

/// An optional description providing more details.
final String? description;

/// The text for the call-to-action button or link.
final String? callToActionText;

/// Converts this [EngagementContentTemplate] instance to JSON data.
Map<String, dynamic> toJson() => _$EngagementContentTemplateToJson(this);

@override
List<Object?> get props => [type, title, description, callToActionText];
}
35 changes: 35 additions & 0 deletions lib/src/models/feed_extras/engagement_content_template.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions lib/src/models/feed_extras/feed_extras.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export 'engagement_content_template.dart';
export 'feed_template_types.dart';
export 'suggested_content_template.dart';
37 changes: 37 additions & 0 deletions lib/src/models/feed_extras/feed_template_types.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import 'package:json_annotation/json_annotation.dart';

/// Defines the types of engagement content templates available.
/// The string value of the enum (e.g., 'rate-app') will be used as the ID
/// to link rules in AppConfig to specific EngagementContentTemplate instances.
@JsonEnum(fieldRename: FieldRename.kebab)
enum EngagementTemplateType {
/// Prompt to rate the application.
rateApp,

/// Prompt for guest users to create/link an account.
linkAccount,

/// Prompt for standard users to upgrade to premium.
upgradeToPremium,

/// Prompt to complete user profile (e.g., select content preferences).
completeProfile,

/// Prompt to explore a new feature.
exploreNewFeature,
}

/// Defines the types of suggested content templates available.
/// The string value of the enum (e.g., 'categories-to-follow') will be used as the ID
/// to link rules in AppConfig to specific SuggestedContentTemplate instances.
@JsonEnum(fieldRename: FieldRename.kebab)
enum SuggestionTemplateType {
/// Suggest categories to follow.
categoriesToFollow,

/// Suggest sources to follow.
sourcesToFollow,

/// Suggest countries to follow for news.
countriesToFollow,
}
66 changes: 66 additions & 0 deletions lib/src/models/feed_extras/suggested_content_template.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import 'package:equatable/equatable.dart';
import 'package:ht_shared/src/models/core/content_type.dart';
import 'package:ht_shared/src/models/feed_decorators/suggested_content_display_type.dart';
import 'package:ht_shared/src/models/feed_extras/feed_template_types.dart';
import 'package:json_annotation/json_annotation.dart';

part 'suggested_content_template.g.dart';

/// {@template suggested_content_template}
/// Defines the static content and configuration for a suggestion block.
/// The 'type' of an instance should match a [SuggestionTemplateType] value.
/// {@endtemplate}
@JsonSerializable(explicitToJson: true)
class SuggestedContentTemplate extends Equatable {
/// {@macro suggested_content_template}
const SuggestedContentTemplate({
required this.type,
required this.displayType,
required this.suggestedContentType,
this.title,
this.description,
this.maxItemsToDisplay,
this.fetchCriteria,
});

/// Creates a [SuggestedContentTemplate] from JSON data.
factory SuggestedContentTemplate.fromJson(Map<String, dynamic> json) =>
_$SuggestedContentTemplateFromJson(json);

/// The type of suggestion template, matching a [SuggestionTemplateType] value.
final SuggestionTemplateType type;

/// An optional title for the suggestion block (e.g., "You might like...").
final String? title;

/// An optional description for the suggestion block.
final String? description;

/// The visual presentation or layout style for this suggestion block.
final SuggestedContentDisplayType displayType;

/// Defines what kind of primary content this suggestion block will contain
/// (e.g., if suggesting categories, this would be [ContentType.category]).
final ContentType suggestedContentType;

/// Maximum number of items to display within this suggestion block.
final int? maxItemsToDisplay;

/// Criteria for fetching dynamic items, e.g., "popular", "newest".
/// This is a simple string; the decorator will interpret it.
final String? fetchCriteria;

/// Converts this [SuggestedContentTemplate] instance to JSON data.
Map<String, dynamic> toJson() => _$SuggestedContentTemplateToJson(this);

@override
List<Object?> get props => [
type,
title,
description,
displayType,
suggestedContentType,
maxItemsToDisplay,
fetchCriteria,
];
}
60 changes: 60 additions & 0 deletions lib/src/models/feed_extras/suggested_content_template.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions lib/src/models/models.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export 'auth/auth.dart';
export 'core/core.dart';
export 'entities/entities.dart';
export 'feed_decorators/feed_decorators.dart';
export 'feed_extras/feed_extras.dart';
export 'remote_config/remote_config.dart';
export 'responses/responses.dart';
export 'user_preferences/user_preferences.dart';
Expand Down
21 changes: 19 additions & 2 deletions lib/src/models/remote_config/app_config.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:equatable/equatable.dart';
import 'package:ht_shared/src/models/remote_config/ad_config.dart';
import 'package:ht_shared/src/models/remote_config/feed_rules.dart';
import 'package:ht_shared/src/models/remote_config/user_preference_limits.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:meta/meta.dart';
Expand Down Expand Up @@ -31,6 +32,8 @@ class AppConfig extends Equatable {
required this.id,
UserPreferenceLimits? userPreferenceLimits,
AdConfig? adConfig,
List<EngagementRule>? engagementRules,
List<SuggestionRule>? suggestionRules,
}) : userPreferenceLimits = userPreferenceLimits ??
const UserPreferenceLimits(
guestFollowedItemsLimit: 5,
Expand All @@ -48,7 +51,9 @@ class AppConfig extends Equatable {
authenticatedAdPlacementInterval: 5,
premiumAdFrequency: 0, // No ads for premium users by default
premiumAdPlacementInterval: 0,
); // Default ad config
), // Default ad config
engagementRules = engagementRules ?? const [],
suggestionRules = suggestionRules ?? const [];

/// Factory method to create an [AppConfig] instance from a JSON map.
factory AppConfig.fromJson(Map<String, dynamic> json) =>
Expand All @@ -66,11 +71,23 @@ class AppConfig extends Equatable {
/// tiered by user role.
final AdConfig adConfig;

/// Defines rules for triggering engagement prompts.
final List<EngagementRule> engagementRules;

/// Defines rules for triggering content suggestion blocks.
final List<SuggestionRule> suggestionRules;

/// Converts this [AppConfig] instance to a JSON map.
Map<String, dynamic> toJson() => _$AppConfigToJson(this);

@override
List<Object> get props => [id, userPreferenceLimits, adConfig];
List<Object> get props => [
id,
userPreferenceLimits,
adConfig,
engagementRules,
suggestionRules,
];

@override
bool get stringify => true;
Expand Down
Loading
Loading