Skip to content

Commit

Permalink
Merge pull request #567 from rokwire/issue/app/4830-storage-data-sour…
Browse files Browse the repository at this point in the history
…ce-in-flexui

Fixed storage update support in FlexUi [rokwire/illinois-app#4830].
  • Loading branch information
mihail-varbanov authored Feb 28, 2025
2 parents 77ded6c + 4b67ba7 commit a8e305c
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 46 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
<!-- The next version number should be the version from pubspec.yaml -->

## [1.8.X]
<!-- The following updates persist in the 1.8 support branch. -->
<!-- They should eihter appear in the next 1.8 support update, or in the upcoming 1.9 release. -->
### Fixed
- Fixed storage update support in FlexUi [#4830](https://github.com/rokwire/illinois-app/issues/4830).

## [1.8.0] - 2025-02-19
### Changed
- Allow event custom range filters in the past [#4450](https://github.com/rokwire/illinois-app/issues/4450).
Expand Down
113 changes: 67 additions & 46 deletions lib/service/flex_ui.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class FlexUI with Service implements NotificationsListener {
Set<dynamic>? _defaultFeatures;
Map<String, Set<String>>? _sourceAttributes;
Map<String, Set<String>>? _defaultAttributes;
Set<String>? _defaultStorageKeys;

// Singletone Factory

Expand Down Expand Up @@ -86,6 +87,7 @@ class FlexUI with Service implements NotificationsListener {
Groups.notifyUserGroupsUpdated,
GeoFence.notifyCurrentRegionsUpdated,
Config.notifyConfigChanged,
Storage.notifySettingChanged,
]);
}

Expand Down Expand Up @@ -132,7 +134,9 @@ class FlexUI with Service implements NotificationsListener {
(name == Auth2.notifyLinkChanged) ||
(name == Groups.notifyUserGroupsUpdated) ||
(name == GeoFence.notifyCurrentRegionsUpdated) ||
(name == Config.notifyConfigChanged))
(name == Config.notifyConfigChanged) ||
((name == Storage.notifySettingChanged) && (_defaultStorageKeys?.contains(param) == true))
)
{
updateContent();
}
Expand Down Expand Up @@ -266,16 +270,22 @@ class FlexUI with Service implements NotificationsListener {
void build() {
_contentSource = buildContentSource();

_defaultContent = buildContent(defaultContentSourceEntry);
FlexUiBuildContext buildContext = FlexUiBuildContext();
_defaultContent = buildContent(defaultContentSourceEntry, buildContext: buildContext);
_sourceAttributes = buildAttributes(defaultSourceContent);

_defaultFeatures = buildFeatures(_defaultContent);
_defaultAttributes = buildAttributes(_defaultContent);
_defaultStorageKeys = buildContext.storageKeys;
}

@protected
void updateContent() {
Map<String, dynamic>? defaultContent = buildContent(defaultContentSourceEntry);
FlexUiBuildContext buildContext = FlexUiBuildContext();
Map<String, dynamic>? defaultContent = buildContent(defaultContentSourceEntry, buildContext: buildContext);
if (!const DeepCollectionEquality().equals(_defaultStorageKeys, buildContext.storageKeys)) {
_defaultStorageKeys = buildContext.storageKeys;
}
if ((defaultContent != null) && ((_defaultContent == null) || !const DeepCollectionEquality().equals(_defaultContent, defaultContent))) {
_defaultContent = defaultContent;

Expand Down Expand Up @@ -315,7 +325,7 @@ class FlexUI with Service implements NotificationsListener {
return _defaultContent;
}
else {
return buildContent(contentSourceEntry(key));
return buildContent(contentSourceEntry(key, /* buildContext: we do not care about it for non default content */));
}
}

Expand Down Expand Up @@ -360,7 +370,7 @@ class FlexUI with Service implements NotificationsListener {
// Local Build

@protected
Map<String, dynamic>? buildContent(Map<String, dynamic>? contentSource) {
Map<String, dynamic>? buildContent(Map<String, dynamic>? contentSource, { FlexUiBuildContext? buildContext }) {
Map<String, dynamic>? result;
if (contentSource != null) {
Map<String, dynamic> contents = JsonUtils.mapValue(contentSource['content']) ?? <String, dynamic>{};
Expand All @@ -371,7 +381,7 @@ class FlexUI with Service implements NotificationsListener {

if (contentEntry is Map) {
for (String contentEntryKey in contentEntry.keys) {
if (localeIsEntryAvailable(contentEntryKey, group: key, rules: rules)) {
if (localeIsEntryAvailable(contentEntryKey, group: key, rules: rules, buildContext: buildContext)) {
contentEntry = contentEntry[contentEntryKey];
break;
}
Expand All @@ -383,7 +393,7 @@ class FlexUI with Service implements NotificationsListener {
for (dynamic entry in contentEntry) {
String? stringEntry = JsonUtils.stringValue(entry);
if (stringEntry != null) {
if (localeIsEntryAvailable(stringEntry, group: key, rules: rules)) {
if (localeIsEntryAvailable(stringEntry, group: key, rules: rules, buildContext: buildContext)) {
resultList.add(entry);
}
}
Expand All @@ -399,57 +409,57 @@ class FlexUI with Service implements NotificationsListener {
}

@protected
bool localeIsEntryAvailable(String entry, { String? group, required Map<String, dynamic> rules }) {
bool localeIsEntryAvailable(String entry, { String? group, required Map<String, dynamic> rules, FlexUiBuildContext? buildContext }) {

String? pathEntry = (group != null) ? '$group.$entry' : null;

Map<String, dynamic>? roleRules = rules['roles'];
dynamic roleRule = (roleRules != null) ? (((pathEntry != null) ? roleRules[pathEntry] : null) ?? roleRules[entry]) : null;
if ((roleRule != null) && !localeEvalRoleRule(roleRule)) {
if ((roleRule != null) && !localeEvalRoleRule(roleRule, buildContext: buildContext)) {
return false;
}

Map<String, dynamic>? groupRules = rules['groups'];
dynamic groupRule = (groupRules != null) ? (((pathEntry != null) ? groupRules[pathEntry] : null) ?? groupRules[entry]) : null;
if ((groupRule != null) && !localeEvalGroupRule(groupRule)) {
if ((groupRule != null) && !localeEvalGroupRule(groupRule, buildContext: buildContext)) {
return false;
}

Map<String, dynamic>? locationRules = rules['locations'];
dynamic locationRule = (locationRules != null) ? (((pathEntry != null) ? locationRules[pathEntry] : null) ?? locationRules[entry]) : null;
if ((locationRule != null) && !localeEvalLocationRule(locationRule)) {
if ((locationRule != null) && !localeEvalLocationRule(locationRule, buildContext: buildContext)) {
return false;
}

Map<String, dynamic>? privacyRules = rules['privacy'];
dynamic privacyRule = (privacyRules != null) ? (((pathEntry != null) ? privacyRules[pathEntry] : null) ?? privacyRules[entry]) : null;
if ((privacyRule != null) && !localeEvalPrivacyRule(privacyRule)) {
if ((privacyRule != null) && !localeEvalPrivacyRule(privacyRule, buildContext: buildContext)) {
return false;
}

Map<String, dynamic>? authRules = rules['auth'];
dynamic authRule = (authRules != null) ? (((pathEntry != null) ? authRules[pathEntry] : null) ?? authRules[entry]) : null;
if ((authRule != null) && !localeEvalAuthRule(authRule)) {
if ((authRule != null) && !localeEvalAuthRule(authRule, buildContext: buildContext)) {
return false;
}

Map<String, dynamic>? platformRules = rules['platform'];
dynamic platformRule = (platformRules != null) ? (((pathEntry != null) ? platformRules[pathEntry] : null) ?? platformRules[entry]) : null;
if ((platformRule != null) && !localeEvalPlatformRule(platformRule)) {
if ((platformRule != null) && !localeEvalPlatformRule(platformRule, buildContext: buildContext)) {
return false;
}

Map<String, dynamic>? enableRules = rules['enable'];
dynamic enableRule = (enableRules != null) ? (((pathEntry != null) ? enableRules[pathEntry] : null) ?? enableRules[entry]) : null;
if ((enableRule != null) && !localeEvalEnableRule(enableRule)) {
if ((enableRule != null) && !localeEvalEnableRule(enableRule, buildContext: buildContext)) {
return false;
}

return true;
}

@protected
bool localeEvalRoleRule(dynamic roleRule) {
bool localeEvalRoleRule(dynamic roleRule, { FlexUiBuildContext? buildContext }) {
return BoolExpr.eval(roleRule, (dynamic argument) {
if (argument is String) {
bool? not, all, any;
Expand All @@ -463,7 +473,7 @@ class FlexUI with Service implements NotificationsListener {
argument = argument.substring(0, argument.length - 1);
}

Set<UserRole>? userRoles = localeEvalRolesSetParam(argument);
Set<UserRole>? userRoles = localeEvalRolesSetParam(argument, buildContext: buildContext);
if (userRoles != null) {
if (not == true) {
userRoles = Set.from(UserRole.values).cast<UserRole>().difference(userRoles);
Expand All @@ -485,7 +495,7 @@ class FlexUI with Service implements NotificationsListener {
}

@protected
Set<UserRole>? localeEvalRolesSetParam(String? roleParam) {
Set<UserRole>? localeEvalRolesSetParam(String? roleParam, { FlexUiBuildContext? buildContext }) {
if (roleParam != null) {
if (RegExp(r"\(.+\)").hasMatch(roleParam)) {
Set<UserRole> roles = <UserRole>{};
Expand All @@ -501,8 +511,8 @@ class FlexUI with Service implements NotificationsListener {
}
else if (RegExp(r"\${.+}").hasMatch(roleParam)) {
String stringRef = roleParam.substring(2, roleParam.length - 1);
String? stringRefValue = JsonUtils.stringValue(localeEvalStringReference(stringRef));
return localeEvalRolesSetParam(stringRefValue);
String? stringRefValue = JsonUtils.stringValue(localeEvalStringReference(stringRef, buildContext: buildContext));
return localeEvalRolesSetParam(stringRefValue, buildContext: buildContext);
}
else {
UserRole? userRole = UserRole.fromString(roleParam);
Expand All @@ -513,15 +523,15 @@ class FlexUI with Service implements NotificationsListener {
}

@protected
bool localeEvalGroupRule(dynamic groupRule) =>
localeEvalStringsSetExpr(groupRule, Groups().userGroupNames);
bool localeEvalGroupRule(dynamic groupRule, { FlexUiBuildContext? buildContext }) =>
localeEvalStringsSetExpr(groupRule, Groups().userGroupNames, buildContext: buildContext);

@protected
bool localeEvalLocationRule(dynamic locationRule) =>
localeEvalStringsSetExpr(locationRule, GeoFence().currentRegionIds);
bool localeEvalLocationRule(dynamic locationRule, { FlexUiBuildContext? buildContext }) =>
localeEvalStringsSetExpr(locationRule, GeoFence().currentRegionIds, buildContext: buildContext);

@protected
bool localeEvalStringsSetExpr(dynamic rule, Set<String>? allStrings) {
bool localeEvalStringsSetExpr(dynamic rule, Set<String>? allStrings, { FlexUiBuildContext? buildContext }) {
return BoolExpr.eval(rule, (dynamic argument) {
if (argument is String) {
bool? not, all, any;
Expand All @@ -535,7 +545,7 @@ class FlexUI with Service implements NotificationsListener {
argument = argument.substring(0, argument.length - 1);
}

Set<String>? targetStrings = localeEvalStringsSetParam(argument);
Set<String>? targetStrings = localeEvalStringsSetParam(argument, buildContext: buildContext);
if (targetStrings != null) {
if (not == true) {
targetStrings = allStrings?.difference(targetStrings) ?? <String>{};
Expand All @@ -557,16 +567,16 @@ class FlexUI with Service implements NotificationsListener {
}

@protected
Set<String>? localeEvalStringsSetParam(String? stringParam) {
Set<String>? localeEvalStringsSetParam(String? stringParam, { FlexUiBuildContext? buildContext }) {
if (stringParam != null) {
if (RegExp(r"\(.+\)").hasMatch(stringParam)) {
String stringsStr = stringParam.substring(1, stringParam.length - 1);
return Set<String>.from(stringsStr.split(',').map((e) => e.trim()));
}
else if (RegExp(r"\${.+}").hasMatch(stringParam)) {
String stringRef = stringParam.substring(2, stringParam.length - 1);
String? stringRefValue = JsonUtils.stringValue(localeEvalStringReference(stringRef));
return localeEvalStringsSetParam(stringRefValue);
String? stringRefValue = JsonUtils.stringValue(localeEvalStringReference(stringRef, buildContext: buildContext));
return localeEvalStringsSetParam(stringRefValue, buildContext: buildContext);
}
else {
return <String>{ stringParam };
Expand All @@ -576,16 +586,16 @@ class FlexUI with Service implements NotificationsListener {
}

@protected
bool localeEvalPrivacyRule(dynamic privacyRule) {
bool localeEvalPrivacyRule(dynamic privacyRule, { FlexUiBuildContext? buildContext }) {
return (privacyRule is int) ? Auth2().privacyMatch(privacyRule) : true; // allow everything that is not defined or we do not understand
}

@protected
bool localeEvalAuthRule(dynamic authRule) {
return BoolExpr.eval(authRule, (dynamic argument) => localeEvalAuthRuleParam(argument));
bool localeEvalAuthRule(dynamic authRule, { FlexUiBuildContext? buildContext }) {
return BoolExpr.eval(authRule, (dynamic argument) => localeEvalAuthRuleParam(argument, buildContext: buildContext));
}

bool? localeEvalAuthRuleParam(dynamic authParam) {
bool? localeEvalAuthRuleParam(dynamic authParam, { FlexUiBuildContext? buildContext }) {
bool result = true; // allow everything that is not defined or we do not understand
if (authParam is Map) {
authParam.forEach((dynamic key, dynamic value) {
Expand Down Expand Up @@ -621,10 +631,10 @@ class FlexUI with Service implements NotificationsListener {
result = result && (Auth2().isUsernameLinked == value);
}
else if (key == 'accountRole') {
result = result && localeEvalAccountRole(value);
result = result && localeEvalAccountRole(value, buildContext: buildContext);
}
else if (key == 'accountPermission') {
result = result && localeEvalAccountPermission(value);
result = result && localeEvalAccountPermission(value, buildContext: buildContext);
}
else if ((key == 'shibbolethMemberOf') && (value is String)) {
result = result && Auth2().isShibbolethMemberOf(value);
Expand All @@ -642,15 +652,15 @@ class FlexUI with Service implements NotificationsListener {
}

@protected
bool localeEvalAccountRole(dynamic accountRoleRule) =>
localeEvalStringsSetExpr(accountRoleRule, Auth2().account?.allRoleNames);
bool localeEvalAccountRole(dynamic accountRoleRule, { FlexUiBuildContext? buildContext }) =>
localeEvalStringsSetExpr(accountRoleRule, Auth2().account?.allRoleNames, buildContext: buildContext);

@protected
bool localeEvalAccountPermission(dynamic accountPermissionRule) =>
localeEvalStringsSetExpr(accountPermissionRule, Auth2().account?.allPermissionNames);
bool localeEvalAccountPermission(dynamic accountPermissionRule, { FlexUiBuildContext? buildContext } ) =>
localeEvalStringsSetExpr(accountPermissionRule, Auth2().account?.allPermissionNames, buildContext: buildContext);

@protected
bool localeEvalPlatformRule(dynamic platformRule) {
bool localeEvalPlatformRule(dynamic platformRule, { FlexUiBuildContext? buildContext }) {
bool result = true; // allow everything that is not defined or we do not understand
if (platformRule is Map) {
platformRule.forEach((dynamic key, dynamic value) {
Expand Down Expand Up @@ -689,18 +699,18 @@ class FlexUI with Service implements NotificationsListener {
}

@protected
bool localeEvalEnableRule(dynamic enableRule) {
bool localeEvalEnableRule(dynamic enableRule, { FlexUiBuildContext? buildContext }) {
return BoolExpr.eval(enableRule, (dynamic argument) {
return localeEvalBoolParam(argument);
return localeEvalBoolParam(argument, buildContext: buildContext);
});
}

@protected
bool? localeEvalBoolParam(dynamic stringParam) {
bool? localeEvalBoolParam(dynamic stringParam, { FlexUiBuildContext? buildContext }) {
if (stringParam is String) {
if (RegExp(r"\${.+}").hasMatch(stringParam)) {
String stringRef = stringParam.substring(2, stringParam.length - 1);
return JsonUtils.boolValue(localeEvalStringReference(stringRef));
return JsonUtils.boolValue(localeEvalStringReference(stringRef, buildContext: buildContext));
}
}
return null;
Expand All @@ -713,18 +723,29 @@ class FlexUI with Service implements NotificationsListener {
String get appStorageReferenceKey => 'app.storage';

@protected
dynamic localeEvalStringReference(String? stringRef) {
dynamic localeEvalStringReference(String? stringRef, { FlexUiBuildContext? buildContext }) {
if (stringRef != null) {
String configPrefix = '$configReferenceKey${MapPathKey.pathDelimiter}';
if (stringRef.startsWith(configPrefix)) {
return Config().pathEntry(stringRef.substring(configPrefix.length));
}
String appPrefix = '$appStorageReferenceKey${MapPathKey.pathDelimiter}';
if (stringRef.startsWith(appPrefix)) {
return Storage()[stringRef.substring(appPrefix.length)];
String storageKey = stringRef.substring(appPrefix.length);
buildContext?.storageKeys.add(storageKey);
return Storage()[storageKey];
}
}
return null;
}

}

class FlexUiBuildContext {
final Set<String> storageKeys; // referenced storage keys from data source

FlexUiBuildContext({
Set<String>? storageKeys,
}) :
this.storageKeys = storageKeys ?? <String>{};
}

0 comments on commit a8e305c

Please sign in to comment.