11import 'dart:convert' ;
22import 'dart:io' ;
33
4+ import 'package:collection/collection.dart' ;
45import 'package:flutter/material.dart' ;
56import 'package:flutter/services.dart' ;
67import 'package:flutter_riverpod/flutter_riverpod.dart' ;
@@ -9,52 +10,90 @@ import 'package:intl/intl.dart' hide TextDirection;
910import 'package:intl/intl_standalone.dart' ;
1011import 'package:path/path.dart' as path;
1112import 'package:ubuntu_bootstrap/providers/slide_html.dart' ;
13+ import 'package:ubuntu_flavor/ubuntu_flavor.dart' ;
1214import 'package:ubuntu_logger/ubuntu_logger.dart' ;
1315import 'package:ubuntu_provision/ubuntu_provision.dart' ;
1416
1517final slidesProvider = Provider (
16- (ref) => SlidesModel (flavorName: ref.watch (flavorProvider).displayName),
18+ (ref) => SlidesModel (
19+ brightness: ref.watch (brightnessProvider),
20+ flavor: ref.watch (flavorProvider),
21+ ),
1722);
1823
1924final _log = Logger ('slides' );
2025
2126/// Pre-caches and holds the HTML slides.
2227class SlidesModel {
23- SlidesModel ({required this .flavorName});
24-
25- final String flavorName;
26- final List <SlideHtml > slides = [];
27-
28- SlideHtml ? get (int index) => slides[index];
28+ factory SlidesModel ({
29+ required Brightness brightness,
30+ required UbuntuFlavor flavor,
31+ }) =>
32+ _instance
33+ .._brightness = brightness
34+ .._flavor = flavor;
35+
36+ @visibleForTesting
37+ SlidesModel .internal ({
38+ required Brightness brightness,
39+ required UbuntuFlavor flavor,
40+ }) : _brightness = brightness,
41+ _flavor = flavor;
42+
43+ static final SlidesModel _instance = SlidesModel .internal (
44+ flavor: UbuntuFlavor .ubuntu,
45+ brightness: Brightness .light,
46+ );
47+
48+ Brightness _brightness;
49+ UbuntuFlavor _flavor;
50+ final List <SlideHtml > _slides = [];
51+ final List <SlideHtml > _slidesDark = [];
52+
53+ UnmodifiableListView <SlideHtml > get slides => UnmodifiableListView (
54+ switch (_brightness) {
55+ Brightness .light => _slides,
56+ Brightness .dark => _slidesDark,
57+ },
58+ );
2959
3060 Future <void > preCache () async {
31- final loadFutures = < Future <void >> [];
61+ _slides.addAll (await _loadSlides (Brightness .light));
62+ _slidesDark.addAll (await _loadSlides (Brightness .dark));
63+ }
64+
65+ Future <List <SlideHtml >> _loadSlides (Brightness brightness) async {
66+ final loadFutures = < Future <SlideHtml ?>> [];
3267 while (true ) {
3368 final index = loadFutures.length + 1 ;
3469 final slidePath = '${ConfigService .whiteLabelDirectory }slides/$index ' ;
3570 final slideDirectory = Directory (slidePath);
3671 if (! await slideDirectory.exists ()) {
3772 break ;
3873 }
39- loadFutures.add (_loadSlide (index));
74+ loadFutures.add (_loadSlide (index, brightness ));
4075 }
4176
4277 if (loadFutures.isEmpty) {
4378 _log.info ('No custom slides found, using default slides.' );
4479 while (true ) {
4580 final index = loadFutures.length + 1 ;
46- final slideFuture = _loadSlide (index, fromAssets: true );
81+ final slideFuture = _loadSlide (index, brightness, fromAssets: true );
4782 if (await slideFuture == null ) {
4883 break ;
4984 }
5085 loadFutures.add (slideFuture);
5186 }
5287 }
5388
54- slides. addAll (( await Future .wait (loadFutures)).whereType () );
89+ return ( await Future .wait (loadFutures)).nonNulls. toList ( );
5590 }
5691
57- Future <SlideHtml ?> _loadSlide (int index, {bool fromAssets = false }) async {
92+ Future <SlideHtml ?> _loadSlide (
93+ int index,
94+ Brightness brightness, {
95+ bool fromAssets = false ,
96+ }) async {
5897 const defaultLocale = 'en_US' ;
5998 final locale = Intl .defaultLocale ?? await findSystemLocale ();
6099 final locales = [locale, defaultLocale];
@@ -89,11 +128,12 @@ class SlidesModel {
89128 }
90129
91130 final flavorSpecificContent =
92- content.replaceAll ('{{ DISTRO }}' , flavorName );
131+ content.replaceAll ('{{ DISTRO }}' , _flavor.displayName );
93132
94133 final bundledHtml = await _replaceImages (
95134 flavorSpecificContent,
96135 index,
136+ brightness,
97137 fromAssets: fromAssets,
98138 );
99139
@@ -106,7 +146,8 @@ class SlidesModel {
106146 /// Replaces the image src attributes with base64 encoded images.
107147 Future <String > _replaceImages (
108148 String html,
109- int index, {
149+ int index,
150+ Brightness brightness, {
110151 bool fromAssets = false ,
111152 }) async {
112153 final document = parse (html);
@@ -116,11 +157,24 @@ class SlidesModel {
116157 if (src == null ) {
117158 continue ;
118159 }
160+ final srcDark = src.replaceFirstMapped (
161+ // first group matches everything except the file extension, second group
162+ // matches the file extension
163+ RegExp (r'(.*)(\.[^\.]*)' ),
164+ (m) => '${m [1 ]}-dark${m [2 ]}' ,
165+ );
119166
120167 final Uint8List imageBytes;
121168 final fileExtension = path.extension (src);
122169 if (fromAssets) {
123- final imagePath = 'assets/slides/$index /$src ' ;
170+ final basePath = 'assets/slides/$index /' ;
171+ final String imagePath;
172+ if (brightness == Brightness .dark &&
173+ await rootBundle.exists (basePath + srcDark)) {
174+ imagePath = basePath + srcDark;
175+ } else {
176+ imagePath = basePath + src;
177+ }
124178 try {
125179 final data = await rootBundle.load (imagePath);
126180 imageBytes = data.buffer.asUint8List ();
@@ -129,9 +183,15 @@ class SlidesModel {
129183 continue ;
130184 }
131185 } else {
132- final imageFile = File (
133- '${ConfigService .whiteLabelDirectory }slides/$index /$src ' ,
134- );
186+ final baseImagePath =
187+ '${ConfigService .whiteLabelDirectory }slides/$index /' ;
188+ final File imageFile;
189+ if (brightness == Brightness .dark &&
190+ await File (baseImagePath + srcDark).exists ()) {
191+ imageFile = File (baseImagePath + srcDark);
192+ } else {
193+ imageFile = File (baseImagePath + src);
194+ }
135195 if (! await imageFile.exists ()) {
136196 _log.error (
137197 'Error loading image $src from $imageFile : File does not exist.' ,
0 commit comments