-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
263 additions
and
71 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export 'src/network/rest.dart'; | ||
export 'src/utils/collections.dart'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import 'dart:math'; | ||
|
||
/// Partition the given [list] into groups with size of [batchSize]. | ||
Iterable<Iterable<T>> partition<T>(List<T> list, int batchSize) { | ||
final batches = List<List<T>>(); | ||
if (list?.isNotEmpty != true || batchSize == null || batchSize <= 0) return batches; | ||
|
||
final total = list.length; | ||
int offset = 0; | ||
int remains = total; | ||
|
||
while (offset < list.length && remains > 0) { | ||
final size = min(remains, batchSize); | ||
batches.add(list.sublist(offset, offset + size)); | ||
offset += size; | ||
remains -= size; | ||
} | ||
|
||
return batches; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
import 'package:firebase/firebase.dart' show auth; | ||
import 'package:flutter/material.dart'; | ||
import 'package:notever/framework.dart' show partition, postJson; | ||
import 'package:notever/local.dart' show EvernoteConfig; | ||
import 'package:notever/models.dart' show Clipping; | ||
import 'package:uuid/uuid.dart'; | ||
|
||
/// A [FloatingActionButton] to upload selected [Clipping]s. | ||
class ClippingUploaderFab extends StatefulWidget { | ||
/// Instantiate a [ClippingUploaderFab]. | ||
/// | ||
/// To upload the given [clippings] when the FAB is clicked, | ||
/// with an optional [onComplete] listener receiving notification when all clippings are uploaded (successfully or not). | ||
const ClippingUploaderFab({ | ||
Key key, | ||
this.clippings, | ||
this.onComplete, | ||
}) : super(key: key); | ||
|
||
/// A list of [Clipping]s to be uploaded. | ||
final List<Clipping> clippings; | ||
|
||
/// Callback when all [Clipping]s are uploaded, no matter successful or not | ||
final VoidCallback onComplete; | ||
|
||
@override | ||
State<StatefulWidget> createState() => _ClippingUploaderState(); | ||
} | ||
|
||
/// [State] of the [ClippingUploaderFab] widget. | ||
class _ClippingUploaderState extends State<ClippingUploaderFab> { | ||
/// Current user's uid, should has a valid value when the uploader is visible | ||
String get _currentUserID => auth().currentUser?.uid; | ||
|
||
/// [Clipping]s to be uploaded. | ||
List<Clipping> get _clippings => widget.clippings; | ||
|
||
/// Whether clippings is being uploaded | ||
bool _isUploading = false; | ||
|
||
/// Total batches of [Clipping]s to be uploaded | ||
int _totalBatches = 0; | ||
|
||
/// Currently uploaded batches of [Clipping]s | ||
int _uploadedBatches = 0; | ||
|
||
/// Previous number of uploaded batches, used to display progress animation | ||
int _prevUploadedBatches = 0; | ||
|
||
@override | ||
Widget build(BuildContext context) => Stack( | ||
children: <Widget>[ | ||
_buildFab(), | ||
if (_isUploading) _buildProgressIndicator(), // show progress when upload is started | ||
], | ||
); | ||
|
||
/// Upload FAB | ||
Widget _buildFab() => FloatingActionButton( | ||
child: const Icon(Icons.cloud_upload), | ||
tooltip: 'Upload', | ||
onPressed: _isUploading ? null : _onPressed, // disabled when syncing | ||
); | ||
|
||
/// Show uploading progress | ||
Widget _buildProgressIndicator() => Positioned( | ||
width: 28, | ||
height: 28, | ||
top: 14, | ||
left: 14, | ||
child: TweenAnimationBuilder( | ||
duration: const Duration(seconds: 5), | ||
tween: Tween<double>( | ||
begin: _prevUploadedBatches / _totalBatches, | ||
end: _uploadedBatches / _totalBatches, | ||
), | ||
builder: (_, value, __) => | ||
CircularProgressIndicator( | ||
value: value, | ||
valueColor: const AlwaysStoppedAnimation(const Color(0x60ffffff)), | ||
strokeWidth: 28, | ||
), | ||
), | ||
); | ||
|
||
/// Start syncing clippings to Evernote | ||
void _onPressed() async { | ||
if (_isUploading || _clippings?.isNotEmpty != true || _currentUserID?.isNotEmpty != true) return; | ||
|
||
final message = """Are sure to import all clippings newer than your selection into your Evernote account? | ||
${_clippings.length} notes will be created. | ||
Please confirm to continue. | ||
"""; | ||
final confirmed = await showDialog<bool>( | ||
context: context, | ||
builder: (_) => AlertDialog( | ||
title: const Text('Sync Clippings'), | ||
content: Text(message), | ||
actions: <Widget>[ | ||
FlatButton( | ||
child: const Text('Cancel'), | ||
onPressed: () => Navigator.of(context).pop(false), | ||
), | ||
FlatButton( | ||
child: const Text('Continue'), | ||
onPressed: () => Navigator.of(context).pop(true), | ||
) | ||
], | ||
), | ||
); | ||
if (!confirmed) return; | ||
|
||
final batches = partition(_clippings, _BATCH_SIZE); | ||
final taskId = Uuid().v4(); | ||
|
||
setState(() { | ||
_isUploading = true; | ||
_totalBatches = batches.length; | ||
_prevUploadedBatches = _uploadedBatches = 0; | ||
}); | ||
|
||
int i = 0; | ||
for (var clippings in batches) { | ||
await _uploadClippings(taskId, i, clippings); | ||
await Future.delayed(const Duration(milliseconds: 25)); | ||
} | ||
|
||
_notifyComplete(); | ||
} | ||
|
||
Future<void> _uploadClippings(String taskId, int batchNo, Iterable<Clipping> clippings) async { | ||
try { | ||
final uri = '${EvernoteConfig.funcPrefix}/import.json'; | ||
await postJson(uri, body: { | ||
'taskId': taskId, | ||
'batch': batchNo, | ||
'uid': _currentUserID, | ||
'clippings': Clipping.clippingsToJson(clippings), | ||
}); | ||
} catch (e) { | ||
debugPrint('sync clippings request rejected: $e'); | ||
} finally { | ||
setState(() { | ||
// _isUploading = false; | ||
_prevUploadedBatches = _uploadedBatches; | ||
_uploadedBatches += 1; | ||
}); | ||
} | ||
} | ||
|
||
void _notifyComplete() { | ||
WidgetsBinding.instance.addPostFrameCallback((_) => widget.onComplete?.call()); | ||
} | ||
} | ||
|
||
const _BATCH_SIZE = 25; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
export 'src/widgets/clipping_list.dart'; | ||
export 'src/widgets/clipping_uploader_fab.dart'; | ||
export 'src/widgets/login.dart'; | ||
export 'src/widgets/job.dart'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,6 +27,7 @@ dependencies: | |
firebase: ^6.0.0 | ||
http: | ||
intl: | ||
uuid: | ||
|
||
dev_dependencies: | ||
flutter_test: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import 'package:flutter_test/flutter_test.dart'; | ||
import 'package:notever/src/utils/collections.dart'; | ||
|
||
void main() { | ||
test('partition a list', () { | ||
var list = [1, 2, 3, 4, 5, 6]; | ||
var result = partition(list, 3); | ||
expect(result, hasLength(2)); | ||
expect(result.first, hasLength(3)); | ||
expect(result.last, hasLength(3)); | ||
expect(result.last, [4, 5, 6]); | ||
|
||
list = [3, 2]; | ||
result = partition(list, 2); | ||
expect(result, hasLength(1)); | ||
expect(result.last, [3, 2]); | ||
}); | ||
|
||
test('partition a list has an odd length', () { | ||
var list = [1, 2, 3, 4, 5, 6, 7]; | ||
var result = partition(list, 3); | ||
expect(result, hasLength(3)); | ||
expect(result.first, hasLength(3)); | ||
expect(result.last, hasLength(1)); | ||
expect(result.last, [7]); | ||
|
||
list = [1, 2, 3]; | ||
result = partition(list, 3); | ||
expect(result, hasLength(1)); | ||
expect(result.last, [1, 2, 3]); | ||
}); | ||
|
||
test('partition an empty list should get an empty list', () { | ||
expect(partition([], 3), isEmpty); | ||
expect(partition(null, 3), isEmpty); | ||
}); | ||
|
||
test('partition a list with invalid batch size', () { | ||
expect(partition([1, 2], 0), isEmpty); | ||
expect(partition([1, 2], -1), isEmpty); | ||
expect(partition([1, 2], null), isEmpty); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.