diff --git a/android/app/build.gradle b/android/app/build.gradle index a8ae76a..52f02cd 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -43,8 +43,8 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.tangocard_reader" - minSdkVersion 16 + applicationId "xyz.h7x4.tangocard_reader" + minSdkVersion 26 targetSdkVersion 30 versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/lib/components/flashcard.dart b/lib/components/flashcard.dart index 9841d91..594b8da 100644 --- a/lib/components/flashcard.dart +++ b/lib/components/flashcard.dart @@ -1,7 +1,7 @@ import 'dart:math'; -import 'package:tangocard_reader/models/yokutango_entry.dart'; import 'package:flutter/material.dart'; +import 'package:tangocard_reader/models/data_entry.dart'; class Flashcard extends StatelessWidget { final YokutangoEntry? card; diff --git a/lib/models/data_entry.dart b/lib/models/data_entry.dart new file mode 100644 index 0000000..0ec9b95 --- /dev/null +++ b/lib/models/data_entry.dart @@ -0,0 +1,6 @@ +part 'yokutango_entry.dart'; +part 'kanji_entry.dart'; + +abstract class DataEntry { + const DataEntry(); +} \ No newline at end of file diff --git a/lib/models/kanji_entry.dart b/lib/models/kanji_entry.dart new file mode 100644 index 0000000..e55e2f8 --- /dev/null +++ b/lib/models/kanji_entry.dart @@ -0,0 +1,53 @@ +part of 'data_entry.dart'; + +class KanjiEntry extends DataEntry { + final String kanji; + final List kana; + + KanjiEntry.fromJson(Map json) + : kanji = json['kanji'] as String, + kana = [ + for (final j in json['kana'] as List) WordConstruct.fromJson(j) + ]; + + @override + String toString() { + return '$kanji - ${kana.join('、')}'; + } +} + +class WordConstruct { + final List pieces; + + WordConstruct({required this.pieces}); + + WordConstruct.fromJson(dynamic json) + : pieces = (json is String) + ? [WordPiece(word: json, isActive: true)] + : [for (final j in json as List) WordPiece.fromJson(j)]; + + @override + String toString() { + return pieces.map((p) => p.isActive ? p.word : '(${p.word})').join(''); + } +} + +class WordPiece { + final String? kana; + final String? romaji; + final String word; + final bool isActive; + + const WordPiece({ + required this.word, + this.kana, + this.romaji, + this.isActive = false, + }); + + WordPiece.fromJson(Map json) + : kana = json['kana'] as String?, + romaji = json['romaji'] as String?, + word = json['text'] as String, + isActive = json['active'] as bool? ?? false; +} diff --git a/lib/models/router_args.dart b/lib/models/router_args.dart index 6d55d35..705912b 100644 --- a/lib/models/router_args.dart +++ b/lib/models/router_args.dart @@ -1,7 +1,7 @@ -import 'package:tangocard_reader/models/yokutango_entry.dart'; +import 'data_entry.dart'; class BenkyouArgs { - final List cards; + final List cards; final int? index; const BenkyouArgs({required this.cards, this.index}); diff --git a/lib/models/yokutango_entry.dart b/lib/models/yokutango_entry.dart index 0a425ab..5cc6386 100644 --- a/lib/models/yokutango_entry.dart +++ b/lib/models/yokutango_entry.dart @@ -1,4 +1,6 @@ -class YokutangoEntry { +part of 'data_entry.dart'; + +class YokutangoEntry extends DataEntry { final List japanese; final List norwegian; diff --git a/lib/router.dart b/lib/router.dart index 20dfca5..195cf6a 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -1,11 +1,14 @@ import 'dart:io'; +import 'package:tangocard_reader/models/data_entry.dart'; import 'package:tangocard_reader/models/router_args.dart'; -import 'package:tangocard_reader/screens/flashcard.dart'; +import 'package:tangocard_reader/screens/practise/flashcard.dart'; import 'package:tangocard_reader/screens/home.dart'; -import 'package:tangocard_reader/screens/tango_list.dart'; +import 'package:tangocard_reader/screens/pages/tango_list.dart'; import 'package:flutter/material.dart'; +import 'screens/pages/kanji_list.dart'; + class PageRouter { static Route generateRoute(RouteSettings settings) { final args = settings.arguments; @@ -14,22 +17,41 @@ class PageRouter { case '/': return MaterialPageRoute(builder: (_) => const Home()); - case '/tangolist': + case '/list/tango': final file = args as File; return MaterialPageRoute(builder: (_) => TangoList(file: file)); - case '/benkyou': + case '/list/kanji': + final file = args as File; + return MaterialPageRoute(builder: (_) => KanjiList(file: file)); + + case '/benkyou/tango': final benkyouArgs = args as BenkyouArgs; return MaterialPageRoute( builder: (_) => FlashcardView( - cards: benkyouArgs.cards, + cards: benkyouArgs.cards as List, index: benkyouArgs.index, ), ); + case '/benkyou/kanji': + // final benkyouArgs = args as BenkyouArgs; + // return MaterialPageRoute( + // builder: (_) => FlashcardView( + // cards: benkyouArgs.cards as List, + // index: benkyouArgs.index, + // ), + // ); + default: return MaterialPageRoute( - builder: (_) => const Text("ERROR: this route does not exist")); + builder: (_) => Scaffold( + appBar: AppBar(title: const Text('Error')), + body: Center( + child: ErrorWidget('No such route...') + ), + ), + ); } } } diff --git a/lib/screens/home.dart b/lib/screens/home.dart index 383449f..44d1e8e 100644 --- a/lib/screens/home.dart +++ b/lib/screens/home.dart @@ -1,81 +1,37 @@ -import 'dart:convert'; -import 'dart:io'; - -import 'package:tangocard_reader/models/yokutango_entry.dart'; -import 'package:tangocard_reader/screens/error.dart'; -import 'package:tangocard_reader/screens/loading.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; +import 'package:tangocard_reader/service/tangocard_files.dart'; -class Home extends StatelessWidget { +import 'pages/tango_set_list.dart'; + +class Home extends StatefulWidget { const Home({Key? key}) : super(key: key); @override - Widget build(BuildContext context) { - return Scaffold( + State createState() => _HomeState(); +} + +class _HomeState extends State { + int page = 0; + + final _pages = [ + TangoSetList(files: tangocardFilePaths, route: '/list/tango',), + TangoSetList(files: kanjicardFilePaths, route: '/list/kanji',), + ]; + + @override + Widget build(BuildContext context) => Scaffold( appBar: AppBar( title: const Text("よく単語"), centerTitle: true, ), - body: const TangocardList()); - } -} - -class TangocardList extends StatelessWidget { - const TangocardList({Key? key}) : super(key: key); - - Future>> get tangocardFilePaths => rootBundle - .loadString('AssetManifest.json') - .then( - (json) => jsonDecode(json) - .keys - .where((String key) => - key.contains('yokutango/json/') && key.contains('.json')) - .toList(), - ) - .then( - (l) async => Map.fromIterables( - l.map((f) => File(f)), - await Future.wait>( - l - .map>>( - (String t) => rootBundle - .loadString(t) - .then>((s) => jsonDecode(s) - .map( - (e) => YokutangoEntry.fromJson(e)) - .toList()), - ) - ), + body: _pages[page], + bottomNavigationBar: BottomNavigationBar( + onTap: (int index) => setState(() => page = index), + currentIndex: page, + items: const [ + BottomNavigationBarItem(label: 'Tango', icon: Icon(Icons.style)), + BottomNavigationBarItem(label: 'Kanji', icon: Text('漢字')), + ], ), ); - - @override - Widget build(BuildContext context) { - return FutureBuilder( - future: tangocardFilePaths, - builder: (context, snapshot) { - if (snapshot.hasError) { - debugPrint(snapshot.error.toString()); - return const ErrorScreen(); - } else if (!snapshot.hasData) { - return const LoadingScreen(); - } - - return ListView( - children: (snapshot.data as Map>) - .entries - .map( - (e) => ListTile( - title: Text( - "${e.key.uri.pathSegments.last} - ${e.value.length} cards"), - onTap: () => Navigator.pushNamed(context, '/tangolist', - arguments: e.key), - ), - ) - .toList(), - ); - }, - ); - } } diff --git a/lib/screens/error.dart b/lib/screens/misc/error.dart similarity index 100% rename from lib/screens/error.dart rename to lib/screens/misc/error.dart diff --git a/lib/screens/loading.dart b/lib/screens/misc/loading.dart similarity index 100% rename from lib/screens/loading.dart rename to lib/screens/misc/loading.dart diff --git a/lib/screens/tango_list.dart b/lib/screens/pages/kanji_list.dart similarity index 77% rename from lib/screens/tango_list.dart rename to lib/screens/pages/kanji_list.dart index 53852fa..5017573 100644 --- a/lib/screens/tango_list.dart +++ b/lib/screens/pages/kanji_list.dart @@ -1,24 +1,23 @@ import 'dart:convert'; import 'dart:io'; -import 'package:tangocard_reader/models/router_args.dart'; -import 'package:tangocard_reader/models/yokutango_entry.dart'; -import 'package:tangocard_reader/screens/error.dart'; -import 'package:tangocard_reader/screens/loading.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:tangocard_reader/models/data_entry.dart'; +import 'package:tangocard_reader/models/router_args.dart'; +import 'package:tangocard_reader/screens/misc/error.dart'; +import 'package:tangocard_reader/screens/misc/loading.dart'; -class TangoList extends StatelessWidget { +class KanjiList extends StatelessWidget { final File file; - const TangoList({ - required this.file, + const KanjiList({ Key? key, + required this.file, }) : super(key: key); @override - Widget build(BuildContext context) { - return Scaffold( + Widget build(BuildContext context) => Scaffold( appBar: AppBar( title: Text(file.uri.pathSegments.last), ), @@ -33,7 +32,7 @@ class TangoList extends StatelessWidget { } final entries = (jsonSnapshot.data as List) - .map((e) => YokutangoEntry.fromJson(e)) + .map((e) => KanjiEntry.fromJson(e)) .toList(); return ListView( @@ -44,7 +43,7 @@ class TangoList extends StatelessWidget { i, ListTile( title: Text(e.toString()), - onTap: () => Navigator.pushNamed(context, '/benkyou', + onTap: () => Navigator.pushNamed(context, '/benkyou/kanji', arguments: BenkyouArgs(cards: entries, index: i)), ), ), @@ -53,7 +52,7 @@ class TangoList extends StatelessWidget { .toList(), ); }, - ), + ) ); - } + } diff --git a/lib/screens/pages/tango_list.dart b/lib/screens/pages/tango_list.dart new file mode 100644 index 0000000..fbf5978 --- /dev/null +++ b/lib/screens/pages/tango_list.dart @@ -0,0 +1,61 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:tangocard_reader/models/data_entry.dart'; +import 'package:tangocard_reader/models/router_args.dart'; +import 'package:tangocard_reader/screens/misc/error.dart'; +import 'package:tangocard_reader/screens/misc/loading.dart'; + +class TangoList extends StatelessWidget { + final File file; + + const TangoList({ + required this.file, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(file.uri.pathSegments.last), + ), + body: FutureBuilder( + future: + rootBundle.loadString(file.path).then((data) => jsonDecode(data)), + builder: (context, jsonSnapshot) { + if (jsonSnapshot.hasError) { + return const ErrorScreen(); + } else if (!jsonSnapshot.hasData) { + return const LoadingScreen(); + } + + final entries = (jsonSnapshot.data as List) + .map((e) => YokutangoEntry.fromJson(e)) + .toList(); + + return ListView( + children: entries + .asMap() + .map( + (i, e) => MapEntry( + i, + ListTile( + title: Text(e.toString()), + onTap: () => Navigator.pushNamed( + context, + '/benkyou/tango', + arguments: BenkyouArgs(cards: entries, index: i), + ), + ), + ), + ) + .values + .toList(), + ); + }, + )); + } +} diff --git a/lib/screens/pages/tango_set_list.dart b/lib/screens/pages/tango_set_list.dart new file mode 100644 index 0000000..3a7625f --- /dev/null +++ b/lib/screens/pages/tango_set_list.dart @@ -0,0 +1,46 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:tangocard_reader/screens/misc/error.dart'; +import 'package:tangocard_reader/screens/misc/loading.dart'; + +class TangoSetList extends StatelessWidget { + final String route; + final Future> files; + + const TangoSetList({ + Key? key, + required this.route, + required this.files, + }) : super(key: key); + + @override + Widget build(BuildContext context) => FutureBuilder( + future: files, + builder: (context, snapshot) { + if (snapshot.hasError) { + debugPrint(snapshot.error.toString()); + return const ErrorScreen(); + } else if (!snapshot.hasData) { + return const LoadingScreen(); + } + + return ListView( + children: (snapshot.data as Map) + .entries + .map( + (e) => ListTile( + title: Text( + "${e.key.uri.pathSegments.last} - ${e.value.length} cards"), + onTap: () => Navigator.pushNamed( + context, + route, + arguments: e.key, + ), + ), + ) + .toList(), + ); + }, + ); +} diff --git a/lib/screens/flashcard.dart b/lib/screens/practise/flashcard.dart similarity index 98% rename from lib/screens/flashcard.dart rename to lib/screens/practise/flashcard.dart index 5d7c4e0..04f133e 100644 --- a/lib/screens/flashcard.dart +++ b/lib/screens/practise/flashcard.dart @@ -1,9 +1,9 @@ import 'dart:math'; import 'package:tangocard_reader/components/flashcard.dart'; -import 'package:tangocard_reader/models/yokutango_entry.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:tangocard_reader/models/data_entry.dart'; class FlashcardView extends StatefulWidget { final List cards; diff --git a/lib/service/tangocard_files.dart b/lib/service/tangocard_files.dart new file mode 100644 index 0000000..976979b --- /dev/null +++ b/lib/service/tangocard_files.dart @@ -0,0 +1,50 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/services.dart'; +import 'package:tangocard_reader/models/data_entry.dart'; + +// TODO: merge + +Future>> get tangocardFilePaths => rootBundle + .loadString('AssetManifest.json') + .then( + (json) => jsonDecode(json) + .keys + .where((String key) => + key.contains('yokutango/json/') && key.contains('.json')) + .toList(), + ) + .then( + (l) async => Map.fromIterables( + l.map((f) => File(f)), + await Future.wait>( + l.map>>( + (String t) => rootBundle.loadString(t).then>( + (s) => jsonDecode(s) + .map((e) => YokutangoEntry.fromJson(e)) + .toList()), + )), + ), + ); + +Future>> get kanjicardFilePaths => rootBundle + .loadString('AssetManifest.json') + .then( + (json) => jsonDecode(json) + .keys + .where((String key) => + key.contains(RegExp(r'yokutango/kanji/kanji_\d+.json'))) + .toList(), + ) + .then( + (l) async => Map.fromIterables( + l.map((f) => File(f)), + await Future.wait>(l.map>>( + (String t) => rootBundle.loadString(t).then>((s) => + jsonDecode(s) + .map((e) => KanjiEntry.fromJson(e)) + .toList()), + )), + ), + ); diff --git a/pubspec.lock b/pubspec.lock index 53c104c..6b0c5a7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -176,7 +176,7 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.1" sdks: - dart: ">=2.12.0 <3.0.0" + dart: ">=2.14.0 <3.0.0" flutter: ">=1.16.0"