1
0
forked from firka/firka

login_screen: cache images

This commit is contained in:
2025-08-22 11:37:07 +02:00
parent 45cdefc5a8
commit 3327d5f2da
2 changed files with 130 additions and 7 deletions

View File

@@ -0,0 +1,86 @@
import 'dart:ui';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
// Taken from https://gist.github.com/darmawan01/9be266df44594ea59f07032e325ffa3b
// and adapted to use assets
final _globalImageCache = <String, Uint8List>{};
Future<void> precacheAsset(AssetBundle bundle, String asset) async {
if (!_globalImageCache.containsKey(asset)) {
final data = await bundle.load(asset);
_globalImageCache[asset] = data.buffer.asUint8List();
}
}
Future<void> precacheAssets(AssetBundle bundle, List<String> assets) async {
for (final asset in assets) {
await precacheAsset(bundle, asset);
}
}
Future<Uint8List> _cacheLoad(AssetBundle bundle, String asset) async {
if (!_globalImageCache.containsKey(asset)) {
final data = await bundle.load(asset);
_globalImageCache[asset] = data.buffer.asUint8List();
}
return Future.value(_globalImageCache[asset]!);
}
class CacheMemoryImageProvider extends ImageProvider<CacheMemoryImageProvider> {
final AssetBundle bundle;
final String path;
Uint8List? _img;
CacheMemoryImageProvider(this.bundle, this.path);
@override
ImageStreamCompleter loadImage(
CacheMemoryImageProvider key, ImageDecoderCallback decode) {
return MultiFrameImageStreamCompleter(
codec: _loadAsync(decode),
scale: 1.0,
debugLabel: path,
informationCollector: () sync* {
yield ErrorDescription('Tag: $path');
},
);
}
Future<Codec> _loadAsync(ImageDecoderCallback decode) async {
_img ??= await _cacheLoad(bundle, path);
// the DefaultCacheManager() encapsulation, it get cache from local storage.
final Uint8List bytes = _img!;
if (bytes.lengthInBytes == 0) {
// The file may become available later.
PaintingBinding.instance.imageCache.evict(this);
throw StateError('$path is empty and cannot be loaded as an image.');
}
final buffer = await ImmutableBuffer.fromUint8List(bytes);
return await decode(buffer);
}
@override
Future<CacheMemoryImageProvider> obtainKey(ImageConfiguration configuration) {
return SynchronousFuture<CacheMemoryImageProvider>(this);
}
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) return false;
bool res = other is CacheMemoryImageProvider && other.path == path;
return res;
}
@override
int get hashCode => path.hashCode;
@override
String toString() =>
'${objectRuntimeType(this, 'CacheImageProvider')}("$path")';
}

View File

@@ -4,6 +4,7 @@ import 'package:carousel_slider/carousel_slider.dart';
import 'package:firka/helpers/api/client/kreta_client.dart';
import 'package:firka/helpers/api/consts.dart';
import 'package:firka/helpers/db/models/token_model.dart';
import 'package:firka/helpers/firka_bundle.dart';
import 'package:firka/main.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
@@ -11,6 +12,7 @@ import 'package:flutter/services.dart';
import 'package:webview_flutter/webview_flutter.dart';
import '../../../../helpers/api/token_grant.dart';
import '../../../../helpers/cache_memory_image_provider.dart';
import '../../../model/style.dart';
import '../home/home_screen.dart';
@@ -26,6 +28,8 @@ class LoginScreen extends StatefulWidget {
class _LoginScreenState extends State<LoginScreen> {
late WebViewController _webViewController;
bool _preloadDone = false;
@override
void initState() {
super.initState();
@@ -87,6 +91,25 @@ class _LoginScreenState extends State<LoginScreen> {
statusBarColor: Colors.transparent,
systemNavigationBarColor: Color(0xFFFAFFF0),
));
() async {
final firkaBundle = FirkaBundle();
await precacheAssets(firkaBundle, [
"assets/images/carousel/slide1.png",
"assets/images/carousel/slide1_background.gif",
"assets/images/carousel/slide2.png",
"assets/images/carousel/slide2_background.gif",
"assets/images/carousel/slide3.png",
"assets/images/carousel/slide3_foreground.gif",
"assets/images/carousel/slide4.png",
"assets/images/carousel/slide4_background.gif"
]);
setState(() {
_preloadDone = true;
});
}();
}
@override
@@ -96,6 +119,12 @@ class _LoginScreenState extends State<LoginScreen> {
@override
Widget build(BuildContext context) {
if (!_preloadDone) {
return MaterialApp(
home: SizedBox(),
);
}
final paddingWidthHorizontal = MediaQuery.of(context).size.width -
MediaQuery.of(context).size.width * 0.95;
List<Map<String, Object>> slides = [
@@ -167,8 +196,9 @@ class _LoginScreenState extends State<LoginScreen> {
height: 30,
clipBehavior: Clip.antiAlias,
decoration: ShapeDecoration(
image: const DecorationImage(
image: AssetImage(
image: DecorationImage(
image: CacheMemoryImageProvider(
DefaultAssetBundle.of(context),
'assets/images/logos/colored_logo.png'),
fit: BoxFit.cover,
),
@@ -240,8 +270,11 @@ class _LoginScreenState extends State<LoginScreen> {
scale: slides[index]['scale']
as double,
child: Image(
image: AssetImage(slides[index]
['background']! as String),
image: CacheMemoryImageProvider(
DefaultAssetBundle.of(
context),
slides[index]['background']!
as String),
fit: BoxFit.contain,
width: double.infinity,
)),
@@ -257,7 +290,8 @@ class _LoginScreenState extends State<LoginScreen> {
child: SizedBox(
width: MediaQuery.of(context).size.width,
child: Image(
image: AssetImage(
image: CacheMemoryImageProvider(
DefaultAssetBundle.of(context),
slides[index]['picture']! as String),
fit: BoxFit.cover,
width: double.infinity,
@@ -285,8 +319,11 @@ class _LoginScreenState extends State<LoginScreen> {
scale: slides[index]['scale']
as double,
child: Image(
image: AssetImage(slides[index]
['foreground']! as String),
image: CacheMemoryImageProvider(
DefaultAssetBundle.of(
context),
slides[index]['foreground']!
as String),
fit: BoxFit.cover,
width: MediaQuery.of(context)
.size