Skip to content

Commit 59a05bc

Browse files
authored
Merge branch 'main' into android_TV_implementation
2 parents 30b28fa + 3015e9b commit 59a05bc

5 files changed

Lines changed: 131 additions & 35 deletions

File tree

modules/ensemble/lib/action/invoke_api_action.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:ensemble/framework/action.dart';
2+
import 'package:ensemble/framework/apiproviders/firestore/firestore_api_provider.dart';
23
import 'package:ensemble/framework/apiproviders/http_api_provider.dart';
34
import 'package:ensemble/framework/apiproviders/sse_api_provider.dart';
45
import 'package:ensemble/framework/bindings.dart';
@@ -295,7 +296,7 @@ class InvokeAPIController {
295296

296297
String? errorStr;
297298
dynamic data;
298-
if (errorResponse is HttpResponse) {
299+
if (errorResponse is HttpResponse || errorResponse is FirestoreResponse) {
299300
errorResponse.apiState = APIState.error;
300301
APIResponse apiResponse = APIResponse(response: errorResponse);
301302
scopeManager.dataContext.addInvokableContext('response', apiResponse);

modules/ensemble/lib/ensemble.dart

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import 'package:ensemble/framework/app_info.dart';
1818
import 'package:ensemble/framework/logging/console_log_provider.dart';
1919
import 'package:ensemble/framework/logging/log_manager.dart';
2020
import 'package:ensemble/framework/logging/log_provider.dart';
21+
import 'package:ensemble/framework/dotenv_bundle.dart';
2122
import 'package:ensemble/framework/ensemble_config_service.dart';
2223
import 'package:ensemble/framework/route_observer.dart';
2324
import 'package:ensemble/framework/scope.dart';
@@ -244,40 +245,13 @@ class Ensemble extends WithEnsemble with EnsembleRouteObserver {
244245

245246
Future<Map<String, String>> _loadDotEnvConfigFile(String fileName) async {
246247
try {
247-
final content = await rootBundle.loadString(fileName);
248-
return _parseDotEnvContent(content);
248+
final String content = await rootBundle.loadString(fileName);
249+
return parseDotEnvBundleContent(content);
249250
} catch (_) {
250251
return {};
251252
}
252253
}
253254

254-
Map<String, String> _parseDotEnvContent(String content) {
255-
final Map<String, String> values = {};
256-
for (String rawLine in content.split('\n')) {
257-
String line = rawLine.trim();
258-
if (line.isEmpty || line.startsWith('#')) {
259-
continue;
260-
}
261-
if (line.startsWith('export ')) {
262-
line = line.substring('export '.length).trim();
263-
}
264-
265-
final int equalIndex = line.indexOf('=');
266-
if (equalIndex <= 0) {
267-
continue;
268-
}
269-
270-
final String key = line.substring(0, equalIndex).trim();
271-
String value = line.substring(equalIndex + 1).trim();
272-
if ((value.startsWith('"') && value.endsWith('"')) ||
273-
(value.startsWith("'") && value.endsWith("'"))) {
274-
value = value.substring(1, value.length - 1);
275-
}
276-
values[key] = value;
277-
}
278-
return values;
279-
}
280-
281255
void _mergeWithOverwrite(
282256
Map<String, dynamic> target, Map<String, String> source) {
283257
source.forEach((key, value) {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import 'package:flutter_dotenv/flutter_dotenv.dart';
2+
3+
/// Parses `.env`-style text (e.g. from `rootBundle`) using [Parser] from
4+
/// `flutter_dotenv` — same rules as [LocalDefinitionProvider] and
5+
/// [Ensemble.initialize] for quoted values, `\\"`, and `=` inside values.
6+
Map<String, String> parseDotEnvBundleContent(String content) {
7+
const Parser parser = Parser();
8+
final Map<String, String> result = <String, String>{};
9+
for (final String rawLine in content.split(RegExp(r'\r?\n'))) {
10+
final String line = rawLine.replaceAll('\r', '');
11+
final Map<String, String> kv =
12+
parser.parseOne(line, envMap: Map<String, String>.from(result));
13+
if (kv.isNotEmpty) {
14+
final String key = kv.keys.single;
15+
final String value = kv.values.single;
16+
result[key] = value;
17+
}
18+
}
19+
return result;
20+
}

modules/ensemble/lib/framework/theme/theme_loader.dart

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:ensemble/framework/theme/theme_manager.dart';
44
import 'package:ensemble/framework/tv/tv_focus_theme.dart';
55
import 'package:ensemble/model/text_scale.dart';
66
import 'package:ensemble/util/utils.dart';
7+
import 'package:ensemble/widget/image.dart';
78
import 'package:flutter/material.dart';
89
import 'package:yaml/yaml.dart';
910

@@ -163,6 +164,9 @@ mixin ThemeLoader {
163164
maxFactor: Utils.optionalDouble(
164165
getProp(appOverrides, ['textScale', 'maxFactor']),
165166
min: 0)));
167+
168+
// Configure image cache settings from App.imageCache
169+
_configureImageCache(appOverrides);
166170

167171
// extends ThemeData
168172
return customTheme.copyWith(extensions: [
@@ -537,6 +541,32 @@ mixin ThemeLoader {
537541
}
538542
}
539543

544+
/// Configures image cache settings from App.imageCache in theme.yaml.
545+
///
546+
/// Example YAML:
547+
/// ```yaml
548+
/// App:
549+
/// imageCache:
550+
/// stalePeriodMinutes: 10080 # 7 days (default: 15 minutes)
551+
/// maxObjects: 500 # (default: 50)
552+
/// ```
553+
void _configureImageCache(YamlMap? appOverrides) {
554+
final imageCache = appOverrides?['imageCache'];
555+
if (imageCache == null) {
556+
return;
557+
}
558+
559+
final stalePeriodMinutes = Utils.optionalInt(imageCache['stalePeriodMinutes'], min: 1);
560+
final maxObjects = Utils.optionalInt(imageCache['maxObjects'], min: 1);
561+
562+
if (stalePeriodMinutes != null || maxObjects != null) {
563+
ImageCacheConfig().configure(
564+
stalePeriodMinutes: stalePeriodMinutes,
565+
maxObjects: maxObjects,
566+
);
567+
}
568+
}
569+
540570
// add more data to checkbox theme
541571
extension CheckboxThemeDataExtension on CheckboxThemeData {
542572
static int? _size;

modules/ensemble/lib/widget/image.dart

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -381,15 +381,86 @@ class ImageState extends EWidgetState<EnsembleImage> {
381381
}
382382
return fallbackWidget;
383383
}
384+
385+
}
386+
387+
/// Configuration for image caching behavior.
388+
/// Can be set at app level via theme.yaml:
389+
/// ```yaml
390+
/// App:
391+
/// imageCache:
392+
/// stalePeriodMinutes: 10080 # 7 days
393+
/// maxObjects: 500
394+
/// ```
395+
class ImageCacheConfig {
396+
static final ImageCacheConfig _instance = ImageCacheConfig._internal();
397+
398+
factory ImageCacheConfig() => _instance;
399+
400+
ImageCacheConfig._internal();
401+
402+
// Default values - 15 minutes stale period, 50 objects max
403+
static const int defaultStalePeriodMinutes = 15;
404+
static const int defaultMaxObjects = 50;
405+
406+
int _stalePeriodMinutes = defaultStalePeriodMinutes;
407+
int _maxObjects = defaultMaxObjects;
408+
bool _initialized = false;
409+
410+
/// Gets the configured stale period in minutes
411+
int get stalePeriodMinutes => _stalePeriodMinutes;
412+
413+
/// Gets the configured max number of cached objects
414+
int get maxObjects => _maxObjects;
415+
416+
/// Whether the cache config has been explicitly initialized
417+
bool get isInitialized => _initialized;
418+
419+
/// Configure the image cache settings.
420+
/// Called from theme loader when parsing App.imageCache settings.
421+
void configure({int? stalePeriodMinutes, int? maxObjects}) {
422+
if (stalePeriodMinutes != null && stalePeriodMinutes > 0) {
423+
_stalePeriodMinutes = stalePeriodMinutes;
424+
}
425+
if (maxObjects != null && maxObjects > 0) {
426+
_maxObjects = maxObjects;
427+
}
428+
_initialized = true;
429+
430+
// Reinitialize the cache manager with new settings
431+
EnsembleImageCacheManager._reinitialize();
432+
}
433+
434+
/// Reset to default values (useful for testing)
435+
void reset() {
436+
_stalePeriodMinutes = defaultStalePeriodMinutes;
437+
_maxObjects = defaultMaxObjects;
438+
_initialized = false;
439+
EnsembleImageCacheManager._reinitialize();
440+
}
384441
}
385442

386443
class EnsembleImageCacheManager {
387444
static const key = 'ensembleImageCacheKey';
388-
static CacheManager instance = CacheManager(Config(
389-
key,
390-
stalePeriod: const Duration(minutes: 15),
391-
maxNrOfCacheObjects: 50,
392-
));
445+
446+
static CacheManager _instance = _createCacheManager();
447+
448+
static CacheManager get instance => _instance;
449+
450+
static CacheManager _createCacheManager() {
451+
final config = ImageCacheConfig();
452+
return CacheManager(Config(
453+
key,
454+
stalePeriod: Duration(minutes: config.stalePeriodMinutes),
455+
maxNrOfCacheObjects: config.maxObjects,
456+
));
457+
}
458+
459+
/// Reinitialize the cache manager with current config settings.
460+
/// Called when ImageCacheConfig.configure() is called.
461+
static void _reinitialize() {
462+
_instance = _createCacheManager();
463+
}
393464
}
394465

395466
class PinchToZoom extends StatefulWidget {

0 commit comments

Comments
 (0)