Skip to content

Commit ce6beb7

Browse files
authored
feat: update mascot for resolute (#1324)
This includes the new artwork and also adds support for dark variants of image assets used in the slideshow. UDENG-9066
2 parents 8d5848c + dee8b54 commit ce6beb7

File tree

9 files changed

+234
-26
lines changed

9 files changed

+234
-26
lines changed
Lines changed: 67 additions & 0 deletions
Loading

apps/ubuntu_bootstrap/assets/slides/1/mascot.svg

Lines changed: 6 additions & 1 deletion
Loading

apps/ubuntu_bootstrap/lib/providers/slides_provider.dart

Lines changed: 78 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'dart:convert';
22
import 'dart:io';
33

4+
import 'package:collection/collection.dart';
45
import 'package:flutter/material.dart';
56
import 'package:flutter/services.dart';
67
import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -9,52 +10,90 @@ import 'package:intl/intl.dart' hide TextDirection;
910
import 'package:intl/intl_standalone.dart';
1011
import 'package:path/path.dart' as path;
1112
import 'package:ubuntu_bootstrap/providers/slide_html.dart';
13+
import 'package:ubuntu_flavor/ubuntu_flavor.dart';
1214
import 'package:ubuntu_logger/ubuntu_logger.dart';
1315
import 'package:ubuntu_provision/ubuntu_provision.dart';
1416

1517
final 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

1924
final _log = Logger('slides');
2025

2126
/// Pre-caches and holds the HTML slides.
2227
class 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.',

apps/ubuntu_bootstrap/test/install/test_install.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// ignore_for_file: prefer_const_constructors
22

3+
import 'dart:collection';
4+
35
import 'package:flutter/material.dart';
46
import 'package:flutter_riverpod/flutter_riverpod.dart';
57
import 'package:mockito/annotations.dart';
@@ -63,12 +65,12 @@ class MockSlidesModel extends Mock implements SlidesModel {
6365
Future<void> preCache() async {}
6466

6567
@override
66-
late final slides = [
68+
late final slides = UnmodifiableListView([
6769
for (var i = 1; i <= slideCount; i++)
6870
SlideHtml(
6971
'<html><body>slide_$i</body></html>',
7072
index: i,
7173
locale: 'en_US',
7274
),
73-
];
75+
]);
7476
}

0 commit comments

Comments
 (0)