Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
saiteki-kai committed Oct 31, 2020
2 parents 8b9385c + 5d87a2c commit a6ffaa7
Show file tree
Hide file tree
Showing 32 changed files with 895 additions and 266 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
.pub/
/build/
/ios/
/coverage/

# Web related
lib/generated_plugin_registrant.dart
Expand Down
46 changes: 41 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,43 @@
# Jap Vocabs

![deploy](https://github.com/Darklod/jap-vocabs/workflows/deploy/badge.svg?branch=master)
![dev](https://github.com/Darklod/jap-vocabs/workflows/dev/badge.svg?branch=develop)
![codecov](https://codecov.io/gh/Darklod/jap-vocabs/branch/develop/graph/badge.svg?token=C4RT80DY1S)
![Flutter version](https://img.shields.io/badge/flutter-v1.22.2-blue?logo=flutter)
![style: pedantic](https://img.shields.io/badge/style-pedantic-00b4ab.svg)
[![release](https://github.com/Darklod/jap-vocabs/workflows/release/badge.svg?branch=master)](https://github.com/Darklod/jap-vocabs/actions?query=workflow%3Arelease)
[![dev](https://github.com/Darklod/jap-vocabs/workflows/dev/badge.svg?branch=develop)](https://github.com/Darklod/jap-vocabs/actions?query=workflow%3Adev)
[![codecov](https://codecov.io/gh/Darklod/jap-vocabs/branch/develop/graph/badge.svg?token=C4RT80DY1S)](https://codecov.io/gh/Darklod/jap-vocabs)
[![Flutter version](https://img.shields.io/badge/flutter-v1.22.2-blue?logo=flutter)](https://flutter.dev/docs/development/tools/sdk/releases)
[![style: pedantic](https://img.shields.io/badge/style-pedantic-00b4ab.svg)](https://pub.dev/packages/pedantic)
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FDarklod%2Fjap-vocabs.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2FDarklod%2Fjap-vocabs?ref=badge_shield)

### To Do
- [ ] Notifications
- [x] Sorting
- [x] Filter
- [ ] Statistics Page
- [ ] Remove, Update examples
- [ ] Automatic Backups
- [ ] Language localization
- [ ] Redesign Add/Edit Page

### Folder Structure

```
lib
├── components # Common widgets shared between pages
├── database # Database configuration and Dao classes
├── models # Models
├── pages # Page widgets
│ ├─ home
│ │ ├── components # Local widgets used only in the home page
│ │ └── home.dart
│ ├─ details
│ └─ ...
├── redux # Redux folders
│ ├─ actions
│ ├─ reducers
│ ├─ state
│ ├─ thunk
│ └─ store.dart
├── utils # Common functions
├── main.dart
└── routes.dart # Contains the routes and imports all pages.
```

File renamed without changes.
66 changes: 50 additions & 16 deletions lib/database/item_dao.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:jap_vocab/models/item.dart';
import 'package:jap_vocab/database/app_database.dart';
import 'package:jap_vocab/database/review_dao.dart';
import 'package:jap_vocab/redux/state/filter_state.dart';
import 'package:jap_vocab/redux/state/order_state.dart';
import 'package:sembast/sembast.dart';

class ItemDao {
Expand Down Expand Up @@ -34,35 +35,68 @@ class ItemDao {
await _store.delete(await _db, finder: finder);
}

Future<List<Item>> getAllItems({@required String type, String search}) async {
var finder = Finder(filter: Filter.equals('type', type));
Future<List<Item>> getAllItems({FilterState filter, OrderState order}) async {
var filters = [Filter.equals('type', filter.type)];

if (search != null && search.isNotEmpty) {
finder = Finder(
filter: Filter.and(
if (filter.search != null && filter.search.isNotEmpty) {
filters.add(
Filter.or(
[
Filter.equals('type', type),
Filter.or(
[
Filter.matches('text', search),
Filter.matches('reading', search)
],
),
Filter.matches('text', filter.search),
Filter.matches('reading', filter.search),
],
),
);
}

final recordSnapshot = await _store.find(await _db, finder: finder);
if (filter.jlpt != null && filter.jlpt.isNotEmpty) {
filters.add(Filter.inList('jlpt', filter.jlpt));
}

if (filter.type == 'word' &&
filter.partOfSpeech != null &&
filter.partOfSpeech.isNotEmpty) {
filters.add(Filter.custom((record) {
for (var i = 0; i < filter.partOfSpeech.length; i++) {
if (record.value['part_of_speech'].contains(filter.partOfSpeech[i])) {
return true;
}
}
return false;
}));
}

final recordSnapshot = await _store.find(
await _db,
finder: Finder(filter: Filter.and(filters)),
);

if (recordSnapshot != null) {
return Future.wait(recordSnapshot.map((snapshot) async {
final futureList = recordSnapshot.map((snapshot) async {
final item = Item.fromMap(snapshot.value);
item.review1 = await ReviewDao().getReviewById(item.reviewId1);
item.review2 = await ReviewDao().getReviewById(item.reviewId2);

return item;
}).toList());
}).toList();

var list = await Future.wait(futureList);

if (list == null || list.isEmpty) return [];

if (filter.level != null && filter.level.isNotEmpty) {
list = list.where((Item item) {
if (item.streak >= 6.0 && filter.level.contains('strong')) {
return true;
} else if (item.streak >= 4.0 && filter.level.contains('medium')) {
return true;
} else {
return filter.level.contains('weak');
}
}).toList();
}

return list..sort(Item.comparator(order.field, order.mode));
}
return null;
}
Expand Down
79 changes: 62 additions & 17 deletions lib/models/item.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,30 +35,19 @@ class Item {
this.reviewId2,
});

int _checkJLPT(int jlpt) {
if (jlpt < 0 || jlpt > 5) {
throw RangeError.range(jlpt, 0, 5);
}

return jlpt;
}

Map<String, dynamic> toMap() {
final map = {
'id': id,
'text': text,
'type': type,
'jlpt': jlpt,
'favorite': favorite,
'meaning': meaning,
'examples':
examples == null ? [] : examples.map((e) => e.toMap()).toList(),
'reviewId1': reviewId1,
'reviewId2': reviewId2,
};

if (meaning != null && meaning.isNotEmpty) {
map['meaning'] = meaning;
}
// Don't persist empty fields that are not required

if (reading != null && reading.isNotEmpty) {
map['reading'] = reading;
Expand All @@ -68,7 +57,15 @@ class Item {
map['part_of_speech'] = partOfSpeech;
}

if (numberOfStrokes != null) {
if (favorite) {
map['favorite'] = favorite;
}

if (jlpt != null && jlpt > 0) {
map['jlpt'] = jlpt;
}

if (numberOfStrokes != null && numberOfStrokes > 0) {
map['number_of_strokes'] = numberOfStrokes;
}

Expand All @@ -80,7 +77,7 @@ class Item {
id: map['id'],
text: map['text'],
type: map['type'],
favorite: map['favorite'],
favorite: map['favorite'] ?? false,
reading: map['reading'],
meaning: map['meaning'],
examples: map['examples'] == null
Expand Down Expand Up @@ -114,7 +111,7 @@ class Item {
type: type ?? this.type,
reading: reading ?? this.reading,
meaning: meaning ?? this.meaning,
jlpt: jlpt ?? _checkJLPT(this.jlpt),
jlpt: jlpt ?? this.jlpt,
numberOfStrokes: numberOfStrokes ?? this.numberOfStrokes,
partOfSpeech: partOfSpeech ?? this.partOfSpeech,
favorite: favorite ?? this.favorite,
Expand All @@ -124,9 +121,37 @@ class Item {
);
}

// Compute total accuracy
double get accuracy {
if (review1 == null && review2 == null) return 0.0;
if (review1 != null && review2 != null) {
return review1.accuracy * review2.accuracy;
}

if (review1 != null) {
return review1.accuracy;
} else {
return review2.accuracy;
}
}

// Compute mean streak
double get streak {
if (review1 == null && review2 == null) return 0.0;
if (review1 != null && review2 != null) {
return (review1.streak + review2.streak) * 0.5;
}

if (review1 != null) {
return review1.streak.toDouble();
} else {
return review2.streak.toDouble();
}
}

DateTime get nextReview {
if (review1?.next == null && review2?.next == null) return null;

var r1 = 8640000000000000; // maxMillisecondsSinceEpoch
var r2 = 8640000000000000; // maxMillisecondsSinceEpoch

Expand All @@ -148,4 +173,24 @@ class Item {

@override
int get hashCode => id.hashCode ^ text.hashCode ^ type.hashCode;

static Comparator<Item> comparator(String field, String mode) {
final mult = mode == 'ASC' ? -1 : 1;

switch (field) {
case 'Alphabetical':
return (a, b) => a.text.compareTo(b.text) * mult;
case 'Streak':
return (a, b) => a.streak.compareTo(b.streak) * mult;
case 'Accuracy':
return (a, b) => a.accuracy.compareTo(b.accuracy) * mult;
case 'Next Review':
default:
return (a, b) {
final dateA = a.nextReview ?? DateTime.now();
final dateB = b.nextReview ?? DateTime.now();
return dateA.compareTo(dateB) * mult;
};
}
}
}
4 changes: 2 additions & 2 deletions lib/models/review.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ class Review {
ef: map['EF'],
interval: map['interval'],
streak: map['streak'],
last: map['last'] != null ? map['last'].toDateTime() : null,
next: map['next'] != null ? map['next'].toDateTime() : null,
last: map['last'] is Timestamp ? map['last'].toDateTime() : null,
next: map['next'] is Timestamp ? map['next'].toDateTime() : null,
timesCorrect: map['times_correct'],
timesIncorrect: map['times_incorrect'],
reviewType: map['review_type'],
Expand Down
2 changes: 1 addition & 1 deletion lib/pages/details/components/details_appbar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:jap_vocab/models/item.dart';
import 'package:jap_vocab/pages/details/components/confirm_dialog.dart';
import 'package:jap_vocab/pages/details/components/md2_indicator.dart';
import 'package:jap_vocab/components/md2_indicator.dart';
import 'package:jap_vocab/redux/state/app_state.dart';
import 'package:jap_vocab/redux/thunk/items.dart';
import 'package:redux/redux.dart';
Expand Down
Loading

0 comments on commit a6ffaa7

Please sign in to comment.