-
Notifications
You must be signed in to change notification settings - Fork 26
Expand file tree
/
Copy pathdynamic_cached_fonts.dart
More file actions
620 lines (580 loc) · 23.5 KB
/
dynamic_cached_fonts.dart
File metadata and controls
620 lines (580 loc) · 23.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
/// An asset loader which dynamically loads font from the given url and caches it.
/// It can be easily fetched from cache and loaded on demand.
library dynamic_cached_fonts;
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'src/utils.dart';
export 'package:flutter_cache_manager/flutter_cache_manager.dart';
export 'src/utils.dart' show cacheKeyFromUrl;
part 'src/raw_dynamic_cached_fonts.dart';
/// Allows dynamically loading fonts from the given url.
///
/// For ready to use, simple interface, initialize [DynamicCachedFonts] and call
/// the [load] method either in you `initState()` or just before `runApp()` itself.
///
/// ```dart
/// class _SomeStateFulWidgetState extends State<_SomeStateFulWidget> {
/// @override
/// void initState() {
/// const DynamicCachedFonts dynamicCachedFont = DynamicCachedFonts(
/// fontFamily: 'font',
/// url: // Add url to a font
/// );
/// dynamicCachedFont.load();
///
/// super.initState();
/// }
/// ```
///
/// ```dart
/// void main() async {
/// final DynamicCachedFont dynamicCachedFont = DynamicCachedFont(
/// fontFamily: 'font',
/// url: // Add url to a font
/// );
/// dynamicCachedFont.load();
///
/// runApp(
/// YourAppHere(
/// ...
/// TextStyle(
/// fontFamily: 'font'
/// ),
/// ...
/// ),
/// );
/// }
/// ```
///
/// For greater customization, use static methods.
///
/// - [cacheFont] to download a font file and cache it.
/// - [canLoadFont] to check whether a font is available
/// and can be used by calling [loadCachedFont].
/// - [loadCachedFont] to load a downloaded font.
/// - [removeCachedFont] to delete the font file.
class DynamicCachedFonts {
/// Allows dynamically loading fonts from the given url and caching them.
///
/// - **REQUIRED** The [url] property is used to specify the download url
/// for the required font. It should be a valid http/https url which points to
/// a font file.
/// Currently, only OpenType (OTF) and TrueType (TTF) fonts are supported.
///
/// - **REQUIRED** The [fontFamily] property is used to specify the name
/// of the font family which is to be used as [TextStyle.fontFamily].
///
/// - The [maxCacheObjects] property defines how large the cache is allowed to be.
/// If there are more files the files that haven't been used for the longest
/// time will be removed.
///
/// It is used to specify the cache configuration, [Config],
/// for [CacheManager].
///
/// - [cacheStalePeriod] is the time duration in which
/// a cache object is considered 'stale'. When a file is cached but
/// not being used for a certain time the file will be deleted
///
/// Defaults to 365 days.
///
/// It is used to specify the cache configuration, [Config],
/// for [CacheManager].
DynamicCachedFonts({
required String url,
required this.fontFamily,
this.maxCacheObjects = kDefaultMaxCacheObjects,
this.cacheStalePeriod = kDefaultCacheStalePeriod,
}) : assert(
fontFamily != '',
'fontFamily cannot be empty',
),
assert(
url != '',
'url cannot be empty',
),
urls = <String>[url],
_loaded = false;
/// Allows dynamically loading fonts from the given list of url and caching them.
/// The [fontFamily] groups a series of related font assets, each of which defines
/// how to render a specific [FontWeight] and [FontStyle] within the family.
///
/// - **REQUIRED** The [urls] property is used to specify the download urls
/// for the required fonts. It should be a list of valid http/https urls
/// which point to font files.
///
/// Currently, only OpenType (OTF) and TrueType (TTF) fonts are supported.
///
/// - **REQUIRED** The [fontFamily] property is used to specify the name
/// of the font family which is to be used as [TextStyle.fontFamily].
///
/// - The [maxCacheObjects] property defines how large the cache is allowed to be.
/// If there are more files the files that haven't been used for the longest
/// time will be removed.
///
/// It is used to specify the cache configuration, [Config],
/// for [CacheManager].
///
/// - [cacheStalePeriod] is the time duration in which
/// a cache object is considered 'stale'. When a file is cached but
/// not being used for a certain time the file will be deleted
///
/// Defaults to 365 days.
///
/// It is used to specify the cache configuration, [Config],
/// for [CacheManager].
DynamicCachedFonts.family({
required this.urls,
required this.fontFamily,
this.maxCacheObjects = kDefaultMaxCacheObjects,
this.cacheStalePeriod = kDefaultCacheStalePeriod,
}) : assert(
fontFamily != '',
'fontFamily cannot be empty',
),
assert(
urls.length > 1,
'At least 2 urls have to be provided. To load a single font url, use the default constructor',
),
assert(
urls.every(
(String url) => url != '',
),
'url cannot be empty',
),
_loaded = false;
/// Used to specify the download url(s) for the required font(s).
///
/// It should be a valid http/https url which points to a font file.
///
/// Currently, only OpenType (OTF) and TrueType (TTF) fonts are supported.
///
/// To load multiple fonts in a family, use [DynamicCachedFonts.family].
final List<String> urls;
/// Used to specify the name of the font family
/// which is to be used as [TextStyle.fontFamily].
final String fontFamily;
/// Defines how large the cache is allowed to be.
///
/// If there are more files the files that haven't been used for the longest
/// time will be removed.
///
/// It is used to specify the cache configuration, [Config],
/// for [CacheManager].
final int maxCacheObjects;
/// The time duration in which
/// a cache object is considered 'stale'. When a file is cached but
/// not being used for a certain time the file will be deleted
///
/// Defaults to 365 days.
///
/// It is used to specify the cache configuration, [Config],
/// for [CacheManager].
final Duration cacheStalePeriod;
/// Checks whether [load] has already been called.
bool _loaded;
/// Used to download and load a font into the
/// app with the given [urls] and cache configuration.
///
/// This method can be called in `main()`, `initState()` or on button tap/click
/// as needed.
Future<Iterable<FileInfo>> load() async {
if (_loaded) throw StateError('Font has already been loaded');
_loaded = true;
WidgetsFlutterBinding.ensureInitialized();
final List<String> downloadUrls = urls;
Iterable<FileInfo> fontFiles;
try {
fontFiles = await loadCachedFamily(
downloadUrls,
fontFamily: fontFamily,
maxCacheObjects: maxCacheObjects,
cacheStalePeriod: cacheStalePeriod,
);
// Checks whether any of the files is invalid.
// The validity is determined by parsing headers returned when the file was
// requested. The date/time is a file validity guarantee by the source.
// This was done to preserve `Cachemanager.getSingleFile`'s behaviour.
fontFiles
.where((FileInfo font) => font.validTill.isBefore(DateTime.now()))
.forEach((FileInfo font) => cacheFont(
font.originalUrl,
cacheStalePeriod: cacheStalePeriod,
maxCacheObjects: maxCacheObjects,
));
} catch (_) {
devLog(<String>['Font is not in cache.', 'Loading font now...']);
for (final String url in downloadUrls) {
await cacheFont(
url,
cacheStalePeriod: cacheStalePeriod,
maxCacheObjects: maxCacheObjects,
);
}
fontFiles = await loadCachedFamily(
downloadUrls,
fontFamily: fontFamily,
maxCacheObjects: maxCacheObjects,
cacheStalePeriod: cacheStalePeriod,
);
}
return fontFiles;
}
/// Used to download and load a font into the app with the given [urls] and
/// cache configuration. A stream of [FileInfo] object(s) is returned which
/// emits the font files as they are loaded. The total number of files returned
/// corresponds to the number of urls provided.
///
/// - The [itemCountProgressListener] property is used to listen to the download
/// progress of the fonts. It gives information about the number of files
/// downloaded and the total number of files to be downloaded.
/// It is called with
///
/// a [double] value which indicates the fraction of items that have been downloaded,
/// an [int] value which indicates the total number of items to be downloaded and,
/// another [int] value which indicates the number of items that have been
/// downloaded so far.
///
/// - The [downloadProgressListener] property is used to listen to the download
/// progress of the font. It is called with a [DownloadProgress] object which
/// contains information about the download progress.
Stream<FileInfo> loadStream({
ItemCountProgressListener? itemCountProgressListener,
DownloadProgressListener? downloadProgressListener,
}) async* {
if (_loaded) throw StateError('Font has already been loaded');
_loaded = true;
WidgetsFlutterBinding.ensureInitialized();
final List<String> downloadUrls = urls;
final Stream<FileInfo> fontStream = loadCachedFamilyStream(
downloadUrls,
fontFamily: fontFamily,
progressListener: itemCountProgressListener,
maxCacheObjects: maxCacheObjects,
cacheStalePeriod: cacheStalePeriod,
);
try {
await for (final font in fontStream) {
yield font;
// Checks whether any of the files is invalid.
// The validity is determined by parsing headers returned when the file was
// requested. The date/time is a file validity guarantee by the source.
// This was done to preserve `Cachemanager.getSingleFile`'s behaviour.
if (font.validTill.isBefore(DateTime.now())) {
devLog([
'Font file expired on ${font.validTill}.',
'Downloading font from ${font.originalUrl}...',
]);
cacheFontStream(
font.originalUrl,
cacheStalePeriod: cacheStalePeriod,
maxCacheObjects: maxCacheObjects,
progressListener: downloadProgressListener,
).listen((_) {});
}
}
} catch (_) {
devLog(<String>['Font is not in cache.', 'Loading font now...']);
await Future.wait([
for (final String url in downloadUrls)
cacheFontStream(
url,
cacheStalePeriod: cacheStalePeriod,
maxCacheObjects: maxCacheObjects,
progressListener: downloadProgressListener,
).listen((_) {}).asFuture<void>()
]);
yield* loadCachedFamilyStream(
downloadUrls,
fontFamily: fontFamily,
progressListener: itemCountProgressListener,
maxCacheObjects: maxCacheObjects,
cacheStalePeriod: cacheStalePeriod,
);
}
}
/// Accepts [cacheManager] and [force] to provide a custom [CacheManager] for testing.
///
/// - **REQUIRED** The [cacheManager] property is used to specify a custom instance of
/// [CacheManager]. Caching can be customized using the [Config] object passed to
/// the instance.
///
/// - The [force] property is used to specify whether or not to overwrite an existing
/// instance of custom cache manager.
///
/// If [force] is true and a custom cache manager already exists, it will be
/// overwritten with the new instance. This means any fonts cached earlier,
/// cannot be accessed using the new instance.
/// ---
/// Any new [DynamicCachedFonts] instance or any [RawDynamicCachedFonts] methods
/// called after this method will use [cacheManager] to download, cache
/// and load fonts. This means custom configuration **cannot** be provided.
///
/// [maxCacheObjects] and [cacheStalePeriod] will have no effect after calling
/// this method. Customize these values in the [Config] object passed to the
/// [CacheManager] used in [cacheManager].
@visibleForTesting
static void custom({
required CacheManager cacheManager,
bool force = false,
}) =>
RawDynamicCachedFonts.custom(
cacheManager: cacheManager,
force: force,
);
/// Downloads and caches font from the [url] with the given configuration.
///
/// - **REQUIRED** The [url] property is used to specify the download url
/// for the required font. It should be a valid http/https url which points to
/// a font file.
/// Currently, only OpenType (OTF) and TrueType (TTF) fonts are supported.
///
/// - The [maxCacheObjects] property defines how large the cache is allowed to be.
/// If there are more files the files that haven't been used for the longest
/// time will be removed.
///
/// It is used to specify the cache configuration, [Config],
/// for [CacheManager].
///
/// - [cacheStalePeriod] is the time duration in which
/// a cache object is considered 'stale'. When a file is cached but
/// not being used for a certain time the file will be deleted
///
/// Defaults to 365 days.
///
/// It is used to specify the cache configuration, [Config],
/// for [CacheManager].
static Future<FileInfo> cacheFont(
String url, {
Duration cacheStalePeriod = kDefaultCacheStalePeriod,
int maxCacheObjects = kDefaultMaxCacheObjects,
}) =>
RawDynamicCachedFonts.cacheFont(
url,
cacheStalePeriod: cacheStalePeriod,
maxCacheObjects: maxCacheObjects,
);
/// Downloads and caches font from the [url] with the given configuration.
/// A single [FileInfo] object is returned as a stream and the download
/// progress is can be listened to using the [progressListener] callback.
///
/// If this method is called multiple times for the same font [url], then the
/// cached file is returned with no progress events being emitted to the
/// [progressListener] callback.
///
/// - **REQUIRED** The [url] property is used to specify the download url
/// for the required font. It should be a valid http/https url which points to
/// a font file.
/// Currently, only OpenType (OTF) and TrueType (TTF) fonts are supported.
///
/// - The [maxCacheObjects] property defines how large the cache is allowed to be.
/// If there are more files the files that haven't been used for the longest
/// time will be removed.
///
/// It is used to specify the cache configuration, [Config],
/// for [CacheManager].
///
/// - [cacheStalePeriod] is the time duration in which
/// a cache object is considered 'stale'. When a file is cached but
/// not being used for a certain time the file will be deleted
///
/// It is used to specify the cache configuration, [Config],
/// for [CacheManager].
///
/// - The [progressListener] property is used to listen to the download progress
/// of the font. It is called with a [DownloadProgress] object which contains
/// information about the download progress.
static Stream<FileInfo> cacheFontStream(
String url, {
Duration cacheStalePeriod = kDefaultCacheStalePeriod,
int maxCacheObjects = kDefaultMaxCacheObjects,
DownloadProgressListener? progressListener,
}) =>
RawDynamicCachedFonts.cacheFontStream(
url,
cacheStalePeriod: cacheStalePeriod,
maxCacheObjects: maxCacheObjects,
progressListener: progressListener,
);
/// Checks whether the given [url] can be loaded directly from cache.
///
/// - **REQUIRED** The [url] property is used to specify the url
/// for the required font. It should be a valid http/https url which points to
/// a font file. The [url] should match the url passed to [cacheFont].
///
/// - The [maxCacheObjects] property should match the value passed to [cacheFont].
///
/// - The [cacheStalePeriod] property should match the value passed to [cacheFont].
static Future<bool> canLoadFont(
String url, {
int maxCacheObjects = kDefaultMaxCacheObjects,
Duration cacheStalePeriod = kDefaultCacheStalePeriod,
}) =>
RawDynamicCachedFonts.canLoadFont(
url,
maxCacheObjects: maxCacheObjects,
cacheStalePeriod: cacheStalePeriod,
);
/// Fetches the given [url] from cache and loads it as an asset.
///
/// - **REQUIRED** The [url] property is used to specify the url
/// for the required font. It should be a valid http/https url which points to
/// a font file. The [url] should match the url passed to [cacheFont].
///
/// - **REQUIRED** The [fontFamily] property is used to specify the name
/// of the font family which is to be used as [TextStyle.fontFamily].
///
/// - The [maxCacheObjects] property should match the value passed to [cacheFont].
///
/// - The [cacheStalePeriod] property should match the value passed to [cacheFont].
static Future<FileInfo> loadCachedFont(
String url, {
required String fontFamily,
int maxCacheObjects = kDefaultMaxCacheObjects,
Duration cacheStalePeriod = kDefaultCacheStalePeriod,
@visibleForTesting FontLoader? fontLoader,
}) =>
RawDynamicCachedFonts.loadCachedFont(
url,
fontFamily: fontFamily,
maxCacheObjects: maxCacheObjects,
cacheStalePeriod: cacheStalePeriod,
fontLoader: fontLoader,
);
/// Fetches the given [urls] from cache and loads them into the engine to be used.
///
/// [urls] should be a series of related font assets,
/// each of which defines how to render a specific [FontWeight] and [FontStyle]
/// within the family.
///
/// Call [canLoadFont] before calling this method to make sure the font is
/// available in cache.
///
/// - **REQUIRED** The [urls] property is used to specify the urls
/// for the required family. It should be a list of valid http/https urls
/// which point to font files.
/// Every url in [urls] should be loaded into cache by calling [cacheFont] for each.
///
/// - **REQUIRED** The [fontFamily] property is used to specify the name
/// of the font family which is to be used as [TextStyle.fontFamily].
///
/// - The [maxCacheObjects] property should match the value passed to [cacheFont].
///
/// - The [cacheStalePeriod] property should match the value passed to [cacheFont].
static Future<Iterable<FileInfo>> loadCachedFamily(
List<String> urls, {
required String fontFamily,
int maxCacheObjects = kDefaultMaxCacheObjects,
Duration cacheStalePeriod = kDefaultCacheStalePeriod,
@visibleForTesting FontLoader? fontLoader,
}) =>
RawDynamicCachedFonts.loadCachedFamily(
urls,
fontFamily: fontFamily,
maxCacheObjects: maxCacheObjects,
cacheStalePeriod: cacheStalePeriod,
fontLoader: fontLoader,
);
/// Fetches the given [urls] from cache and loads them into the engine to be used.
/// A stream of [FileInfo] objects is returned, which emits the font files as they
/// are loaded. The total number of files returned corresponds to the number of
/// urls provided. The download progress can be listened to using [progressListener].
///
/// [urls] should be a series of related font assets,
/// each of which defines how to render a specific [FontWeight] and [FontStyle]
/// within the family.
///
/// Call [canLoadFont] before calling this method to make sure the font is
/// available in cache.
///
/// - **REQUIRED** The [urls] property is used to specify the urls
/// for the required family. It should be a list of valid http/https urls
/// which point to font files.
/// Every url in [urls] should be loaded into cache by calling [cacheFont] for each.
///
/// - **REQUIRED** The [fontFamily] property is used to specify the name
/// of the font family which is to be used as [TextStyle.fontFamily].
///
/// - The [progressListener] property is used to listen to the download progress
/// of the font. It gives information about the number of files
/// downloaded and the total number of files to be downloaded.
/// It is called with
///
/// a [double] value which indicates the fraction of items that have been downloaded,
/// an [int] value which indicates the total number of items to be downloaded and,
/// another [int] value which indicates the number of items that have been
/// downloaded so far.
static Stream<FileInfo> loadCachedFamilyStream(
List<String> urls, {
required String fontFamily,
ItemCountProgressListener? progressListener,
int maxCacheObjects = kDefaultMaxCacheObjects,
Duration cacheStalePeriod = kDefaultCacheStalePeriod,
@visibleForTesting FontLoader? fontLoader,
}) =>
RawDynamicCachedFonts.loadCachedFamilyStream(
urls,
fontFamily: fontFamily,
progressListener: progressListener,
maxCacheObjects: maxCacheObjects,
cacheStalePeriod: cacheStalePeriod,
fontLoader: fontLoader,
);
/// Removes the given [url] can be loaded directly from cache.
///
/// - **REQUIRED** The [url] property is used to specify the url
/// for the required font. It should be a valid http/https url which points to
/// a font file. The [url] should match the url passed to [cacheFont].
///
/// - The [maxCacheObjects] property should match the value passed to [cacheFont].
///
/// - The [cacheStalePeriod] property should match the value passed to [cacheFont].
static Future<void> removeCachedFont(
String url, {
int maxCacheObjects = kDefaultMaxCacheObjects,
Duration cacheStalePeriod = kDefaultCacheStalePeriod,
}) =>
RawDynamicCachedFonts.removeCachedFont(
url,
maxCacheObjects: maxCacheObjects,
cacheStalePeriod: cacheStalePeriod,
);
/// Used to specify whether detailed logs should be printed for debugging.
///
/// Logging is disabled by default.
///
/// Call this method before any other `DynamicCachedFonts.*` or `RawDynamicCachedFonts.*`
/// method(s) to enable logging.
/// Once this method is called with false, any command called after that won't log.
///
/// ```dart
/// DynamicCachedFonts.toggleVerboseLogging(true);
/// ... // Any command called here will log results.
/// DynamicCachedFonts.toggleVerboseLogging(false);
/// ... // Any command called here won't log results.
/// ```
static void toggleVerboseLogging(bool shouldVerboseLog) {
Utils.shouldVerboseLog = shouldVerboseLog;
devLog(
['${shouldVerboseLog ? 'Enabled' : 'Disabled'} verbose logging'],
overrideLoggerConfig: true,
);
}
/// ### MIGRATION TOOL FOR v2
///
/// Deletes any cache files downloaded using the default cache manager.
/// v2 creates separate folders for each font file.
///
/// **WARNING: Any fonts downloaded using v1 of this package ( without a custom
/// `cacheStalePeriod` or `maxCacheObjects` ) will be deleted.**
///
/// Can be placed before or after other [DynamicCachedFonts] methods. The tool
/// is required to be run only once but multiple executions will have no side
/// effects. An empty folder named 'DynamicCachedFontsFontCacheKey' will be
/// present in the cache folder after running this tool.
///
/// Sample Usage: Users may add this to the next version of their app and
/// remove it in the next version. The purpose is to ensure atleast 1 execution
/// of the tool. Subsequent runs will be useless.
static Future<void> runMigrationTool() => migrationTool();
}