Skip to content

Commit

Permalink
Add 32 languages
Browse files Browse the repository at this point in the history
Remote URL also changed. Add some more error handling and tests
  • Loading branch information
holybiber committed Sep 20, 2023
1 parent 53b7933 commit 7b2cea7
Show file tree
Hide file tree
Showing 17 changed files with 159 additions and 54 deletions.
67 changes: 58 additions & 9 deletions lib/data/globals.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,67 @@ class ProviderNotOverriddenException implements Exception {

/// global constants
class Globals {
static const List<String> availableLanguages = ["en", "de"];
static const List<String> availableLanguages = [
'tr',
'zh',
'vi',
'ro',
'ky',
'pl',
'id',
'xh',
'uz',
'af',
'ta',
'sr',
'ms',
'az',
'ti',
'sw',
'nb',
'ku',
'sv',
'ml',
'hi',
'lg',
'kn',
'it',
'cs',
'fa',
'ar',
'ru',
'nl',
'fr',
'es',
'sq',
'en',
'de'
];

/// Which page is loaded after startup?
static const String defaultPage = "God's_Story_(five_fingers)";

/// Remote Repository
static const String urlStart = "https://github.com/holybiber/test-html-";
static const String urlEnd = "/archive/refs/heads/main.zip";
static const String pathStart = "/test-html-";
static const String pathEnd = "-main";

static const String latestCommitsStart =
"https://api.github.com/repos/holybiber/test-html-";
static const String latestCommitsEnd = "/commits?since=";
static const String githubUser = '4training';
static const String branch = 'main';
static const String htmlPath = 'html';
static const String remoteZipUrl = '/archive/refs/heads/$branch.zip';

/// Url of the zip file for the HTML resources of a language
static String getRemoteUrl(String languageCode) {
return 'https://github.com/$githubUser/$htmlPath-$languageCode$remoteZipUrl';
}

/// File system path (relative to assets directory)
/// of the resources in a language
/// Must be the main folder name that is inside the zip file we download
static String getLocalPath(String languageCode) {
return '$htmlPath-$languageCode-$branch';
}

/// Url of Github API: have been commits since [timestamp]?
static String getCommitsSince(String languageCode, DateTime timestamp) {
return 'https://api.github.com/repos/$githubUser/$htmlPath-$languageCode'
'/commits?since=${timestamp.toIso8601String()}';
}
}
61 changes: 33 additions & 28 deletions lib/data/languages.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ typedef Resource = ({String name, String langCode});

/// Provide image data (base64-encoded)
/// Returns empty string in case something went wrong
/// TODO improve error handling
final imageContentProvider = Provider.family<String, Resource>((ref, res) {
final String path = ref.watch(languageProvider(res.langCode)).path;
if (path == '') {
Expand All @@ -26,14 +25,18 @@ final imageContentProvider = Provider.family<String, Resource>((ref, res) {
return '';
}
final fileSystem = ref.watch(fileSystemProvider);
File image = fileSystem.file(join(path, 'files', res.name));
debugPrint("Successfully loaded ${res.name}");
return base64Encode(image.readAsBytesSync());
try {
File image = fileSystem.file(join(path, 'files', res.name));
debugPrint('Successfully loaded ${res.name}');
return base64Encode(image.readAsBytesSync());
} on FileSystemException catch (e) {
debugPrint("Couldn't load ${res.name}: $e");
return '';
}
});

/// Provide HTML content of a specific page in a specific language
/// Returns empty string in case something went wrong
/// TODO: improve error handling
final pageContentProvider =
FutureProvider.family<String, Resource>((ref, page) async {
final fileSystem = ref.watch(fileSystemProvider);
Expand All @@ -50,24 +53,28 @@ final pageContentProvider =
}

debugPrint("Fetching content of '${page.name}/${page.langCode}'...");
String content = await fileSystem
.file(join(lang.path, pageDetails.fileName))
.readAsString();

// Load images directly into the HTML:
// Replace <img src="xyz.png"> with <img src="base64-encoded image data">
content =
content.replaceAllMapped(RegExp(r'src="files/([^.]+.png)"'), (match) {
if (!lang.images.containsKey(match.group(1))) {
debugPrint(
'Warning: image ${match.group(1)} missing (in ${pageDetails.fileName})');
return match.group(0)!;
}
String imageData = ref.watch(
imageContentProvider((name: match.group(1)!, langCode: page.langCode)));
return 'src="data:image/png;base64,$imageData"';
});
return content;
try {
String content = await fileSystem
.file(join(lang.path, pageDetails.fileName))
.readAsString();
// Load images directly into the HTML:
// Replace <img src="xyz.png"> with <img src="base64-encoded image data">
content =
content.replaceAllMapped(RegExp(r'src="files/([^.]+.png)"'), (match) {
if (!lang.images.containsKey(match.group(1))) {
debugPrint(
'Warning: image ${match.group(1)} missing (in ${pageDetails.fileName})');
return match.group(0)!;
}
String imageData = ref.watch(imageContentProvider(
(name: match.group(1)!, langCode: page.langCode)));
return 'src="data:image/png;base64,$imageData"';
});
return content;
} on FileSystemException catch (e) {
debugPrint(e.toString());
return "Internal error: Couldn't read page";
}
});

/// Usage:
Expand Down Expand Up @@ -101,10 +108,8 @@ class LanguageController extends FamilyNotifier<Language, String> {

try {
// Now we store the full path to the language
String path = _controller.assetsDir! +
Globals.pathStart +
languageCode +
Globals.pathEnd;
String path =
'${_controller.assetsDir!}/${Globals.getLocalPath(languageCode)}';
debugPrint("Path: $path");
Directory dir = fileSystem.directory(path);

Expand Down Expand Up @@ -171,7 +176,7 @@ class LanguageController extends FamilyNotifier<Language, String> {
Future _download() async {
debugPrint("Starting downloadLanguage: $languageCode ...");
// URL of the zip file to be downloaded
String remoteUrl = Globals.urlStart + languageCode + Globals.urlEnd;
String remoteUrl = Globals.getRemoteUrl(languageCode);

await _controller.startDownload(
assetsUrls: [remoteUrl],
Expand Down
6 changes: 2 additions & 4 deletions lib/data/updates.dart
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,8 @@ class LanguageStatusNotifier extends FamilyNotifier<LanguageStatus, String> {
Future<int> check() async {
assert(_languageCode != '');
// since = since.subtract(const Duration(days: 100)); // for testing
var uri = Globals.latestCommitsStart +
_languageCode +
Globals.latestCommitsEnd +
state.lastCheckedTimestamp.toIso8601String();
var uri =
Globals.getCommitsSince(_languageCode, state.lastCheckedTimestamp);
debugPrint(uri);
final response = await http.get(Uri.parse(uri));

Expand Down
6 changes: 3 additions & 3 deletions lib/widgets/main_drawer.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:collection';

import 'package:app4training/data/globals.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:app4training/widgets/upward_expansion_tile.dart';
Expand Down Expand Up @@ -58,9 +59,8 @@ class LanguageSelection extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
List<ListTile> allLanguages = [];

for (var language in ['de', 'en']) {
// TODO context.global.languages) {
// TODO if (!language.downloaded) continue;
for (var language in Globals.availableLanguages) {
if (!ref.watch(languageProvider(language)).downloaded) continue;
String title = language.toUpperCase();
allLanguages.add(ListTile(
title: Text(title, style: Theme.of(context).textTheme.labelMedium),
Expand Down
27 changes: 27 additions & 0 deletions test/assets-de/html-de-main/structure/contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"language_code": "de",
"english_name": "German",
"worksheets": [
{
"page": "God's_Story_(five_fingers)",
"title": "Gottes Geschichte (f\u00fcnf Finger)",
"filename": "Gottes_Geschichte_(f\u00fcnf_Finger).html",
"version": "2.1",
"pdf": "Gottes_Geschichte_(f\u00fcnf_Finger).pdf"
},
{
"page": "Forgiving_Step_by_Step",
"title": "Schritte der Vergebung",
"filename": "Schritte_der_Vergebung.html",
"version": "1.3",
"pdf": "Schritte_der_Vergebung.pdf"
},
{
"page": "MissingTest",
"title": "MissingTest",
"filename": "MissingTest.html",
"version": "1.0",
"pdf": "MissingTest.pdf"
}
]
}
1 change: 0 additions & 1 deletion test/assets-de/test-html-de-main/structure/contents.json

This file was deleted.

16 changes: 16 additions & 0 deletions test/globals_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import 'package:app4training/data/globals.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
test('Test remote URLs and local path', () {
expect(
Globals.getRemoteUrl('de'),
equals(
'https://github.com/4training/html-de/archive/refs/heads/main.zip'));
expect(Globals.getLocalPath('de'), equals('html-de-main'));
expect(
Globals.getCommitsSince('de', DateTime(2023)),
equals(
'https://api.github.com/repos/4training/html-de/commits?since=2023-01-01T00:00:00.000'));
});
}
5 changes: 3 additions & 2 deletions test/languages_table_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class TestLanguageStatusNotifier extends LanguageStatusNotifier {
}

void main() {
final int countLanguages = Globals.availableLanguages.length;
testWidgets('Basic test with no language downloaded',
(WidgetTester tester) async {
final testLanguageProvider =
Expand All @@ -48,7 +49,7 @@ void main() {
expect(find.text('EN'), findsOneWidget);
expect(find.byIcon(Icons.check), findsNothing);
expect(find.byIcon(Icons.delete), findsNothing);
expect(find.byIcon(Icons.download), findsNWidgets(2));
expect(find.byIcon(Icons.download), findsNWidgets(countLanguages));
expect(find.byIcon(Icons.refresh), findsNothing);
});
testWidgets('Basic test with only German downloaded',
Expand Down Expand Up @@ -78,7 +79,7 @@ void main() {

expect(find.byIcon(Icons.delete), findsOneWidget);
expect(find.byIcon(Icons.refresh), findsOneWidget);
expect(find.byIcon(Icons.download), findsOneWidget);
expect(find.byIcon(Icons.download), findsNWidgets(countLanguages - 1));
});
// TODO add more tests to check whether icons change according to user interaction
}
21 changes: 15 additions & 6 deletions test/languages_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,10 @@ void main() {
test('Test error handling when structure is inconsistent', () async {
var fileSystem = MemoryFileSystem();
await fileSystem
.directory('assets-de/test-html-de-main/structure')
.directory('assets-de/html-de-main/structure')
.create(recursive: true);
var contentsJson = fileSystem
.file('assets-de/test-html-de-main/structure/contents.json');
var contentsJson =
fileSystem.file('assets-de/html-de-main/structure/contents.json');
contentsJson.writeAsString('invalid');
final container = ProviderContainer(overrides: [
languageProvider
Expand Down Expand Up @@ -164,10 +164,19 @@ void main() {
deTest.state.getPageTitles().values,
orderedEquals(const [
'Gottes Geschichte (fünf Finger)',
'Schritte der Vergebung'
'Schritte der Vergebung',
'MissingTest'
]));
expect(deTest.state.sizeInKB, 79);
expect(deTest.state.path, equals('assets-de/test-html-de-main'));
expect(deTest.state.sizeInKB, 80);
expect(deTest.state.path, equals('assets-de/html-de-main'));

// Test some error handling
content = await container.read(
pageContentProvider((name: 'MissingTest', langCode: 'de')).future);
expect(content, contains("Couldn't read page"));
content = await container
.read(pageContentProvider((name: 'Invalid', langCode: 'de')).future);
expect(content, equals(''));
});
});
}
3 changes: 2 additions & 1 deletion test/settings_page_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ void main() {
sharedPrefsProvider.overrideWithValue(prefs),
languageProvider.overrideWithProvider(testLanguageProvider)
], child: const TestSettingsPage()));
expect(find.textContaining('84 kB'), findsOneWidget);
int expectedSize = 42 * Globals.availableLanguages.length;
expect(find.textContaining('$expectedSize kB'), findsOneWidget);
});
}

0 comments on commit 7b2cea7

Please sign in to comment.