diff --git a/CHANGELOG.md b/CHANGELOG.md index 734d2418..97737c6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +## [1.8.X] + + +### 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). diff --git a/lib/service/flex_ui.dart b/lib/service/flex_ui.dart index 2e61c3cc..5cf9bfb1 100644 --- a/lib/service/flex_ui.dart +++ b/lib/service/flex_ui.dart @@ -56,6 +56,7 @@ class FlexUI with Service implements NotificationsListener { Set? _defaultFeatures; Map>? _sourceAttributes; Map>? _defaultAttributes; + Set? _defaultStorageKeys; // Singletone Factory @@ -86,6 +87,7 @@ class FlexUI with Service implements NotificationsListener { Groups.notifyUserGroupsUpdated, GeoFence.notifyCurrentRegionsUpdated, Config.notifyConfigChanged, + Storage.notifySettingChanged, ]); } @@ -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(); } @@ -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? defaultContent = buildContent(defaultContentSourceEntry); + FlexUiBuildContext buildContext = FlexUiBuildContext(); + Map? 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; @@ -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 */)); } } @@ -360,7 +370,7 @@ class FlexUI with Service implements NotificationsListener { // Local Build @protected - Map? buildContent(Map? contentSource) { + Map? buildContent(Map? contentSource, { FlexUiBuildContext? buildContext }) { Map? result; if (contentSource != null) { Map contents = JsonUtils.mapValue(contentSource['content']) ?? {}; @@ -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; } @@ -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); } } @@ -399,49 +409,49 @@ class FlexUI with Service implements NotificationsListener { } @protected - bool localeIsEntryAvailable(String entry, { String? group, required Map rules }) { + bool localeIsEntryAvailable(String entry, { String? group, required Map rules, FlexUiBuildContext? buildContext }) { String? pathEntry = (group != null) ? '$group.$entry' : null; Map? 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? 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? 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? 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? 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? 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? 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; } @@ -449,7 +459,7 @@ class FlexUI with Service implements NotificationsListener { } @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; @@ -463,7 +473,7 @@ class FlexUI with Service implements NotificationsListener { argument = argument.substring(0, argument.length - 1); } - Set? userRoles = localeEvalRolesSetParam(argument); + Set? userRoles = localeEvalRolesSetParam(argument, buildContext: buildContext); if (userRoles != null) { if (not == true) { userRoles = Set.from(UserRole.values).cast().difference(userRoles); @@ -485,7 +495,7 @@ class FlexUI with Service implements NotificationsListener { } @protected - Set? localeEvalRolesSetParam(String? roleParam) { + Set? localeEvalRolesSetParam(String? roleParam, { FlexUiBuildContext? buildContext }) { if (roleParam != null) { if (RegExp(r"\(.+\)").hasMatch(roleParam)) { Set roles = {}; @@ -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); @@ -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? allStrings) { + bool localeEvalStringsSetExpr(dynamic rule, Set? allStrings, { FlexUiBuildContext? buildContext }) { return BoolExpr.eval(rule, (dynamic argument) { if (argument is String) { bool? not, all, any; @@ -535,7 +545,7 @@ class FlexUI with Service implements NotificationsListener { argument = argument.substring(0, argument.length - 1); } - Set? targetStrings = localeEvalStringsSetParam(argument); + Set? targetStrings = localeEvalStringsSetParam(argument, buildContext: buildContext); if (targetStrings != null) { if (not == true) { targetStrings = allStrings?.difference(targetStrings) ?? {}; @@ -557,7 +567,7 @@ class FlexUI with Service implements NotificationsListener { } @protected - Set? localeEvalStringsSetParam(String? stringParam) { + Set? localeEvalStringsSetParam(String? stringParam, { FlexUiBuildContext? buildContext }) { if (stringParam != null) { if (RegExp(r"\(.+\)").hasMatch(stringParam)) { String stringsStr = stringParam.substring(1, stringParam.length - 1); @@ -565,8 +575,8 @@ class FlexUI with Service implements NotificationsListener { } 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 { stringParam }; @@ -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) { @@ -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); @@ -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) { @@ -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; @@ -713,7 +723,7 @@ 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)) { @@ -721,10 +731,21 @@ class FlexUI with Service implements NotificationsListener { } 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 storageKeys; // referenced storage keys from data source + + FlexUiBuildContext({ + Set? storageKeys, + }) : + this.storageKeys = storageKeys ?? {}; +} \ No newline at end of file