diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 8252402..3ec410c 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -32,6 +32,17 @@
+
+
+
+
+
+
+
+
+
diff --git a/lib/helpers/api.dart b/lib/helpers/api.dart
index 5257af4..b2f0c7c 100644
--- a/lib/helpers/api.dart
+++ b/lib/helpers/api.dart
@@ -47,7 +47,6 @@ class KnockoutAPI {
String mBaseurl =
await box.get('env') == 'knockout' ? KNOCKOUT_URL : QA_URL;
-
Dio dio = new Dio();
dio.options.baseUrl = mBaseurl;
dio.options.contentType = ContentType.json;
@@ -76,8 +75,8 @@ class KnockoutAPI {
try {
final response2 = await _request(url: 'subforum');
return response2.data['list']
- .map((json) => Subforum.fromJson(json))
- .toList();
+ .map((json) => Subforum.fromJson(json))
+ .toList();
} on DioError catch (e) {
throw e;
}
@@ -86,8 +85,8 @@ class KnockoutAPI {
Future getSubforumDetails(int id, {int page = 1}) async {
try {
final response = await _request(
- url: 'subforum/' + id.toString() + '/' + page.toString());
- return SubforumDetails.fromJson(response.data);
+ url: 'subforum/' + id.toString() + '/' + page.toString());
+ return SubforumDetails.fromJson(response.data);
} on DioError catch (e) {
print(e);
return null;
@@ -102,7 +101,8 @@ class KnockoutAPI {
Future authCheck() async {
try {
- final response = await _request(url: 'user/authCheck', headers: {'content-format-version': 1});
+ final response = await _request(
+ url: 'user/authCheck', headers: {'content-format-version': 1});
return response.data;
} on DioError catch (e) {
return e.response.data;
@@ -124,8 +124,7 @@ class KnockoutAPI {
Future readThreads(DateTime lastseen, int threadId) async {
ReadThreads jsonToPost =
new ReadThreads(lastSeen: lastseen, threadId: threadId);
- await _request(
- type: 'post', url: 'readThreads', data: jsonToPost.toJson());
+ await _request(type: 'post', url: 'readThreads', data: jsonToPost.toJson());
}
Future readThreadSubsciption(DateTime lastseen, int threadId) async {
@@ -138,16 +137,15 @@ class KnockoutAPI {
final response = await _request(type: 'get', url: 'events');
return response.data
- .map((json) => KnockoutEvent.fromJson(json))
- .toList();
+ .map((json) => KnockoutEvent.fromJson(json))
+ .toList();
}
Future deleteThreadAlert(int threadid) async {
final response = await _request(
url: 'alert', type: 'delete', data: {'threadId': threadid});
- if (response.statusCode == 200) {
- }
+ if (response.statusCode == 200) {}
}
Future subscribe(DateTime lastSeen, int threadid) async {
@@ -156,8 +154,7 @@ class KnockoutAPI {
type: 'post',
data: {'lastSeen': lastSeen.toIso8601String(), 'threadId': threadid});
- if (response.statusCode == 200) {
- }
+ if (response.statusCode == 200) {}
}
Future ratePost(int postId, String rating) async {
@@ -170,6 +167,8 @@ class KnockoutAPI {
}
Future newPost(dynamic content, int threadId) async {
+ print(content);
+ print(threadId);
try {
await _request(type: 'post', url: 'post', data: {
'content': json.encode(content).toString(),
@@ -180,33 +179,38 @@ class KnockoutAPI {
});
} on DioError catch (e) {
print(e);
+ print(e.response);
}
}
- Future updatePost(String content, int postId, int threadId) async {
- await _request(
- type: 'post',
- url: 'post',
- data: {'content': content, 'id': postId, 'thread_id': threadId});
+ Future updatePost(dynamic content, int postId, int threadId) async {
+ try {
+ await _request(type: 'put', url: 'post', data: {
+ 'content': json.encode(content).toString(),
+ 'id': postId,
+ 'thread_id': threadId
+ });
+ } on DioError catch (e) {
+ print(e);
+ print(e.response);
+ }
}
Future> latestThreads() async {
- final response = await _request(
- type: 'get',
- url: 'thread/latest');
+ final response = await _request(type: 'get', url: 'thread/latest');
return response.data['list']
- .map((json) => SubforumThreadLatestPopular.fromJson(json))
+ .map(
+ (json) => SubforumThreadLatestPopular.fromJson(json))
.toList();
}
Future> popularThreads() async {
- final response = await _request(
- type: 'get',
- url: 'thread/popular');
+ final response = await _request(type: 'get', url: 'thread/popular');
return response.data['list']
- .map((json) => SubforumThreadLatestPopular.fromJson(json))
+ .map(
+ (json) => SubforumThreadLatestPopular.fromJson(json))
.toList();
}
diff --git a/lib/helpers/bbcode.dart b/lib/helpers/bbcode.dart
index de80bd4..b2bac19 100644
--- a/lib/helpers/bbcode.dart
+++ b/lib/helpers/bbcode.dart
@@ -1,19 +1,17 @@
import 'package:bbob_dart/bbob_dart.dart' as bbob;
import 'package:knocky/models/slateDocument.dart';
-import 'package:knocky/models/thread.dart';
+
class BBCodeHandler implements bbob.NodeVisitor {
- SlateDocument document = SlateDocument(object: 'document', nodes: List());
+ SlateNode paragraph = SlateNode(object: 'block', nodes: List());
StringBuffer _leafContentBuffer = StringBuffer();
- List _replyList = List();
- Thread _thread;
SlateNode _lastElement;
List _leafMarks = List();
- SlateObject parse(String text, Thread thread, List replyList) {
- _thread = thread;
- _replyList = replyList;
+ SlateNode parse(String text, {type: 'paragraph'}) {
+ paragraph.type = type;
+
var ast = bbob.parse(text);
for (final node in ast) {
@@ -32,50 +30,15 @@ class BBCodeHandler implements bbob.NodeVisitor {
]);
// Add node
- _lastElement.nodes.add(textLeafNode);
+ paragraph.nodes.add(textLeafNode);
_leafContentBuffer = StringBuffer();
- document.nodes.add(_lastElement);
}
- return SlateObject(object: 'value', document: document);
+ return paragraph;
}
void visitText(bbob.Text text) {
- if (_lastElement == null) {
- _lastElement = SlateNode(
- object: 'block',
- type: 'paragraph',
- data: SlateNodeData(),
- nodes: List());
- }
-
- if (text.textContent == '\n') {
- // New leaf is appearing, add old leaf to node
- SlateNode textLeafNode = SlateNode(object: 'text', leaves: [
- SlateLeaf(
- text: _leafContentBuffer.toString(),
- marks: _leafMarks,
- object: 'leaf'),
- ]);
-
- // Reset leaf marks
- _leafMarks = List();
-
- // Add node
- _lastElement.nodes.add(textLeafNode);
- _leafContentBuffer = StringBuffer();
-
- document.nodes.add(_lastElement);
-
- // Paragraph ended, to reset last element
- _lastElement = SlateNode(
- object: 'block',
- type: 'paragraph',
- data: SlateNodeData(),
- nodes: List());
- } else {
- _leafContentBuffer.write(text.textContent);
- }
+ _leafContentBuffer.write(text.textContent);
}
bool visitElementBefore(bbob.Element element) {
@@ -91,7 +54,7 @@ class BBCodeHandler implements bbob.NodeVisitor {
// Reset leaf marks
_leafMarks = List();
- _lastElement.nodes.add(textNode);
+ paragraph.nodes.add(textNode);
_leafContentBuffer = StringBuffer();
}
@@ -113,15 +76,7 @@ class BBCodeHandler implements bbob.NodeVisitor {
}
if (element.tag == 'url') {
- if (_lastElement == null) {
- _lastElement = SlateNode(
- object: 'block',
- type: 'paragraph',
- data: SlateNodeData(),
- nodes: List());
- }
-
- _lastElement.nodes.add(SlateNode(
+ paragraph.nodes.add(SlateNode(
object: 'inline',
type: 'link',
data: SlateNodeData(href: element.children.first.textContent),
@@ -137,170 +92,29 @@ class BBCodeHandler implements bbob.NodeVisitor {
return false;
}
- if (element.tag == 'img') {
- if (_lastElement != null) {
- _lastElement = SlateNode(
- object: 'block',
- type: 'paragraph',
- data: SlateNodeData(),
- nodes: List());
- }
-
- SlateNode imgNode = SlateNode(
- object: 'block',
- type: 'image',
- data: SlateNodeData(src: element.children.first.textContent),
- nodes: List(),
- );
-
- document.nodes.add(imgNode);
- // Do not handle children
- return false;
- }
-
- if (element.tag == 'h1') {
- _lastElement = SlateNode(
- object: 'block',
- type: 'heading-one',
- data: null,
- nodes: [
- SlateNode(object: 'text', leaves: []),
- ],
- );
- }
-
- if (element.tag == 'h2') {
- _lastElement =
- SlateNode(object: 'block', type: 'heading-two', data: null, nodes: [
- SlateNode(
- object: 'text',
- leaves: [],
- )
- ]);
- }
-
- if (element.tag == 'blockquote') {
- _lastElement =
- SlateNode(object: 'block', type: 'block-quote', data: null, nodes: [
- SlateNode(
- object: 'text',
- leaves: [],
- )
- ]);
- }
-
- if (element.tag == 'youtube') {
- document.nodes.add(SlateNode(
- object: 'block',
- type: 'youtube',
- data: SlateNodeData(src: element.children.first.textContent),
- nodes: []));
- return false;
- }
-
- if (element.tag == 'video') {
- document.nodes.add(SlateNode(
- object: 'block',
- type: 'video',
- data: SlateNodeData(src: element.children.first.textContent),
- nodes: []));
- return false;
- }
-
- if (element.tag == 'userquote') {
- int replyIndex = int.parse(element.children.first.textContent) - 1;
-
- if (_replyList[replyIndex] != null) {
- ThreadPost reply = _replyList[replyIndex];
-
- document.nodes.add(
- SlateNode(
- object: 'block',
- type: 'userquote',
- data: SlateNodeData(
- postData: NodeDataPostData(
- postId: reply.id,
- threadId: _thread.id,
- threadPage: _thread.currentPage,
- username: reply.user.username,
- ),
- ),
- nodes: reply.content.document.nodes),
- );
- }
- return false;
- }
-
- if (element.tag == 'ul') {
- _lastElement = SlateNode(
- object: 'block',
- type: 'bulleted-list',
- data: SlateNodeData(),
- nodes: []);
- }
-
- if (element.tag == 'ol') {
- _lastElement = SlateNode(
- object: 'block',
- type: 'numbered-list',
- data: SlateNodeData(),
- nodes: []);
- }
-
- if (element.tag == 'li') {
- _lastElement.nodes.add(
- SlateNode(
- object: 'block',
- type: 'list-item',
- data: SlateNodeData(),
- nodes: []),
- );
- }
-
// Handle children
return true;
}
void visitElementAfter(bbob.Element element) {
- switch (element.tag) {
- case 'li':
- SlateNode textNode = SlateNode(object: 'text', leaves: [
- SlateLeaf(
- text: _leafContentBuffer.toString(),
- marks: _leafMarks,
- object: 'leaf')
- ]);
-
- // Reset leaf marks
- _leafMarks = List();
-
- _lastElement.nodes.last.nodes.add(textNode);
- _leafContentBuffer = StringBuffer();
- break;
- case 'ul':
- document.nodes.add(_lastElement);
- _lastElement = null;
- break;
- default:
- // Tag is done, add leaf
- SlateNode textNode = SlateNode(object: 'text', leaves: [
- SlateLeaf(
- text: _leafContentBuffer.toString(),
- marks: _leafMarks,
- object: 'leaf')
- ]);
-
- // Reset leaf marks
- _leafMarks = List();
-
- _lastElement.nodes.add(textNode);
- _leafContentBuffer = StringBuffer();
- }
+ // Tag is done, add leaf
+ SlateNode textNode = SlateNode(object: 'text', leaves: [
+ SlateLeaf(
+ text: _leafContentBuffer.toString(),
+ marks: _leafMarks,
+ object: 'leaf')
+ ]);
+
+ // Reset leaf marks
+ _leafMarks = List();
+
+ paragraph.nodes.add(textNode);
+ _leafContentBuffer = StringBuffer();
}
- Map slateDocumentToBBCode(SlateDocument document) {
- String bbcode = _handleNodes(document.nodes).trim();
- return {'bbcode': bbcode, 'userquotes': {}};
+ String slateParagraphToBBCode(SlateNode node) {
+ String bbcode = _handleNodes(node.nodes).trim();
+ return bbcode;
}
String _inlineHandler(SlateNode object, SlateNode node) {
@@ -329,127 +143,27 @@ class BBCodeHandler implements bbob.NodeVisitor {
StringBuffer content = new StringBuffer();
List contentItems = List();
- nodes.forEach((node) {
- if (asList) {
- print(node.type);
+ nodes.forEach((line) {
+ if (line.leaves != null) {
+ content.write(_leafHandler(line.leaves));
}
- // Handle blocks
- switch (node.type) {
- case 'paragraph':
- node.nodes.asMap().forEach((i, line) {
- if (line.leaves != null) {
- content.write(_leafHandler(line.leaves));
- }
-
- // Handle inline element
- if (line.object == 'inline') {
- // Handle links
- if (line.type == 'link') {
- line.nodes.forEach((inlineNode) {
- inlineNode.leaves.forEach((leaf) {
- content.write('[url]' + leaf.text + '[/url]');
- });
- });
- } else {
- line.nodes.forEach((inlineNode) {
- inlineNode.leaves.forEach((leaf) {
- content.write(leaf.text);
- });
- });
- }
- }
- });
- content.write('\n');
- break;
- case 'heading-one':
- node.nodes.forEach((line) {
- content.write('[h1]');
- if (line.leaves != null) {
- // Handle node leaves
- content.write(_leafHandler(line.leaves));
- }
-
- // Handle inline element
- if (line.object == 'inline') {
- // Handle links
- content.write(_inlineHandler(node, line));
- }
- });
- content.write('[/h1]\n');
- //widgets.add(headingToWidget(node));
- break;
- case 'heading-two':
- node.nodes.forEach((line) {
- content.write('[h2]');
- if (line.leaves != null) {
- // Handle node leaves
- content.write(_leafHandler(line.leaves));
- }
-
- // Handle inline element
- if (line.object == 'inline') {
- // Handle links
- content.write(_inlineHandler(node, line));
- }
- });
- content.write('[/h2]\n');
- break;
- case 'userquote':
- //widgets.add(userquoteToWidget(node, isChild: isChild));
- break;
- case 'bulleted-list':
- content.write('[ul]');
- List listItemsContent = List();
- listItemsContent.addAll(_handleNodes(node.nodes, asList: true));
- listItemsContent.forEach((item) {
- content.write('[li]' + item + '[/li]');
+ // Handle inline element
+ if (line.object == 'inline') {
+ // Handle links
+ if (line.type == 'link') {
+ line.nodes.forEach((inlineNode) {
+ inlineNode.leaves.forEach((leaf) {
+ content.write('[url]' + leaf.text + '[/url]');
+ });
});
- content.write('[/ul]\n');
- break;
- case 'numbered-list':
- //widgets.add(numberedListToWidget(node));
- break;
- case 'list-item':
- node.nodes.asMap().forEach((i, line) {
- if (line.leaves != null) {
- content.write(_leafHandler(line.leaves));
- }
-
- // Handle inline element
- if (line.object == 'inline') {
- // Handle links
- if (line.type == 'link') {
- line.nodes.forEach((inlineNode) {
- inlineNode.leaves.forEach((leaf) {
- content.write('[url]' + leaf.text + '[/url]');
- });
- });
- } else {
- line.nodes.forEach((inlineNode) {
- inlineNode.leaves.forEach((leaf) {
- content.write(leaf.text);
- });
- });
- }
- }
+ } else {
+ line.nodes.forEach((inlineNode) {
+ inlineNode.leaves.forEach((leaf) {
+ content.write(leaf.text);
+ });
});
- break;
- case 'image':
- content.write('[img]' + node.data.src + '[/img]\n');
- break;
- case 'youtube':
- //(widgets.add(youTubeToWidget(node));
- break;
- case 'block-quote':
- //widgets.add(handleQuotes(node));
- break;
- case 'twitter':
- //widgets.add(EmbedWidget(url: node.data.src));
- break;
- case 'video':
- //widgets.add(handleVideo(node));
- break;
+ }
}
if (asList) {
diff --git a/lib/models/slateDocument.dart b/lib/models/slateDocument.dart
index 4acc1cd..f46c795 100644
--- a/lib/models/slateDocument.dart
+++ b/lib/models/slateDocument.dart
@@ -12,6 +12,8 @@ class SlateObject {
factory SlateObject.fromJson(Map json) => _$SlateObjectFromJson(json);
Map toJson() => _$SlateObjectToJson(this);
+
+ SlateObject.clone(SlateObject slateObject): this(object: slateObject.object, document: slateObject.document);
}
Map _documentToJson(SlateDocument document) => document.toJson();
diff --git a/lib/models/thread.dart b/lib/models/thread.dart
index 60d5da5..2fb8b2f 100644
--- a/lib/models/thread.dart
+++ b/lib/models/thread.dart
@@ -81,6 +81,8 @@ class ThreadPost {
factory ThreadPost.fromJson(Map json) =>
_$ThreadPostFromJson(json);
Map toJson() => _$ThreadPostToJson(this);
+
+ ThreadPost.clone(ThreadPost post): this(id: post.id, content: SlateObject.fromJson(post.content.toJson()), user: post.user, ratings: post.ratings, createdAt: post.createdAt, bans: post.bans);
}
SlateObject _contentFromJson(String jsonString) {
diff --git a/lib/screens/editPost.dart b/lib/screens/editPost.dart
deleted file mode 100644
index fc676c2..0000000
--- a/lib/screens/editPost.dart
+++ /dev/null
@@ -1,497 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
-import 'package:knocky/helpers/bbcode.dart';
-import 'package:knocky/models/slateDocument.dart';
-import 'package:knocky/models/thread.dart';
-import 'package:knocky/widget/SlateDocumentParser/SlateDocumentParser.dart';
-import 'package:knocky/helpers/api.dart';
-import 'package:knocky/widget/KnockoutLoadingIndicator.dart';
-
-class EditPostScreen extends StatefulWidget {
- final ThreadPost post;
- final Thread thread;
-
- EditPostScreen(
- {this.post, this.thread});
-
- @override
- _EditPostScreenState createState() => _EditPostScreenState();
-}
-
-class _EditPostScreenState extends State {
- TextEditingController controller = TextEditingController(text: '');
- SlateObject document;
- GlobalKey _scaffoldKey;
- FocusNode textFocusNode = FocusNode();
- bool _isPosting = false;
- List replyList = List();
-
- List history = List();
-
- @override
- void initState() {
- super.initState();
-
- Map bbcodeObj = BBCodeHandler().slateDocumentToBBCode(this.widget.post.content.document);
- controller.text = bbcodeObj['bbcode'];
-
- document = BBCodeHandler()
- .parse(controller.text, this.widget.thread, replyList);
- }
-
- void onPressPost() async {
- setState(() {
- _isPosting = true;
- });
- await KnockoutAPI().newPost(document.toJson(), this.widget.thread.id);
- Navigator.pop(context, true);
- }
-
- void refreshPreview() {
- setState(() {
- document = BBCodeHandler()
- .parse(controller.text, this.widget.thread, this.replyList);
- });
- }
-
- void onPressSpoiler(BuildContext context, String content) {
- showDialog(
- context: context,
- builder: (BuildContext context) {
- // return object of type Dialog
- return AlertDialog(
- content: new Text(content),
- actions: [
- // usually buttons at the bottom of the dialog
- new FlatButton(
- child: new Text("Close"),
- onPressed: () {
- Navigator.of(context).pop();
- },
- ),
- ],
- );
- },
- );
- }
-
- void addTagAtSelection(int start, int end, String tag) {
- RegExp regExp = new RegExp(
- r'(\[([^/].*?)(=(.+?))?\](.*?)\[/\2\]|\[([^/].*?)(=(.+?))?\])',
- caseSensitive: false,
- multiLine: false,
- );
-
- String newline = tag == 'h1' || tag == 'h2' ? "\n" : '';
- String selectedText = controller.text.substring(start, end);
- String replaceWith = '';
-
- if (regExp.hasMatch(selectedText)) {
- replaceWith = selectedText.replaceAll('[${tag}]', ''); //ignore: unnecessary_brace_in_string_interps
- replaceWith = replaceWith.replaceAll('[/${tag}]', ''); //ignore: unnecessary_brace_in_string_interps
- } else {
- replaceWith = newline + '[${tag}]' + selectedText + '[/${tag}]'; //ignore: unnecessary_brace_in_string_interps
- }
- controller.text = controller.text.replaceRange(start, end, replaceWith);
-
- refreshPreview();
- }
-
- void addImageDialog() async {
- ClipboardData clipBoardText = await Clipboard.getData('text/plain');
- TextEditingController imgurlController =
- TextEditingController(text: clipBoardText.text);
- await showDialog(
- context: context,
- child: new AlertDialog(
- contentPadding: const EdgeInsets.all(16.0),
- content: new Row(
- children: [
- new Expanded(
- child: new TextField(
- autofocus: true,
- keyboardType: TextInputType.url,
- controller: imgurlController,
- decoration: new InputDecoration(labelText: 'Image url'),
- ),
- )
- ],
- ),
- actions: [
- new FlatButton(
- child: const Text('Cancel'),
- onPressed: () {
- Navigator.of(context, rootNavigator: true).pop();
- }),
- new FlatButton(
- child: const Text('Insert'),
- onPressed: () {
- Navigator.of(context, rootNavigator: true).pop();
-
- if(controller.text.endsWith('\n') || controller.text.isEmpty) {
- controller.text =
- controller.text + '[img]${imgurlController.text}[/img]';
- } else {
- controller.text =
- controller.text + '\n[img]${imgurlController.text}[/img]';
- }
- refreshPreview();
- })
- ],
- ),
- );
- }
-
- void addLinkDialog() async {
- ClipboardData clipBoardText = await Clipboard.getData('text/plain');
- TextEditingController urlController =
- TextEditingController(text: clipBoardText.text);
- await showDialog(
- context: context,
- child: new AlertDialog(
- contentPadding: const EdgeInsets.all(16.0),
- content: new Row(
- children: [
- new Expanded(
- child: new TextField(
- autofocus: true,
- keyboardType: TextInputType.url,
- controller: urlController,
- decoration: new InputDecoration(labelText: 'Url'),
- ),
- )
- ],
- ),
- actions: [
- new FlatButton(
- child: const Text('Cancel'),
- onPressed: () {
- Navigator.of(context, rootNavigator: true).pop();
- }),
- new FlatButton(
- child: const Text('Insert'),
- onPressed: () {
- Navigator.of(context, rootNavigator: true).pop();
-
- if(controller.text.endsWith('\n') || controller.text.isEmpty) {
- controller.text =
- controller.text + '[url]${urlController.text}[/url]';
- } else {
- controller.text =
- controller.text + '\n[url]${urlController.text}[/url]';
- }
-
- refreshPreview();
- })
- ],
- ),
- );
- }
-
- void addYoutubeVideoDialog() async {
- ClipboardData clipBoardText = await Clipboard.getData('text/plain');
- TextEditingController urlController =
- TextEditingController(text: clipBoardText.text);
- await showDialog(
- context: context,
- child: new AlertDialog(
- contentPadding: const EdgeInsets.all(16.0),
- content: new Row(
- children: [
- new Expanded(
- child: new TextField(
- autofocus: true,
- keyboardType: TextInputType.url,
- controller: urlController,
- decoration: new InputDecoration(labelText: 'YouTube URL'),
- ),
- )
- ],
- ),
- actions: [
- new FlatButton(
- child: const Text('Cancel'),
- onPressed: () {
- Navigator.of(context, rootNavigator: true).pop();
- }),
- new FlatButton(
- child: const Text('Insert'),
- onPressed: () {
- Navigator.of(context, rootNavigator: true).pop();
-
- if(controller.text.endsWith('\n') || controller.text.isEmpty) {
- controller.text = controller.text +
- '[youtube]${urlController.text}[/youtube]';
- } else {
- controller.text = controller.text +
- '\n[youtube]${urlController.text}[/youtube]';
- }
- refreshPreview();
- })
- ],
- ),
- );
- }
-
- void addVideoDialog() async {
- ClipboardData clipBoardText = await Clipboard.getData('text/plain');
- TextEditingController urlController =
- TextEditingController(text: clipBoardText.text);
- await showDialog(
- context: context,
- child: new AlertDialog(
- contentPadding: const EdgeInsets.all(16.0),
- content: new Row(
- children: [
- new Expanded(
- child: new TextField(
- autofocus: true,
- keyboardType: TextInputType.url,
- controller: urlController,
- decoration:
- new InputDecoration(labelText: 'Video URL (Webm/mp4)'),
- ),
- )
- ],
- ),
- actions: [
- new FlatButton(
- child: const Text('Cancel'),
- onPressed: () {
- Navigator.of(context, rootNavigator: true).pop();
- }),
- new FlatButton(
- child: const Text('Insert'),
- onPressed: () {
- Navigator.of(context, rootNavigator: true).pop();
-
- if(controller.text.endsWith('\n') || controller.text.isEmpty) {
- controller.text =
- controller.text + '[video]${urlController.text}[/video]';
- } else {
- controller.text =
- controller.text + '\n[video]${urlController.text}[/video]';
- }
- refreshPreview();
- })
- ],
- ),
- );
- }
-
- void addUserquoteDialog(bcontext) async {
- BuildContext self = bcontext;
- await showDialog(
- context: bcontext,
- child: new AlertDialog(
- title: Text('Select post'),
- contentPadding: const EdgeInsets.all(16.0),
- content: Container(
- height: 400,
- width: 200,
- child: ListView.builder(
- itemCount: this.replyList.length,
- itemBuilder: (BuildContext context, int index) {
- ThreadPost item = this.replyList[index];
- return ListTile(
- title: Text(item.user.username),
- onTap: () {
- Navigator.of(bcontext, rootNavigator: true).pop();
-
- if(controller.text.endsWith('\n') || controller.text.isEmpty) {
- controller.text =
- controller.text + '[userquote]${index + 1}[/userquote]';
- } else {
- controller.text =
- controller.text + '\n[userquote]${index + 1}[/userquote]';
- }
- refreshPreview();
- },
- );
- },
- ),
- ),
- actions: [
- new FlatButton(
- child: const Text('Cancel'),
- onPressed: () {
- Navigator.of(bcontext, rootNavigator: true).pop();
- }),
- ],
- ),
- );
- }
-
- @override
- Widget build(BuildContext wcontext) {
- return DefaultTabController(
- length: 2,
- child: Scaffold(
- key: _scaffoldKey,
- appBar: AppBar(
- title: Text('New post'),
- actions: [
- IconButton(
- onPressed: !_isPosting ? onPressPost : null,
- icon: Icon(Icons.send),
- ),
- ],
- bottom: TabBar(
- tabs: [
- Tab(
- text: 'Edit',
- ),
- Tab(text: 'Preview'),
- ],
- ),
- ),
- body: KnockoutLoadingIndicator(
- show: _isPosting,
- child: TabBarView(
- physics: NeverScrollableScrollPhysics(),
- children: [
- Column(
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: [
- Expanded(
- child: Container(
- padding: EdgeInsets.all(25),
- child: TextField(
- controller: controller,
- focusNode: textFocusNode,
- maxLines: null,
- keyboardType: TextInputType.multiline,
- textCapitalization: TextCapitalization.sentences,
- onChanged: (text) {
- setState(() {
- document = BBCodeHandler().parse(text,
- this.widget.thread, this.replyList);
- });
- },
- ),
- ),
- ),
- Container(
- color: Colors.grey[600],
- padding: EdgeInsets.all(0),
- child: Wrap(
- children: [
- IconButton(
- icon: Icon(Icons.format_bold),
- onPressed: () {
- TextSelection theSelection = controller.selection;
- addTagAtSelection(
- theSelection.start, theSelection.end, 'b');
- },
- ),
- IconButton(
- icon: Icon(Icons.format_italic),
- onPressed: () {
- TextSelection theSelection = controller.selection;
- addTagAtSelection(
- theSelection.start, theSelection.end, 'i');
- },
- ),
- IconButton(
- icon: Icon(Icons.format_underlined),
- onPressed: () {
- TextSelection theSelection = controller.selection;
- addTagAtSelection(
- theSelection.start, theSelection.end, 'u');
- },
- ),
- IconButton(
- icon: Icon(Icons.code),
- onPressed: () {
- TextSelection theSelection = controller.selection;
- addTagAtSelection(
- theSelection.start, theSelection.end, 'code');
- },
- ),
- IconButton(
- icon: Icon(Icons.title),
- onPressed: () {
- TextSelection theSelection = controller.selection;
- addTagAtSelection(
- theSelection.start, theSelection.end, 'h1');
- },
- ),
- IconButton(
- icon: Icon(Icons.format_size),
- onPressed: () {
- TextSelection theSelection = controller.selection;
- addTagAtSelection(
- theSelection.start, theSelection.end, 'h2');
- },
- ),
- IconButton(
- icon: Icon(Icons.format_quote),
- onPressed: () {
- TextSelection theSelection = controller.selection;
- addTagAtSelection(
- theSelection.start, theSelection.end, 'blockquote');
- },
- ),
- IconButton(
- icon: Icon(Icons.image),
- onPressed: () {
- addImageDialog();
- },
- ),
- IconButton(
- icon: Icon(Icons.link),
- onPressed: () {
- addLinkDialog();
- },
- ),
- IconButton(
- icon: Icon(Icons.ondemand_video),
- onPressed: () {
- addYoutubeVideoDialog();
- },
- ),
- IconButton(
- icon: Icon(Icons.videocam),
- onPressed: () {
- addVideoDialog();
- },
- ),
- if (this.replyList.length > 0)
- Builder(
- builder: (BuildContext bcontext) {
- return IconButton(
- tooltip: 'Insert userquote',
- icon: Icon(Icons.message),
- onPressed: () {
- addUserquoteDialog(bcontext);
- },
- );
- },
- ),
- ],
- ),
- ),
- ],
- ),
- Container(
- child: SingleChildScrollView(
- child: Container(
- padding: EdgeInsets.all(15),
- child: SlateDocumentParser(
- context: context,
- scaffoldkey: _scaffoldKey,
- slateObject: document,
- onPressSpoiler: (content) {
- onPressSpoiler(context, content);
- },
- ),
- ),
- ),
- ),
- ],
- ),
- ),
- ),
- );
- }
-}
diff --git a/lib/screens/home.dart b/lib/screens/home.dart
index 63c7eb2..08a5a2e 100644
--- a/lib/screens/home.dart
+++ b/lib/screens/home.dart
@@ -2,9 +2,13 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
+import 'package:hive/hive.dart';
+import 'package:knocky/helpers/hiveHelper.dart';
import 'package:knocky/models/subforum.dart';
import 'package:after_layout/after_layout.dart';
+import 'package:knocky/screens/thread.dart';
import 'package:knocky/widget/Drawer.dart';
+import 'package:quick_actions/quick_actions.dart';
import 'package:scoped_model/scoped_model.dart';
import 'package:knocky/state/authentication.dart';
import 'package:knocky/state/subscriptions.dart';
@@ -12,6 +16,12 @@ import 'package:knocky/state/appState.dart';
import 'package:knocky/widget/tab-navigator.dart';
import 'package:knocky/events.dart';
+import 'dart:async';
+import 'dart:io';
+
+import 'package:uni_links/uni_links.dart';
+import 'package:flutter/services.dart' show PlatformException;
+
class HomeScreen extends StatefulWidget {
@override
_HomeScreenState createState() => _HomeScreenState();
@@ -30,9 +40,46 @@ class _HomeScreenState extends State
2: GlobalKey(),
3: GlobalKey(),
};
+ StreamSubscription _sub;
void initState() {
super.initState();
+
+ initUniLinks();
+
+ final QuickActions quickActions = new QuickActions();
+
+ quickActions.setShortcutItems([
+ const ShortcutItem(
+ type: 'action_subscriptions',
+ localizedTitle: 'Subscriptions',
+ icon: 'icon_help'),
+ const ShortcutItem(
+ type: 'action_popular',
+ localizedTitle: 'Popular threads',
+ icon: 'icon_help'),
+ const ShortcutItem(
+ type: 'action_latest',
+ localizedTitle: 'Latest threads',
+ icon: 'icon_help')
+ ]);
+
+ quickActions.initialize((shortcutType) {
+ if (shortcutType == 'action_subscriptions') {
+ AppHiveBox.getBox().then((Box box) {
+ box.get('isLoggedIn', defaultValue: false).then((loginState) {
+ if (loginState) {
+ ScopedModel.of(context).setCurrentTab(1);
+ }
+ });
+ });
+ }
+ if (shortcutType == 'action_popular')
+ ScopedModel.of(context).setCurrentTab(3);
+ if (shortcutType == 'action_latest')
+ ScopedModel.of(context).setCurrentTab(2);
+ // More handling code...
+ });
}
@override
@@ -49,9 +96,55 @@ class _HomeScreenState extends State
@override
void dispose() {
_dataSub.cancel();
+ _sub.cancel();
super.dispose();
}
+ Future initUniLinks() async {
+ // Platform messages may fail, so we use a try/catch PlatformException.
+ try {
+ Uri initialUri = await getInitialUri();
+ print(initialUri.toString());
+ if (initialUri != null) handleLink(initialUri);
+ // Parse the link and warn the user, if it is not correct,
+ // but keep in mind it could be `null`.
+ } on PlatformException {
+ // Handle exception by warning the user their action did not succeed
+ // return?
+ }
+
+ // Attach a listener to the stream
+ _sub = getUriLinksStream().listen((Uri uri) {
+ handleLink(uri);
+ // Use the uri and warn the user, if it is not correct
+ }, onError: (err) {
+ // Handle exception by warning the user their action did not succeed
+ });
+ }
+
+ void handleLink (Uri uri) {
+ print(uri.toString());
+ print(uri.pathSegments.length);
+
+ // Handle thread links
+ if (uri.pathSegments.length > 0) {
+ if (uri.pathSegments[0] == 'thread') {
+ int threadId = int.tryParse(uri.pathSegments[1]);
+
+ if (threadId != null) {
+ navigatorKeys[0].currentState.push(MaterialPageRoute(
+ builder: (context) => ThreadScreen(
+ threadId: int.parse(uri.pathSegments[1]),
+ ),
+ ),);
+ }
+ }
+ }
+ uri.pathSegments.forEach((segment) {
+ print(segment);
+ });
+ }
+
Future _onWillPop() async {
// handle login overlay
if (_loginIsOpen) {
diff --git a/lib/screens/newPost.dart b/lib/screens/newPost.dart
index 7495be5..7f37354 100644
--- a/lib/screens/newPost.dart
+++ b/lib/screens/newPost.dart
@@ -1,56 +1,129 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
+import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:knocky/helpers/bbcode.dart';
import 'package:knocky/models/slateDocument.dart';
import 'package:knocky/models/thread.dart';
-import 'package:knocky/widget/SlateDocumentParser/SlateDocumentParser.dart';
+import 'package:knocky/widget/ListEditor.dart';
+import 'package:knocky/widget/PostEditor.dart';
import 'package:knocky/helpers/api.dart';
import 'package:knocky/widget/KnockoutLoadingIndicator.dart';
+import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
+import 'package:knocky/widget/Thread/PostContent.dart';
class NewPostScreen extends StatefulWidget {
- final ThreadPost replyTo;
+ final ThreadPost post;
final Thread thread;
final List replyList;
+ final bool editingPost;
NewPostScreen(
- {this.replyTo, this.thread, this.replyList});
+ {this.post, this.thread, this.replyList, this.editingPost = false});
@override
_NewPostScreenState createState() => _NewPostScreenState();
}
class _NewPostScreenState extends State {
- TextEditingController controller = TextEditingController(text: '');
- SlateObject document;
+ SlateObject document = new SlateObject(
+ object: 'value',
+ document: SlateDocument(object: 'document', nodes: List()),
+ );
GlobalKey _scaffoldKey;
- FocusNode textFocusNode = FocusNode();
bool _isPosting = false;
-
- List history = List();
+ TextEditingController controller = TextEditingController();
+ List replyListConverted = List();
@override
void initState() {
super.initState();
- document = BBCodeHandler()
- .parse(controller.text, this.widget.thread, this.widget.replyList);
+ this.replyListConverted = this.widget.replyList;
+
+ if (this.widget.editingPost) {
+ this.document = this.widget.post.content;
+ } else {
+ if (this.replyListConverted.length > 0) {
+ this.convertReplyEmbedsToText();
+ }
+ }
+
+ if (this.replyListConverted.length == 1) {
+ ThreadPost item = this.replyListConverted.first;
+ this.document.document.nodes.add(
+ SlateNode(
+ object: 'block',
+ type: 'userquote',
+ data: SlateNodeData(
+ postData: NodeDataPostData(
+ postId: item.id,
+ threadId: this.widget.thread.id,
+ threadPage: this.widget.thread.currentPage,
+ username: item.user.username,
+ ),
+ ),
+ nodes: item.content.document.nodes),
+ );
+ }
}
- void onPressPost() async {
+ void convertReplyEmbedsToText() {
+ print('Convert embeds');
setState(() {
- _isPosting = true;
+ this.replyListConverted.forEach((reply) {
+ reply.content.document.nodes.forEach((node) {
+ switch (node.type) {
+ case 'image':
+ case 'youtube':
+ case 'video':
+ case 'strawpoll':
+ case 'twitter':
+ print('Should be convertet');
+ int replyindex = this.replyListConverted.indexOf(reply);
+ int nodeIndex = reply.content.document.nodes.indexOf(node);
+
+ this.replyListConverted[replyindex].content.document.nodes.removeAt(nodeIndex);
+ this.replyListConverted[replyindex].content.document.nodes.insert(nodeIndex, embedConverter(node.type, node.data.src));
+ break;
+ }
+ });
+ });
});
- await KnockoutAPI().newPost(document.toJson(), this.widget.thread.id);
- Navigator.pop(context, true);
}
- void refreshPreview() {
+ SlateNode embedConverter(String type, String url) {
+ return new SlateNode(type: 'paragraph', object: 'block', nodes: [
+ SlateNode(object: 'text', leaves: [
+ SlateLeaf(text: '[${type}: ${url}]', marks: [], object: 'leaf')
+ ])
+ ]);
+ }
+
+ void onPressPost() async {
setState(() {
- document = BBCodeHandler()
- .parse(controller.text, this.widget.thread, this.widget.replyList);
+ _isPosting = true;
});
+
+ if (this.widget.editingPost) {
+ await KnockoutAPI()
+ .updatePost(
+ document.toJson(), this.widget.post.id, this.widget.thread.id)
+ .catchError((error) {
+ print(error);
+ });
+ } else {
+ await KnockoutAPI()
+ .newPost(document.toJson(), this.widget.thread.id)
+ .catchError((error) {
+ print(error);
+ });
+ }
+
+ Navigator.pop(context, true);
}
+ void refreshPreview() {}
+
void onPressSpoiler(BuildContext context, String content) {
showDialog(
context: context,
@@ -72,7 +145,8 @@ class _NewPostScreenState extends State {
);
}
- void addTagAtSelection(int start, int end, String tag) {
+ void addTagAtSelection(
+ TextEditingController controller, int start, int end, String tag) {
RegExp regExp = new RegExp(
r'(\[([^/].*?)(=(.+?))?\](.*?)\[/\2\]|\[([^/].*?)(=(.+?))?\])',
caseSensitive: false,
@@ -84,20 +158,29 @@ class _NewPostScreenState extends State {
String replaceWith = '';
if (regExp.hasMatch(selectedText)) {
- replaceWith = selectedText.replaceAll('[${tag}]', ''); //ignore: unnecessary_brace_in_string_interps
- replaceWith = replaceWith.replaceAll('[/${tag}]', ''); //ignore: unnecessary_brace_in_string_interps
+ replaceWith = selectedText.replaceAll(
+ '[${tag}]', ''); //ignore: unnecessary_brace_in_string_interps
+ replaceWith = replaceWith.replaceAll(
+ '[/${tag}]', ''); //ignore: unnecessary_brace_in_string_interps
} else {
- replaceWith = newline + '[${tag}]' + selectedText + '[/${tag}]'; //ignore: unnecessary_brace_in_string_interps
+ replaceWith = newline +
+ '[${tag}]' +
+ selectedText +
+ '[/${tag}]'; //ignore: unnecessary_brace_in_string_interps
}
controller.text = controller.text.replaceRange(start, end, replaceWith);
refreshPreview();
}
+ /**
+ * Toolbar callbacks
+ */
+
void addImageDialog() async {
ClipboardData clipBoardText = await Clipboard.getData('text/plain');
- TextEditingController imgurlController =
- TextEditingController(text: clipBoardText.text);
+ TextEditingController imgurlController = TextEditingController(
+ text: clipBoardText != null ? clipBoardText.text : '');
await showDialog(
context: context,
child: new AlertDialog(
@@ -124,22 +207,22 @@ class _NewPostScreenState extends State {
child: const Text('Insert'),
onPressed: () {
Navigator.of(context, rootNavigator: true).pop();
-
- if(controller.text.endsWith('\n') || controller.text.isEmpty) {
- controller.text =
- controller.text + '[img]${imgurlController.text}[/img]';
- } else {
- controller.text =
- controller.text + '\n[img]${imgurlController.text}[/img]';
- }
- refreshPreview();
+ setState(() {
+ document.document.nodes.add(
+ SlateNode(
+ object: 'block',
+ type: 'image',
+ data: SlateNodeData(src: imgurlController.text),
+ ),
+ );
+ });
})
],
),
);
}
- void addLinkDialog() async {
+ void addLinkDialog(TextEditingController mainController) async {
ClipboardData clipBoardText = await Clipboard.getData('text/plain');
TextEditingController urlController =
TextEditingController(text: clipBoardText.text);
@@ -170,12 +253,13 @@ class _NewPostScreenState extends State {
onPressed: () {
Navigator.of(context, rootNavigator: true).pop();
- if(controller.text.endsWith('\n') || controller.text.isEmpty) {
- controller.text =
- controller.text + '[url]${urlController.text}[/url]';
+ if (mainController.text.endsWith('\n') ||
+ controller.text.isEmpty) {
+ mainController.text =
+ mainController.text + '[url]${urlController.text}[/url]';
} else {
- controller.text =
- controller.text + '\n[url]${urlController.text}[/url]';
+ mainController.text = mainController.text +
+ '\n[url]${urlController.text}[/url]';
}
refreshPreview();
@@ -214,16 +298,13 @@ class _NewPostScreenState extends State {
new FlatButton(
child: const Text('Insert'),
onPressed: () {
- Navigator.of(context, rootNavigator: true).pop();
+ setState(() {
+ this.document.document.nodes.add(SlateNode(
+ type: 'youtube',
+ data: SlateNodeData(src: urlController.text)));
+ });
- if(controller.text.endsWith('\n') || controller.text.isEmpty) {
- controller.text = controller.text +
- '[youtube]${urlController.text}[/youtube]';
- } else {
- controller.text = controller.text +
- '\n[youtube]${urlController.text}[/youtube]';
- }
- refreshPreview();
+ Navigator.of(context, rootNavigator: true).pop();
})
],
),
@@ -236,7 +317,7 @@ class _NewPostScreenState extends State {
TextEditingController(text: clipBoardText.text);
await showDialog(
context: context,
- child: new AlertDialog(
+ builder: (BuildContext context) => new AlertDialog(
contentPadding: const EdgeInsets.all(16.0),
content: new Row(
children: [
@@ -260,16 +341,16 @@ class _NewPostScreenState extends State {
new FlatButton(
child: const Text('Insert'),
onPressed: () {
- Navigator.of(context, rootNavigator: true).pop();
+ setState(() {
+ this.document.document.nodes.add(
+ SlateNode(
+ type: 'video',
+ data: SlateNodeData(src: urlController.text),
+ ),
+ );
+ });
- if(controller.text.endsWith('\n') || controller.text.isEmpty) {
- controller.text =
- controller.text + '[video]${urlController.text}[/video]';
- } else {
- controller.text =
- controller.text + '\n[video]${urlController.text}[/video]';
- }
- refreshPreview();
+ Navigator.of(context, rootNavigator: true).pop();
})
],
),
@@ -286,22 +367,29 @@ class _NewPostScreenState extends State {
height: 400,
width: 200,
child: ListView.builder(
- itemCount: this.widget.replyList.length,
+ itemCount: this.replyListConverted.length,
itemBuilder: (BuildContext context, int index) {
- ThreadPost item = this.widget.replyList[index];
+ ThreadPost item = this.replyListConverted[index];
return ListTile(
title: Text(item.user.username),
onTap: () {
+ setState(() {
+ this.document.document.nodes.add(
+ SlateNode(
+ object: 'block',
+ type: 'userquote',
+ data: SlateNodeData(
+ postData: NodeDataPostData(
+ postId: item.id,
+ threadId: this.widget.thread.id,
+ threadPage: this.widget.thread.currentPage,
+ username: item.user.username,
+ ),
+ ),
+ nodes: item.content.document.nodes),
+ );
+ });
Navigator.of(bcontext, rootNavigator: true).pop();
-
- if(controller.text.endsWith('\n') || controller.text.isEmpty) {
- controller.text =
- controller.text + '[userquote]${index + 1}[/userquote]';
- } else {
- controller.text =
- controller.text + '\n[userquote]${index + 1}[/userquote]';
- }
- refreshPreview();
},
);
},
@@ -318,6 +406,570 @@ class _NewPostScreenState extends State {
);
}
+ void addTextBlock() {
+ setState(
+ () {
+ this.document.document.nodes.add(
+ SlateNode(
+ type: 'paragraph',
+ object: 'block',
+ nodes: [
+ SlateNode(
+ object: 'text',
+ leaves: [],
+ ),
+ ],
+ ),
+ );
+ },
+ );
+ }
+
+ void addHeadingBlock(String type) {
+ setState(
+ () {
+ this.document.document.nodes.add(
+ SlateNode(
+ type: 'heading-' + type,
+ object: 'block',
+ nodes: [
+ SlateNode(
+ object: 'text',
+ leaves: [],
+ ),
+ ],
+ ),
+ );
+ },
+ );
+ }
+
+ void addQuoteBlock() {
+ setState(() {
+ this
+ .document
+ .document
+ .nodes
+ .add(SlateNode(type: 'block-quote', nodes: List()));
+ });
+ }
+
+ void addListBlock(String type) {
+ setState(() {
+ this
+ .document
+ .document
+ .nodes
+ .add(SlateNode(object: 'block', type: type, nodes: List()));
+ });
+ }
+
+ void addTwitterEmbed() async {
+ ClipboardData clipBoardText = await Clipboard.getData('text/plain');
+ TextEditingController urlController =
+ TextEditingController(text: clipBoardText.text);
+ await showDialog(
+ context: context,
+ child: new AlertDialog(
+ contentPadding: const EdgeInsets.all(16.0),
+ content: new Row(
+ children: [
+ new Expanded(
+ child: new TextField(
+ autofocus: true,
+ keyboardType: TextInputType.url,
+ controller: urlController,
+ decoration: new InputDecoration(labelText: 'Twitter URL'),
+ ),
+ )
+ ],
+ ),
+ actions: [
+ new FlatButton(
+ child: const Text('Cancel'),
+ onPressed: () {
+ Navigator.of(context, rootNavigator: true).pop();
+ }),
+ new FlatButton(
+ child: const Text('Insert'),
+ onPressed: () {
+ setState(() {
+ this.document.document.nodes.add(
+ SlateNode(
+ type: 'twitter',
+ object: 'block',
+ data: SlateNodeData(src: urlController.text),
+ nodes: [
+ SlateNode(
+ object: 'text',
+ leaves: [
+ SlateLeaf(text: '', marks: [], object: 'leaf')
+ ],
+ ),
+ ]),
+ );
+ });
+
+ Navigator.of(context, rootNavigator: true).pop();
+ })
+ ],
+ ),
+ );
+ }
+
+ void addStrawpollEmbed() async {
+ ClipboardData clipBoardText = await Clipboard.getData('text/plain');
+ TextEditingController urlController =
+ TextEditingController(text: clipBoardText.text);
+ await showDialog(
+ context: context,
+ child: new AlertDialog(
+ contentPadding: const EdgeInsets.all(16.0),
+ content: new Row(
+ children: [
+ new Expanded(
+ child: new TextField(
+ autofocus: true,
+ keyboardType: TextInputType.url,
+ controller: urlController,
+ decoration: new InputDecoration(labelText: 'Strawpoll URL'),
+ ),
+ )
+ ],
+ ),
+ actions: [
+ new FlatButton(
+ child: const Text('Cancel'),
+ onPressed: () {
+ Navigator.of(context, rootNavigator: true).pop();
+ }),
+ new FlatButton(
+ child: const Text('Insert'),
+ onPressed: () {
+ setState(() {
+ this.document.document.nodes.add(
+ SlateNode(
+ type: 'strawpoll',
+ object: 'block',
+ data: SlateNodeData(src: urlController.text),
+ nodes: [
+ SlateNode(
+ object: 'text',
+ leaves: [
+ SlateLeaf(text: '', marks: [], object: 'leaf')
+ ],
+ ),
+ ]),
+ );
+ });
+
+ Navigator.of(context, rootNavigator: true).pop();
+ })
+ ],
+ ),
+ );
+ }
+
+ /*
+ Block tap callbacks
+ */
+ void showTextEditDialog(BuildContext context, SlateNode node) {
+ String bbcodeText = BBCodeHandler().slateParagraphToBBCode(node);
+ TextEditingController textEditingController =
+ TextEditingController(text: bbcodeText);
+
+ showDialog(
+ context: context,
+ builder: (BuildContext bcontext) {
+ return AlertDialog(
+ actions: [
+ FlatButton(
+ child: Text('Remove'),
+ onPressed: () {
+ setState(() {
+ int index = this.document.document.nodes.indexOf(node);
+ this.document.document.nodes.removeAt(index);
+ });
+ Navigator.pop(bcontext);
+ },
+ ),
+ FlatButton(
+ child: Text('Save'),
+ onPressed: () {
+ SlateNode newNode = BBCodeHandler()
+ .parse(textEditingController.text, type: node.type);
+ print(BBCodeHandler().slateParagraphToBBCode(newNode));
+ print(newNode.toJson());
+ Navigator.pop(bcontext, newNode);
+ },
+ )
+ ],
+ title: Text('Edit text block'),
+ content: Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ mainAxisAlignment: MainAxisAlignment.start,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Container(
+ margin: EdgeInsets.only(bottom: 10),
+ child: TextField(
+ keyboardType: TextInputType.multiline,
+ maxLines: null,
+ textCapitalization: TextCapitalization.sentences,
+ controller: textEditingController,
+ ),
+ ),
+ Container(
+ color: Colors.grey[600],
+ padding: EdgeInsets.all(0),
+ child: Wrap(
+ children: [
+ IconButton(
+ icon: Icon(Icons.format_bold),
+ onPressed: () {
+ TextSelection theSelection =
+ textEditingController.selection;
+ addTagAtSelection(textEditingController,
+ theSelection.start, theSelection.end, 'b');
+ },
+ ),
+ IconButton(
+ icon: Icon(Icons.format_italic),
+ onPressed: () {
+ TextSelection theSelection =
+ textEditingController.selection;
+ addTagAtSelection(textEditingController,
+ theSelection.start, theSelection.end, 'i');
+ },
+ ),
+ IconButton(
+ icon: Icon(Icons.format_underlined),
+ onPressed: () {
+ TextSelection theSelection =
+ textEditingController.selection;
+ addTagAtSelection(textEditingController,
+ theSelection.start, theSelection.end, 'u');
+ },
+ ),
+ IconButton(
+ icon: Icon(Icons.code),
+ onPressed: () {
+ TextSelection theSelection =
+ textEditingController.selection;
+ addTagAtSelection(textEditingController,
+ theSelection.start, theSelection.end, 'code');
+ },
+ ),
+ IconButton(
+ icon: Icon(Icons.link),
+ onPressed: () {
+ addLinkDialog(textEditingController);
+ },
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ );
+ }).then((newNode) {
+ setState(() {
+ if (newNode != null) {
+ int index = this.document.document.nodes.indexOf(node);
+ this.document.document.nodes[index] = newNode;
+ }
+ });
+ });
+ }
+
+ void editImageDialog(slateObject, SlateNode node) async {
+ TextEditingController imgurlController =
+ TextEditingController(text: node.data.src);
+ await showDialog(
+ context: context,
+ builder: (BuildContext context) {
+ return new AlertDialog(
+ contentPadding: const EdgeInsets.all(16.0),
+ content: new Row(
+ children: [
+ new Expanded(
+ child: new TextField(
+ autofocus: true,
+ keyboardType: TextInputType.url,
+ controller: imgurlController,
+ decoration: new InputDecoration(labelText: 'Image url'),
+ ),
+ )
+ ],
+ ),
+ actions: [
+ new FlatButton(
+ child: const Text('Remove'),
+ onPressed: () {
+ setState(() {
+ int index = this.document.document.nodes.indexOf(node);
+ this.document.document.nodes.removeAt(index);
+ });
+ Navigator.of(context, rootNavigator: true).pop();
+ }),
+ new FlatButton(
+ child: const Text('Update'),
+ onPressed: () {
+ setState(() {
+ int index = this.document.document.nodes.indexOf(node);
+ this.document.document.nodes[index].data.src =
+ imgurlController.text;
+ });
+ Navigator.of(context, rootNavigator: true).pop();
+ })
+ ],
+ );
+ },
+ );
+ }
+
+ void editYoutubeVideoDialog(String youTubeUrl, SlateNode node) async {
+ TextEditingController urlController =
+ TextEditingController(text: node.data.src);
+ await showDialog(
+ context: context,
+ builder: (BuildContext context) => AlertDialog(
+ contentPadding: const EdgeInsets.all(16.0),
+ content: new Row(
+ children: [
+ new Expanded(
+ child: new TextField(
+ autofocus: true,
+ keyboardType: TextInputType.url,
+ controller: urlController,
+ decoration: new InputDecoration(labelText: 'YouTube URL'),
+ ),
+ )
+ ],
+ ),
+ actions: [
+ new FlatButton(
+ child: const Text('Remove'),
+ onPressed: () {
+ setState(() {
+ int index = this.document.document.nodes.indexOf(node);
+ this.document.document.nodes.removeAt(index);
+ });
+ Navigator.of(context, rootNavigator: true).pop();
+ }),
+ new FlatButton(
+ child: const Text('Update'),
+ onPressed: () {
+ setState(() {
+ int index = this.document.document.nodes.indexOf(node);
+ this.document.document.nodes[index].data.src =
+ urlController.text;
+ });
+ Navigator.of(context, rootNavigator: true).pop();
+ })
+ ],
+ ),
+ );
+ }
+
+ void editVideoDialog(SlateNode node) async {
+ TextEditingController urlController =
+ TextEditingController(text: node.data.src);
+ await showDialog(
+ context: context,
+ builder: (BuildContext context) => new AlertDialog(
+ contentPadding: const EdgeInsets.all(16.0),
+ content: new Row(
+ children: [
+ new Expanded(
+ child: new TextField(
+ autofocus: true,
+ keyboardType: TextInputType.url,
+ controller: urlController,
+ decoration:
+ new InputDecoration(labelText: 'Video URL (Webm/mp4)'),
+ ),
+ )
+ ],
+ ),
+ actions: [
+ new FlatButton(
+ child: const Text('Remove'),
+ onPressed: () {
+ setState(() {
+ int index = this.document.document.nodes.indexOf(node);
+ this.document.document.nodes.removeAt(index);
+ });
+ Navigator.of(context, rootNavigator: true).pop();
+ }),
+ new FlatButton(
+ child: const Text('Update'),
+ onPressed: () {
+ setState(() {
+ int index = this.document.document.nodes.indexOf(node);
+ this.document.document.nodes[index].data.src =
+ urlController.text;
+ });
+
+ Navigator.of(context, rootNavigator: true).pop();
+ })
+ ],
+ ),
+ );
+ }
+
+ void editList(SlateNode node) async {
+ List listItems = List();
+
+ node.nodes.forEach((listItem) {
+ listItems.add(listItem);
+ });
+
+ await showDialog(
+ context: context,
+ builder: (BuildContext context) => AlertDialog(
+ title: Text('Edit list'),
+ contentPadding: const EdgeInsets.all(16.0),
+ content: ListEditor(
+ oldListItems: listItems,
+ onListUpdated: (newListItems) {
+ setState(() {
+ int index = this.document.document.nodes.indexOf(node);
+ this.document.document.nodes[index].nodes = newListItems;
+ });
+ },
+ ),
+ actions: [
+ new FlatButton(
+ child: const Text('Remove'),
+ onPressed: () {
+ Navigator.of(context, rootNavigator: true).pop();
+ }),
+ new FlatButton(
+ child: const Text('Update'),
+ onPressed: () {
+ Navigator.of(context, rootNavigator: true).pop();
+ })
+ ],
+ ),
+ );
+ }
+
+ void editTwitterEmbed(String youTubeUrl, SlateNode node) async {
+ TextEditingController urlController =
+ TextEditingController(text: node.data.src);
+ await showDialog(
+ context: context,
+ builder: (BuildContext context) => AlertDialog(
+ contentPadding: const EdgeInsets.all(16.0),
+ content: new Row(
+ children: [
+ new Expanded(
+ child: new TextField(
+ autofocus: true,
+ keyboardType: TextInputType.url,
+ controller: urlController,
+ decoration: new InputDecoration(labelText: 'Twitter URL'),
+ ),
+ )
+ ],
+ ),
+ actions: [
+ new FlatButton(
+ child: const Text('Remove'),
+ onPressed: () {
+ setState(() {
+ int index = this.document.document.nodes.indexOf(node);
+ this.document.document.nodes.removeAt(index);
+ });
+ Navigator.of(context, rootNavigator: true).pop();
+ }),
+ new FlatButton(
+ child: const Text('Update'),
+ onPressed: () {
+ setState(() {
+ int index = this.document.document.nodes.indexOf(node);
+ this.document.document.nodes[index].data.src =
+ urlController.text;
+ });
+ Navigator.of(context, rootNavigator: true).pop();
+ })
+ ],
+ ),
+ );
+ }
+
+ void editStrawpollEmbed(String url, SlateNode node) async {
+ TextEditingController urlController =
+ TextEditingController(text: node.data.src);
+ await showDialog(
+ context: context,
+ builder: (BuildContext context) => AlertDialog(
+ contentPadding: const EdgeInsets.all(16.0),
+ content: new Row(
+ children: [
+ new Expanded(
+ child: new TextField(
+ autofocus: true,
+ keyboardType: TextInputType.url,
+ controller: urlController,
+ decoration: new InputDecoration(labelText: 'Strawpoll URL'),
+ ),
+ )
+ ],
+ ),
+ actions: [
+ new FlatButton(
+ child: const Text('Remove'),
+ onPressed: () {
+ setState(() {
+ int index = this.document.document.nodes.indexOf(node);
+ this.document.document.nodes.removeAt(index);
+ });
+ Navigator.of(context, rootNavigator: true).pop();
+ }),
+ new FlatButton(
+ child: const Text('Update'),
+ onPressed: () {
+ setState(() {
+ int index = this.document.document.nodes.indexOf(node);
+ this.document.document.nodes[index].data.src =
+ urlController.text;
+ });
+ Navigator.of(context, rootNavigator: true).pop();
+ })
+ ],
+ ),
+ );
+ }
+
+ void editUserQuote(SlateNode node) async {
+ await showDialog(
+ context: context,
+ builder: (BuildContext context) => AlertDialog(
+ title: Text('Remove user quote?'),
+ contentPadding: const EdgeInsets.all(16.0),
+ content: Text('Are you sure you want to remove the user quote?'),
+ actions: [
+ new FlatButton(
+ child: const Text('No'),
+ onPressed: () {
+ Navigator.of(context, rootNavigator: true).pop();
+ }),
+ new FlatButton(
+ child: const Text('Yes'),
+ onPressed: () {
+ setState(() {
+ int index = this.document.document.nodes.indexOf(node);
+ this.document.document.nodes.removeAt(index);
+ });
+ Navigator.of(context, rootNavigator: true).pop();
+ }),
+ ],
+ ),
+ );
+ }
+
@override
Widget build(BuildContext wcontext) {
return DefaultTabController(
@@ -325,7 +977,7 @@ class _NewPostScreenState extends State {
child: Scaffold(
key: _scaffoldKey,
appBar: AppBar(
- title: Text('New post'),
+ title: Text(this.widget.editingPost ? 'Edit post ' : 'New post'),
actions: [
IconButton(
onPressed: !_isPosting ? onPressPost : null,
@@ -346,141 +998,54 @@ class _NewPostScreenState extends State {
child: TabBarView(
physics: NeverScrollableScrollPhysics(),
children: [
- Column(
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: [
- Expanded(
- child: Container(
- padding: EdgeInsets.all(25),
- child: TextField(
- controller: controller,
- focusNode: textFocusNode,
- maxLines: null,
- keyboardType: TextInputType.multiline,
- textCapitalization: TextCapitalization.sentences,
- onChanged: (text) {
- setState(() {
- document = BBCodeHandler().parse(text,
- this.widget.thread, this.widget.replyList);
- });
- },
- ),
- ),
- ),
- Container(
- color: Colors.grey[600],
- padding: EdgeInsets.all(0),
- child: Wrap(
- children: [
- IconButton(
- icon: Icon(Icons.format_bold),
- onPressed: () {
- TextSelection theSelection = controller.selection;
- addTagAtSelection(
- theSelection.start, theSelection.end, 'b');
- },
- ),
- IconButton(
- icon: Icon(Icons.format_italic),
- onPressed: () {
- TextSelection theSelection = controller.selection;
- addTagAtSelection(
- theSelection.start, theSelection.end, 'i');
- },
- ),
- IconButton(
- icon: Icon(Icons.format_underlined),
- onPressed: () {
- TextSelection theSelection = controller.selection;
- addTagAtSelection(
- theSelection.start, theSelection.end, 'u');
- },
- ),
- IconButton(
- icon: Icon(Icons.code),
- onPressed: () {
- TextSelection theSelection = controller.selection;
- addTagAtSelection(
- theSelection.start, theSelection.end, 'code');
- },
- ),
- IconButton(
- icon: Icon(Icons.title),
- onPressed: () {
- TextSelection theSelection = controller.selection;
- addTagAtSelection(
- theSelection.start, theSelection.end, 'h1');
- },
- ),
- IconButton(
- icon: Icon(Icons.format_size),
- onPressed: () {
- TextSelection theSelection = controller.selection;
- addTagAtSelection(
- theSelection.start, theSelection.end, 'h2');
- },
- ),
- IconButton(
- icon: Icon(Icons.format_quote),
- onPressed: () {
- TextSelection theSelection = controller.selection;
- addTagAtSelection(
- theSelection.start, theSelection.end, 'blockquote');
- },
- ),
- IconButton(
- icon: Icon(Icons.image),
- onPressed: () {
- addImageDialog();
- },
- ),
- IconButton(
- icon: Icon(Icons.link),
- onPressed: () {
- addLinkDialog();
- },
- ),
- IconButton(
- icon: Icon(Icons.ondemand_video),
- onPressed: () {
- addYoutubeVideoDialog();
- },
- ),
- IconButton(
- icon: Icon(Icons.videocam),
- onPressed: () {
- addVideoDialog();
- },
- ),
- if (this.widget.replyList.length > 0)
- Builder(
- builder: (BuildContext bcontext) {
- return IconButton(
- tooltip: 'Insert userquote',
- icon: Icon(Icons.message),
- onPressed: () {
- addUserquoteDialog(bcontext);
- },
- );
- },
- ),
- ],
- ),
- ),
- ],
+ PostEditor(
+ document: document,
+ replyList: this.replyListConverted,
+ // Blocks
+ onTapTextBlock: this.showTextEditDialog,
+ onTapImageBlock: this.editImageDialog,
+ onTapQuoteBlock: this.showTextEditDialog,
+ onTapYouTubeBlock: this.editYoutubeVideoDialog,
+ onTapVideoBlock: this.editVideoDialog,
+ onTapListBlock: this.editList,
+ onTapTwitterBlock: this.editTwitterEmbed,
+ onTapUserQuoteBlock: this.editUserQuote,
+ onTapStrawpollBlock: this.editStrawpollEmbed,
+ // Toolbar
+ onTapAddTextBlock: this.addTextBlock,
+ onTapAddHeadingOne: () => this.addHeadingBlock('one'),
+ onTapAddHeadingTwo: () => this.addHeadingBlock('two'),
+ onTapAddImage: this.addImageDialog,
+ onTapAddQuote: this.addQuoteBlock,
+ onTapAddYouTubeVideo: this.addYoutubeVideoDialog,
+ onTapAddVideo: this.addVideoDialog,
+ onTapAddBulletedList: () => this.addListBlock('bulleted-list'),
+ onTapAddNumberedList: () => this.addListBlock('numbered-list'),
+ onTapAddTwitterEmbed: this.addTwitterEmbed,
+ onTapAddStrawPollEmbed: this.addStrawpollEmbed,
+ onTapAddUserQuote: () => this.addUserquoteDialog(context),
+ onReorderHandler: (int oldIndex, int newIndex) {
+ if (oldIndex < newIndex) {
+ // removing the item at oldIndex will shorten the list by 1.
+ newIndex -= 1;
+ }
+ setState(() {
+ final SlateNode element =
+ document.document.nodes.removeAt(oldIndex);
+ document.document.nodes.insert(newIndex, element);
+ });
+ },
),
Container(
child: SingleChildScrollView(
child: Container(
padding: EdgeInsets.all(15),
- child: SlateDocumentParser(
- context: context,
- scaffoldkey: _scaffoldKey,
- slateObject: document,
- onPressSpoiler: (content) {
- onPressSpoiler(context, content);
- },
- ),
+ child: PostContent(
+ content: document,
+ onTapSpoiler: (text) {
+ onPressSpoiler(context, text);
+ },
+ scaffoldKey: this._scaffoldKey),
),
),
),
diff --git a/lib/screens/thread.dart b/lib/screens/thread.dart
index 31c128c..2808504 100644
--- a/lib/screens/thread.dart
+++ b/lib/screens/thread.dart
@@ -5,8 +5,8 @@ import 'package:flutter/rendering.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:knocky/helpers/api.dart';
import 'package:after_layout/after_layout.dart';
+import 'package:knocky/models/slateDocument.dart';
import 'package:knocky/models/thread.dart';
-import 'package:knocky/screens/editPost.dart';
import 'package:knocky/widget/Thread/ThreadPostItem.dart';
import 'package:knocky/widget/KnockoutLoadingIndicator.dart';
import 'package:numberpicker/numberpicker.dart';
@@ -20,7 +20,7 @@ class ThreadScreen extends StatefulWidget {
final int threadId;
final int page;
- ThreadScreen({this.title, this.page = 1, this.postCount, this.threadId});
+ ThreadScreen({this.title = 'Loading thread', this.page = 1, this.postCount, this.threadId});
@override
_ThreadScreenState createState() => _ThreadScreenState();
@@ -45,7 +45,10 @@ class _ThreadScreenState extends State
void initState() {
super.initState();
_currentPage = this.widget.page;
- _totalPages = (widget.postCount / 20).ceil();
+
+ if (widget.postCount != null) {
+ _totalPages = (widget.postCount / 20).ceil();
+ }
prepareAnimations();
@@ -118,6 +121,7 @@ class _ThreadScreenState extends State
setState(() {
details = res;
_isLoading = false;
+ _totalPages = (details.totalPosts / 20).ceil();
});
checkIfShouldMarkThreadRead();
});
@@ -353,17 +357,11 @@ class _ThreadScreenState extends State
void onPressReply(ThreadPost post) async {
if (postsToReplyTo.length > 0) {
- onLongPressReply(post);
+ onLongPressReply(new ThreadPost.clone(post));
} else {
List reply = List();
reply.add(
- new ThreadPost(
- bans: post.bans,
- content: post.content,
- createdAt: post.createdAt,
- id: post.id,
- ratings: post.ratings,
- user: post.user),
+ new ThreadPost.clone(post),
);
final result = await Navigator.push(
@@ -480,6 +478,9 @@ class _ThreadScreenState extends State
void onSelectOverflowItem(int item) {
switch (item) {
+ case 0:
+ refreshPage();
+ break;
case 1:
onTapSubscribe(context);
break;
@@ -491,12 +492,15 @@ class _ThreadScreenState extends State
}
void onTapEditPost (BuildContext context, ThreadPost post) async {
+
final result = await Navigator.push(
context,
MaterialPageRoute(
- builder: (context) => EditPostScreen(
+ builder: (context) => NewPostScreen(
+ editingPost: true,
thread: details,
post: post,
+ replyList: List(),
),
),
);
@@ -510,6 +514,7 @@ class _ThreadScreenState extends State
print('Do the scroll');
scrollController.jumpTo(scrollController.position.maxScrollExtent);
}
+
}
@override
@@ -525,6 +530,7 @@ class _ThreadScreenState extends State
onSelected: onSelectOverflowItem,
itemBuilder: (BuildContext context) {
return [
+ overFlowItem(Icon(Icons.refresh), 'Refresh', 0),
if (details != null)
overFlowItem(
Icon(FontAwesomeIcons.eye), 'Subscribe to thread', 1),
diff --git a/lib/widget/CategoryListItem.dart b/lib/widget/CategoryListItem.dart
index ec35a29..5573b64 100644
--- a/lib/widget/CategoryListItem.dart
+++ b/lib/widget/CategoryListItem.dart
@@ -89,7 +89,7 @@ class CategoryListItem extends StatelessWidget {
),
Container(
child: CachedNetworkImage(
- imageUrl: subforum.icon,
+ imageUrl: !subforum.icon.contains('static') ? subforum.icon : 'https://knockout.chat' + subforum.icon,
width: 40.0,
),
)
diff --git a/lib/widget/ListEditor.dart b/lib/widget/ListEditor.dart
new file mode 100644
index 0000000..a0cb547
--- /dev/null
+++ b/lib/widget/ListEditor.dart
@@ -0,0 +1,343 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:knocky/helpers/bbcode.dart';
+import 'package:knocky/models/slateDocument.dart';
+import 'package:knocky/widget/SlateDocumentParser/SlateDocumentParser.dart';
+
+class ListEditor extends StatefulWidget {
+ final List oldListItems;
+ final Function onTapListItem;
+ final Function onListUpdated;
+
+ ListEditor({
+ this.oldListItems,
+ this.onTapListItem,
+ this.onListUpdated,
+ });
+
+ @override
+ _ListEditorState createState() => _ListEditorState();
+}
+
+class _ListEditorState extends State {
+ List currentItemList = List();
+
+ @override
+ void initState() {
+ super.initState();
+ currentItemList = this.widget.oldListItems;
+ }
+
+ List leafHandler(List leaves, {double fontSize = 14.0}) {
+ List lines = List();
+ leaves.forEach((leaf) {
+ bool isBold = leaf.marks.where((mark) => mark.type == 'bold').length > 0;
+
+ bool isItalic =
+ leaf.marks.where((mark) => mark.type == 'italic').length > 0;
+
+ bool isUnderlined =
+ leaf.marks.where((mark) => mark.type == 'underlined').length > 0;
+
+ bool isCode = leaf.marks.where((mark) => mark.type == 'code').length > 0;
+
+ bool isSpoiler =
+ leaf.marks.where((mark) => mark.type == 'spoiler').length > 0;
+
+ TextStyle textStyle = Theme.of(context).textTheme.body1.copyWith(
+ fontSize: fontSize,
+ fontFamily: isCode ? 'RobotoMono' : 'Roboto',
+ decoration:
+ isUnderlined ? TextDecoration.underline : TextDecoration.none,
+ fontWeight: isBold ? FontWeight.bold : FontWeight.normal,
+ fontStyle: isItalic ? FontStyle.italic : FontStyle.normal,
+ );
+
+ TextStyle spoilerStyle = textStyle.copyWith(
+ background: Paint()..color = Theme.of(context).textTheme.body1.color,
+ color: Theme.of(context).textTheme.body1.color);
+
+ if (isSpoiler) {
+ lines.add(
+ TextSpan(
+ text: leaf.text,
+ style: spoilerStyle,
+ ),
+ );
+ } else {
+ lines.add(
+ TextSpan(
+ text: leaf.text,
+ style: textStyle,
+ ),
+ );
+ }
+ });
+
+ return lines;
+ }
+
+ Widget listItemTextHandler(SlateNode node) {
+ List lines = List();
+
+ // Handle block nodes
+ node.nodes.forEach((line) {
+ if (line.leaves != null) {
+ lines.addAll(this.leafHandler(line.leaves));
+ }
+
+ // Handle inline element
+ if (line.object == 'inline') {
+ // Handle links
+ if (line.type == 'link') {
+ line.nodes.forEach((inlineNode) {
+ inlineNode.leaves.forEach((leaf) {
+ lines.add(
+ TextSpan(
+ text: leaf.text,
+ style: TextStyle(color: Colors.blue),
+ ),
+ );
+ });
+ });
+ } else {
+ line.nodes.forEach((inlineNode) {
+ inlineNode.leaves.forEach((leaf) {
+ lines.add(TextSpan(text: leaf.text));
+ });
+ });
+ }
+ }
+ });
+
+ return Container(
+ child: RichText(
+ text: TextSpan(children: lines),
+ ),
+ );
+ }
+
+ void showTextEditDialog(BuildContext context, SlateNode node) {
+ String bbcodeText = BBCodeHandler().slateParagraphToBBCode(node);
+ TextEditingController textEditingController =
+ TextEditingController(text: bbcodeText);
+
+ showDialog(
+ context: context,
+ builder: (BuildContext bcontext) {
+ return AlertDialog(
+ actions: [
+ FlatButton(
+ child: Text('Remove'),
+ onPressed: () {
+ setState(() {
+ int index = this.currentItemList.indexOf(node);
+ this.currentItemList.removeAt(index);
+ this.widget.onListUpdated(this.currentItemList);
+ });
+ Navigator.pop(bcontext);
+ },
+ ),
+ FlatButton(
+ child: Text('Save'),
+ onPressed: () {
+ SlateNode newNode = BBCodeHandler()
+ .parse(textEditingController.text, type: node.type);
+ Navigator.pop(bcontext, newNode);
+ },
+ )
+ ],
+ title: Text('Edit text'),
+ content: Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ mainAxisAlignment: MainAxisAlignment.start,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Container(
+ margin: EdgeInsets.only(bottom: 10),
+ child: TextField(
+ keyboardType: TextInputType.multiline,
+ maxLines: null,
+ textCapitalization: TextCapitalization.sentences,
+ controller: textEditingController,
+ ),
+ ),
+ Container(
+ color: Colors.grey[600],
+ padding: EdgeInsets.all(0),
+ child: Wrap(
+ children: [
+ IconButton(
+ icon: Icon(Icons.format_bold),
+ onPressed: () {
+ TextSelection theSelection =
+ textEditingController.selection;
+ addTagAtSelection(textEditingController,
+ theSelection.start, theSelection.end, 'b');
+ },
+ ),
+ IconButton(
+ icon: Icon(Icons.format_italic),
+ onPressed: () {
+ TextSelection theSelection =
+ textEditingController.selection;
+ addTagAtSelection(textEditingController,
+ theSelection.start, theSelection.end, 'i');
+ },
+ ),
+ IconButton(
+ icon: Icon(Icons.format_underlined),
+ onPressed: () {
+ TextSelection theSelection =
+ textEditingController.selection;
+ addTagAtSelection(textEditingController,
+ theSelection.start, theSelection.end, 'u');
+ },
+ ),
+ IconButton(
+ icon: Icon(Icons.code),
+ onPressed: () {
+ TextSelection theSelection =
+ textEditingController.selection;
+ addTagAtSelection(textEditingController,
+ theSelection.start, theSelection.end, 'code');
+ },
+ ),
+ IconButton(
+ icon: Icon(Icons.link),
+ onPressed: () {
+ addLinkDialog(textEditingController);
+ },
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ );
+ }).then(
+ (newNode) {
+ SlateNode newNodeAsListItem = newNode;
+ newNodeAsListItem.type = 'list-item';
+ setState(() {
+ int index = this.currentItemList.indexOf(node);
+ this.currentItemList[index] = newNodeAsListItem;
+ this.widget.onListUpdated(this.currentItemList);
+ });
+ },
+ );
+ }
+
+ void addLinkDialog(TextEditingController mainController) async {
+ ClipboardData clipBoardText = await Clipboard.getData('text/plain');
+ TextEditingController urlController =
+ TextEditingController(text: clipBoardText.text);
+ await showDialog(
+ context: context,
+ child: new AlertDialog(
+ contentPadding: const EdgeInsets.all(16.0),
+ content: new Row(
+ children: [
+ new Expanded(
+ child: new TextField(
+ autofocus: true,
+ keyboardType: TextInputType.url,
+ controller: urlController,
+ decoration: new InputDecoration(labelText: 'Url'),
+ ),
+ )
+ ],
+ ),
+ actions: [
+ new FlatButton(
+ child: const Text('Cancel'),
+ onPressed: () {
+ Navigator.of(context, rootNavigator: true).pop();
+ }),
+ new FlatButton(
+ child: const Text('Insert'),
+ onPressed: () {
+ Navigator.of(context, rootNavigator: true).pop();
+
+ if (mainController.text.endsWith('\n') ||
+ mainController.text.isEmpty) {
+ mainController.text =
+ mainController.text + '[url]${urlController.text}[/url]';
+ } else {
+ mainController.text = mainController.text +
+ '\n[url]${urlController.text}[/url]';
+ }
+ })
+ ],
+ ),
+ );
+ }
+
+ void addTagAtSelection(
+ TextEditingController controller, int start, int end, String tag) {
+ RegExp regExp = new RegExp(
+ r'(\[([^/].*?)(=(.+?))?\](.*?)\[/\2\]|\[([^/].*?)(=(.+?))?\])',
+ caseSensitive: false,
+ multiLine: false,
+ );
+
+ String newline = tag == 'h1' || tag == 'h2' ? "\n" : '';
+ String selectedText = controller.text.substring(start, end);
+ String replaceWith = '';
+
+ if (regExp.hasMatch(selectedText)) {
+ replaceWith = selectedText.replaceAll(
+ '[${tag}]', ''); //ignore: unnecessary_brace_in_string_interps
+ replaceWith = replaceWith.replaceAll(
+ '[/${tag}]', ''); //ignore: unnecessary_brace_in_string_interps
+ } else {
+ replaceWith = newline +
+ '[${tag}]' +
+ selectedText +
+ '[/${tag}]'; //ignore: unnecessary_brace_in_string_interps
+ }
+ controller.text = controller.text.replaceRange(start, end, replaceWith);
+ }
+
+ List buildItemList() {
+ return currentItemList
+ .map((SlateNode item) => ListTile(
+ onTap: () => this.showTextEditDialog(context, item),
+ title: item.nodes.first.leaves.length > 0
+ ? listItemTextHandler(item)
+ : Text('- empty list item -')))
+ .toList();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ LimitedBox(
+ maxHeight: 400,
+ child: SingleChildScrollView(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ mainAxisSize: MainAxisSize.min,
+ children: buildItemList()),
+ ),
+ ),
+ FlatButton(
+ child: Text('Add list item'),
+ onPressed: () {
+ setState(() {
+ currentItemList.add(
+ SlateNode(
+ type: 'list-item',
+ nodes: [SlateNode(object: 'block', leaves: [])],
+ ),
+ );
+ });
+ },
+ ),
+ ],
+ );
+ }
+}
diff --git a/lib/widget/PostEditor.dart b/lib/widget/PostEditor.dart
new file mode 100644
index 0000000..b19f2a0
--- /dev/null
+++ b/lib/widget/PostEditor.dart
@@ -0,0 +1,435 @@
+import 'package:cached_network_image/cached_network_image.dart';
+import 'package:flutter/material.dart';
+import 'package:knocky/models/slateDocument.dart';
+import 'package:knocky/models/thread.dart';
+import 'package:knocky/widget/SlateDocumentParser/SlateDocumentParser.dart';
+import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
+
+class PostEditor extends StatefulWidget {
+ final SlateObject document;
+ final Function onTapTextBlock;
+ final Function onTapImageBlock;
+ final Function onTapVideoBlock;
+ final Function onTapYouTubeBlock;
+ final Function onTapQuoteBlock;
+ final Function onTapListBlock;
+ final Function onTapUserQuoteBlock;
+ final Function onTapTwitterBlock;
+ final Function onTapStrawpollBlock;
+
+ // Toolbar callbacks
+ final Function onTapAddTextBlock;
+ final Function onTapAddHeadingOne;
+ final Function onTapAddHeadingTwo;
+ final Function onTapAddImage;
+ final Function onTapAddQuote;
+ final Function onTapAddYouTubeVideo;
+ final Function onTapAddVideo;
+ final Function onReorderHandler;
+ final Function onTapAddBulletedList;
+ final Function onTapAddNumberedList;
+ final Function onTapAddUserQuote;
+ final Function onTapAddTwitterEmbed;
+ final Function onTapAddStrawPollEmbed;
+
+ final List replyList;
+
+ PostEditor(
+ {this.document,
+ this.onTapTextBlock,
+ this.onTapImageBlock,
+ this.onTapListBlock,
+ this.onTapQuoteBlock,
+ this.onTapTwitterBlock,
+ this.onTapUserQuoteBlock,
+ this.onTapVideoBlock,
+ this.onTapYouTubeBlock,
+ this.onTapStrawpollBlock,
+ this.onTapAddHeadingOne,
+ this.onTapAddHeadingTwo,
+ this.onTapAddImage,
+ this.onTapAddQuote,
+ this.onTapAddTextBlock,
+ this.onTapAddVideo,
+ this.onTapAddYouTubeVideo,
+ this.onReorderHandler,
+ this.onTapAddBulletedList,
+ this.onTapAddNumberedList,
+ this.onTapAddUserQuote,
+ this.onTapAddTwitterEmbed,
+ this.onTapAddStrawPollEmbed,
+ this.replyList});
+
+ @override
+ _PostEditorState createState() => _PostEditorState();
+}
+
+class _PostEditorState extends State {
+ @override
+ void initState() {
+ super.initState();
+ print(this.widget.replyList);
+ }
+
+ Widget _quoteHandler(
+ SlateNode node, Function inlineHandler, Function leafHandler) {
+ List lines = List();
+ // Handle block nodes
+ node.nodes.forEach((line) {
+ if (line.leaves != null) {
+ double headingSize = 14.0;
+
+ if (node.type.contains('-one')) {
+ headingSize = 30.0;
+ }
+
+ if (node.type.contains('-two')) {
+ headingSize = 20.0;
+ }
+
+ // Handle node leaves
+ lines.addAll(leafHandler(line.leaves, fontSize: headingSize));
+ }
+
+ // Handle inline element
+ if (line.object == 'inline') {
+ // Handle links
+ inlineHandler(node, line);
+ }
+ });
+
+ return ListTile(
+ onTap: () => this.widget.onTapQuoteBlock(context, node),
+ leading: Icon(Icons.format_quote),
+ title: RichText(
+ text: TextSpan(
+ children:
+ lines.length > 0 ? lines : [TextSpan(text: '- empty quote')]),
+ ),
+ );
+ }
+
+ Widget _youtubeHandler(String youTubeUrl, SlateNode node) {
+ return ListTile(
+ onTap: () => this.widget.onTapYouTubeBlock(youTubeUrl, node),
+ leading: Icon(Icons.ondemand_video),
+ title: Text(youTubeUrl),
+ );
+ }
+
+ Widget _videoHandler(SlateNode node) {
+ return ListTile(
+ onTap: () => this.widget.onTapVideoBlock(node),
+ leading: Icon(Icons.videocam),
+ title: Text(node.data.src),
+ );
+ }
+
+ Widget _bulletedListHandler(List listItemsContent, SlateNode node) {
+ List listItems = List();
+ // Handle block nodes
+ listItemsContent.forEach((item) {
+ listItems.add(
+ Container(
+ margin: EdgeInsets.only(bottom: 5.0),
+ child: Row(
+ children: [
+ Container(
+ margin: EdgeInsets.only(right: 10.0),
+ height: 5.0,
+ width: 5.0,
+ decoration: new BoxDecoration(
+ color: Theme.of(context).textTheme.body1.color,
+ shape: BoxShape.circle,
+ ),
+ ),
+ Expanded(child: item)
+ ],
+ ),
+ ),
+ );
+ });
+
+ return ListTile(
+ onTap: () => this.widget.onTapListBlock(node),
+ leading: Icon(Icons.format_list_bulleted),
+ title: Container(
+ margin: EdgeInsets.only(top: 10, bottom: 10),
+ child: listItems.length > 0
+ ? Text('${listItems.length} items')
+ : Text('- empty list -'),
+ ),
+ );
+ }
+
+ Widget _numberedListHandler(List listItemsContent, SlateNode node) {
+ List listItems = List();
+ // Handle block nodes
+ listItemsContent.forEach((item) {
+ listItems.add(
+ Container(
+ margin: EdgeInsets.only(bottom: 5.0),
+ child: Row(
+ children: [
+ Container(
+ margin: EdgeInsets.only(right: 10.0),
+ child: Text(
+ (listItems.length + 1).toString(),
+ ),
+ ),
+ Expanded(
+ child: item,
+ )
+ ],
+ ),
+ ),
+ );
+ });
+
+ return ListTile(
+ onTap: () => this.widget.onTapListBlock(node),
+ leading: Icon(Icons.format_list_numbered),
+ title: Container(
+ margin: EdgeInsets.only(top: 10, bottom: 10),
+ child: listItems.length > 0
+ ? Text('${listItems.length} items')
+ : Text('- empty list -'),
+ ),
+ );
+ }
+
+ Widget _imageWidgetHandler(String imageUrl, slateObject, SlateNode node) {
+ return ListTile(
+ leading: Icon(Icons.image),
+ onTap: () {
+ this.widget.onTapImageBlock(slateObject, node);
+ },
+ title: Container(
+ margin: EdgeInsets.only(top: 10.0, bottom: 10.0),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [Text('Image block'), Text(imageUrl)],
+ ),
+ ),
+ );
+ }
+
+ Widget headingHandler(
+ SlateNode node, Function inlineHandler, Function leafHandler) {
+ List lines = List();
+
+ // Handle block nodes
+ node.nodes.forEach((line) {
+ if (line.leaves != null) {
+ double headingSize = 14.0;
+
+ if (node.type.contains('-one')) {
+ headingSize = 30.0;
+ }
+
+ if (node.type.contains('-two')) {
+ headingSize = 20.0;
+ }
+
+ // Handle node leaves
+ lines.addAll(leafHandler(line.leaves, fontSize: headingSize));
+ }
+
+ // Handle inline element
+ if (line.object == 'inline') {
+ // Handle links
+ inlineHandler(node, line);
+ }
+ });
+
+ return ListTile(
+ leading: Icon(node.type.contains('-one')
+ ? MdiIcons.formatHeader1
+ : MdiIcons.formatHeader2),
+ onTap: () {
+ this.widget.onTapTextBlock(context, node);
+ },
+ title: Container(
+ margin: EdgeInsets.only(bottom: 10),
+ child: RichText(
+ text: lines.length > 0
+ ? TextSpan(children: lines)
+ : TextSpan(text: '- empty heading block -'),
+ ),
+ ),
+ );
+ }
+
+ Widget paragraphHandler(SlateNode node, Function leafHandler) {
+ List lines = List();
+
+ // Handle block nodes
+ node.nodes.forEach((line) {
+ if (line.leaves != null) {
+ lines.addAll(leafHandler(line.leaves));
+ }
+
+ // Handle inline element
+ if (line.object == 'inline') {
+ // Handle links
+ if (line.type == 'link') {
+ line.nodes.forEach((inlineNode) {
+ inlineNode.leaves.forEach((leaf) {
+ lines.add(TextSpan(
+ text: leaf.text,
+ style: TextStyle(color: Colors.blue),
+ ));
+ });
+ });
+ } else {
+ line.nodes.forEach((inlineNode) {
+ inlineNode.leaves.forEach((leaf) {
+ lines.add(TextSpan(text: leaf.text));
+ });
+ });
+ }
+ }
+ });
+
+ return ListTile(
+ leading: Icon(Icons.text_format),
+ onTap: () {
+ this.widget.onTapTextBlock(context, node);
+ },
+ title: Container(
+ margin: EdgeInsets.only(bottom: 5),
+ child: RichText(
+ text: lines.length > 0
+ ? TextSpan(children: lines)
+ : TextSpan(text: '- empty text block -')),
+ ),
+ );
+ }
+
+ Widget _twitterHandler(String twitterUrl, SlateNode node) {
+ return ListTile(
+ onTap: () => this.widget.onTapTwitterBlock(twitterUrl, node),
+ leading: Icon(MdiIcons.twitter),
+ title: Text(twitterUrl),
+ );
+ }
+
+ Widget _strawpollHandler(SlateNode node) {
+ return ListTile(
+ onTap: () => this.widget.onTapStrawpollBlock(node.data.src, node),
+ leading: Icon(MdiIcons.poll),
+ title: Text(node.data.src),
+ );
+ }
+
+ Widget _userquoteHandler(
+ String username, List widgets, bool isChild, SlateNode node) {
+ return ListTile(
+ onTap: () => this.widget.onTapUserQuoteBlock(node),
+ leading: Icon(Icons.message),
+ title: Text(username),
+ );
+ }
+
+ List editorContent() {
+ return SlateDocumentParser(
+ slateObject: this.widget.document,
+ context: context,
+ paragraphHandler: this.paragraphHandler,
+ headingHandler: this.headingHandler,
+ imageWidgetHandler: this._imageWidgetHandler,
+ quotesHandler: _quoteHandler,
+ youTubeWidgetHandler: this._youtubeHandler,
+ videoWidgetHandler: this._videoHandler,
+ bulletedListHandler: this._bulletedListHandler,
+ numberedListHandler: this._numberedListHandler,
+ twitterEmbedHandler: this._twitterHandler,
+ strawpollHandler: this._strawpollHandler,
+ userQuoteHandler: this._userquoteHandler,
+ )
+ .asWidgetList()
+ .map((widget) => Container(
+ key: ValueKey(widget),
+ child: widget,
+ ))
+ .toList();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ Expanded(
+ child: ReorderableListView(
+ onReorder: this.widget.onReorderHandler,
+ children: editorContent(),
+ ),
+ ),
+ Container(
+ color: Colors.grey[600],
+ padding: EdgeInsets.all(0),
+ child: Wrap(
+ children: [
+ IconButton(
+ icon: Icon(MdiIcons.textbox),
+ onPressed: this.widget.onTapAddTextBlock,
+ ),
+ IconButton(
+ icon: Icon(MdiIcons.formatHeader1),
+ onPressed: this.widget.onTapAddHeadingOne,
+ ),
+ IconButton(
+ icon: Icon(MdiIcons.formatHeader2),
+ onPressed: this.widget.onTapAddHeadingTwo,
+ ),
+ IconButton(
+ icon: Icon(Icons.format_quote),
+ onPressed: this.widget.onTapAddQuote,
+ ),
+ IconButton(
+ icon: Icon(Icons.format_list_bulleted),
+ onPressed: this.widget.onTapAddBulletedList,
+ ),
+ IconButton(
+ icon: Icon(Icons.format_list_numbered),
+ onPressed: this.widget.onTapAddNumberedList,
+ ),
+ IconButton(
+ icon: Icon(Icons.image),
+ onPressed: this.widget.onTapAddImage,
+ ),
+ IconButton(
+ icon: Icon(Icons.ondemand_video),
+ onPressed: this.widget.onTapAddYouTubeVideo,
+ ),
+ IconButton(
+ icon: Icon(Icons.videocam),
+ onPressed: this.widget.onTapAddVideo,
+ ),
+ IconButton(
+ icon: Icon(MdiIcons.twitter),
+ onPressed: this.widget.onTapAddTwitterEmbed,
+ ),
+ IconButton(
+ icon: Icon(MdiIcons.poll),
+ onPressed: this.widget.onTapAddStrawPollEmbed,
+ ),
+ if (this.widget.replyList.length > 0)
+ Builder(
+ builder: (BuildContext bcontext) {
+ return IconButton(
+ tooltip: 'Insert userquote',
+ icon: Icon(Icons.message),
+ onPressed: this.widget.onTapAddUserQuote,
+ );
+ },
+ ),
+ ],
+ ),
+ ),
+ ],
+ );
+ }
+}
diff --git a/lib/widget/SlateDocumentParser/SlateDocumentController.dart b/lib/widget/SlateDocumentParser/SlateDocumentController.dart
new file mode 100644
index 0000000..6f1e260
--- /dev/null
+++ b/lib/widget/SlateDocumentParser/SlateDocumentController.dart
@@ -0,0 +1,7 @@
+import 'package:flutter/material.dart';
+
+class SlateDocumentController {
+ List widgetList = List();
+
+ SlateDocumentController({this.widgetList});
+}
diff --git a/lib/widget/SlateDocumentParser/SlateDocumentParser.dart b/lib/widget/SlateDocumentParser/SlateDocumentParser.dart
index d9f7035..01c1dd7 100644
--- a/lib/widget/SlateDocumentParser/SlateDocumentParser.dart
+++ b/lib/widget/SlateDocumentParser/SlateDocumentParser.dart
@@ -1,66 +1,49 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:knocky/models/slateDocument.dart';
-import 'package:knocky/widget/Thread/PostElements/Video.dart';
-import 'package:knocky/widget/Thread/PostElements/YouTubeEmbed.dart';
-import 'package:knocky/widget/Thread/PostElements/Image.dart';
-import 'package:intent/intent.dart' as Intent;
-import 'package:intent/action.dart' as Action;
-import 'package:knocky/widget/Thread/PostElements/Embed.dart';
-import 'package:knocky/widget/Thread/PostElements/UserQuote.dart';
+import 'package:knocky/widget/SlateDocumentParser/SlateDocumentController.dart';
class SlateDocumentParser extends StatelessWidget {
final SlateObject slateObject;
final Function onPressSpoiler;
final GlobalKey scaffoldkey;
final BuildContext context;
-
- SlateDocumentParser(
- {this.slateObject, this.onPressSpoiler, this.scaffoldkey, this.context});
+ final Function imageWidgetHandler;
+ final Function videoWidgetHandler;
+ final Function youTubeWidgetHandler;
+ final Function twitterEmbedHandler;
+ final Function userQuoteHandler;
+ final Function bulletedListHandler;
+ final Function numberedListHandler;
+ final Function quotesHandler;
+ final Function paragraphHandler;
+ final Function headingHandler;
+ final Function strawpollHandler;
+ final SlateDocumentController slateDocumentController;
+ final bool asListView;
+
+ SlateDocumentParser({
+ this.slateObject,
+ this.onPressSpoiler,
+ this.scaffoldkey,
+ this.context,
+ this.slateDocumentController,
+ @required this.imageWidgetHandler,
+ @required this.videoWidgetHandler,
+ @required this.youTubeWidgetHandler,
+ @required this.twitterEmbedHandler,
+ @required this.userQuoteHandler,
+ @required this.bulletedListHandler,
+ @required this.numberedListHandler,
+ @required this.quotesHandler,
+ @required this.paragraphHandler,
+ @required this.headingHandler,
+ @required this.strawpollHandler,
+ this.asListView,
+ });
Widget paragraphToWidget(SlateNode node) {
- List lines = List();
-
- // Handle block nodes
- node.nodes.forEach((line) {
- if (line.leaves != null) {
- lines.addAll(leafHandler(line.leaves));
- }
-
- // Handle inline element
- if (line.object == 'inline') {
- // Handle links
- if (line.type == 'link') {
- line.nodes.forEach((inlineNode) {
- inlineNode.leaves.forEach((leaf) {
- lines.add(TextSpan(
- text: leaf.text,
- style: TextStyle(color: Colors.blue),
- recognizer: TapGestureRecognizer()
- ..onTap = () {
- Intent.Intent()
- ..setAction(Action.Action.ACTION_VIEW)
- ..setData(Uri.parse(line.data.href))
- ..startActivity().catchError((e) => print(e));
- print('Clicked link: ' + line.data.href);
- }));
- });
- });
- } else {
- line.nodes.forEach((inlineNode) {
- inlineNode.leaves.forEach((leaf) {
- lines.add(TextSpan(text: leaf.text));
- });
- });
- }
- }
- });
-
- return Container(
- child: RichText(
- text: TextSpan(children: lines),
- ),
- );
+ return this.paragraphHandler(node, leafHandler);
}
List leafHandler(List leaves, {double fontSize = 14.0}) {
@@ -148,106 +131,20 @@ class SlateDocumentParser extends StatelessWidget {
Widget bulletListToWidget(SlateNode node) {
List listItemsContent = List();
- List listItems = List();
-
listItemsContent.addAll(handleNodes(node.nodes));
- // Handle block nodes
- listItemsContent.forEach((item) {
- listItems.add(
- Container(
- margin: EdgeInsets.only(bottom: 5.0),
- child: Row(
- children: [
- Container(
- margin: EdgeInsets.only(right: 10.0),
- height: 5.0,
- width: 5.0,
- decoration: new BoxDecoration(
- color: Theme.of(context).textTheme.body1.color,
- shape: BoxShape.circle,
- ),
- ),
- Expanded(child: item)
- ],
- ),
- ),
- );
- });
-
- return Container(
- margin: EdgeInsets.only(top: 10, bottom: 10),
- child: Column(children: listItems),
- );
+ return this.bulletedListHandler(listItemsContent, node);
}
Widget numberedListToWidget(SlateNode node) {
List listItemsContent = List();
- List listItems = List();
-
listItemsContent.addAll(handleNodes(node.nodes));
- // Handle block nodes
- listItemsContent.forEach((item) {
- listItems.add(
- Container(
- margin: EdgeInsets.only(bottom: 5.0),
- child: Row(
- children: [
- Container(
- margin: EdgeInsets.only(right: 10.0),
- child: Text(
- (listItems.length + 1).toString(),
- ),
- ),
- Expanded(
- child: item,
- )
- ],
- ),
- ),
- );
- });
-
- return Container(
- margin: EdgeInsets.only(bottom: 10),
- child: Column(children: listItems),
- );
+ return this.numberedListHandler(listItemsContent, node);
}
Widget headingToWidget(SlateNode node) {
- List lines = List();
-
- // Handle block nodes
- node.nodes.forEach((line) {
- if (line.leaves != null) {
- double headingSize = 14.0;
-
- if (node.type.contains('-one')) {
- headingSize = 30.0;
- }
-
- if (node.type.contains('-two')) {
- headingSize = 20.0;
- }
-
- // Handle node leaves
- lines.addAll(leafHandler(line.leaves, fontSize: headingSize));
- }
-
- // Handle inline element
- if (line.object == 'inline') {
- // Handle links
- inlineHandler(node, line);
- }
- });
-
- return Container(
- margin: EdgeInsets.only(bottom: 10),
- child: RichText(
- text: TextSpan(children: lines),
- ),
- );
+ return this.headingHandler(node, inlineHandler, leafHandler);
}
Widget userquoteToWidget(SlateNode node, {bool isChild = false}) {
@@ -256,44 +153,23 @@ class SlateDocumentParser extends StatelessWidget {
// Handle block nodes
widgets.addAll(handleNodes(node.nodes, isChild: !isChild));
- return UserQuoteWidget(
- username: node.data.postData.username,
- children: widgets,
- isChild: isChild,
- );
+ return this.userQuoteHandler(node.data.postData.username, widgets, isChild, node);
}
Widget youTubeToWidget(SlateNode node) {
- return YoutubeVideoEmbed(url: node.data.src);
+ return this.youTubeWidgetHandler(node.data.src, node);
}
Widget handleQuotes(SlateNode node) {
- return Container(
- margin: EdgeInsets.only(bottom: 10.0),
- padding: EdgeInsets.all(10.0),
- decoration: BoxDecoration(
- border: Border(
- left: BorderSide(color: Colors.blue, width: 3.0),
- ),
- color: Colors.grey,
- ),
- child: paragraphToWidget(node),
- );
+ return this.quotesHandler(node, inlineHandler, leafHandler);
}
Widget handleImage(SlateNode node) {
-
- return Container(
- margin: EdgeInsets.only(top: 10.0, bottom: 10.0),
- child: LimitedBox(
- maxHeight: 300,
- child: ImageWidget(url: node.data.src, slateObject: slateObject),
- ),
- );
+ return this.imageWidgetHandler(node.data.src, slateObject, node);
}
Widget handleVideo(SlateNode node) {
- return VideoElement(url: node.data.src, scaffoldKey: scaffoldkey);
+ return this.videoWidgetHandler(node);
}
List handleNodes(List nodes, {bool isChild = false}) {
@@ -333,11 +209,14 @@ class SlateDocumentParser extends StatelessWidget {
widgets.add(handleQuotes(node));
break;
case 'twitter':
- widgets.add(EmbedWidget(url: node.data.src));
+ widgets.add(this.twitterEmbedHandler(node.data.src, node));
break;
case 'video':
widgets.add(handleVideo(node));
break;
+ case 'strawpoll':
+ widgets.add(this.strawpollHandler(node));
+ break;
default:
if (node.object == 'text') {
widgets.add(
@@ -355,12 +234,18 @@ class SlateDocumentParser extends StatelessWidget {
return widgets;
}
+ List asWidgetList() {
+ return handleNodes(slateObject.document.nodes);
+ }
+
@override
Widget build(BuildContext context) {
return Container(
- child: Column(
- mainAxisAlignment: MainAxisAlignment.start,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: handleNodes(slateObject.document.nodes)));
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.start,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: handleNodes(slateObject.document.nodes),
+ ),
+ );
}
}
diff --git a/lib/widget/Thread/PostContent.dart b/lib/widget/Thread/PostContent.dart
new file mode 100644
index 0000000..4ded1da
--- /dev/null
+++ b/lib/widget/Thread/PostContent.dart
@@ -0,0 +1,250 @@
+import 'package:flutter/gestures.dart';
+import 'package:flutter/material.dart';
+import 'package:knocky/models/slateDocument.dart';
+import 'package:knocky/widget/SlateDocumentParser/SlateDocumentParser.dart';
+import 'package:knocky/widget/Thread/PostElements/Embed.dart';
+import 'package:knocky/widget/Thread/PostElements/Image.dart';
+import 'package:knocky/widget/Thread/PostElements/UserQuote.dart';
+import 'package:knocky/widget/Thread/PostElements/Video.dart';
+import 'package:knocky/widget/Thread/PostElements/YouTubeEmbed.dart';
+import 'package:intent/intent.dart' as Intent;
+import 'package:intent/action.dart' as Action;
+
+class PostContent extends StatelessWidget {
+ final SlateObject content;
+ final Function onTapSpoiler;
+ final GlobalKey scaffoldKey;
+
+ PostContent({
+ this.onTapSpoiler,
+ this.content,
+ this.scaffoldKey,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return SlateDocumentParser(
+ slateObject: this.content,
+ onPressSpoiler: (text) {
+ this.onTapSpoiler(text);
+ },
+ context: context,
+ paragraphHandler: (SlateNode node, Function leafHandler) {
+ List lines = List();
+
+ // Handle block nodes
+ node.nodes.forEach((line) {
+ if (line.leaves != null) {
+ lines.addAll(leafHandler(line.leaves));
+ }
+
+ // Handle inline element
+ if (line.object == 'inline') {
+ // Handle links
+ if (line.type == 'link') {
+ line.nodes.forEach((inlineNode) {
+ inlineNode.leaves.forEach((leaf) {
+ lines.add(TextSpan(
+ text: leaf.text,
+ style: TextStyle(color: Colors.blue),
+ recognizer: TapGestureRecognizer()
+ ..onTap = () {
+ Intent.Intent()
+ ..setAction(Action.Action.ACTION_VIEW)
+ ..setData(Uri.parse(line.data.href))
+ ..startActivity().catchError((e) => print(e));
+ print('Clicked link: ' + line.data.href);
+ }));
+ });
+ });
+ } else {
+ line.nodes.forEach((inlineNode) {
+ inlineNode.leaves.forEach((leaf) {
+ lines.add(TextSpan(text: leaf.text));
+ });
+ });
+ }
+ }
+ });
+
+ return Container(
+ child: RichText(
+ text: TextSpan(children: lines),
+ ),
+ );
+ },
+ headingHandler:
+ (SlateNode node, Function inlineHandler, Function leafHandler) {
+ List lines = List();
+
+ // Handle block nodes
+ node.nodes.forEach((line) {
+ if (line.leaves != null) {
+ double headingSize = 14.0;
+
+ if (node.type.contains('-one')) {
+ headingSize = 30.0;
+ }
+
+ if (node.type.contains('-two')) {
+ headingSize = 20.0;
+ }
+
+ // Handle node leaves
+ lines.addAll(leafHandler(line.leaves, fontSize: headingSize));
+ }
+
+ // Handle inline element
+ if (line.object == 'inline') {
+ // Handle links
+ inlineHandler(node, line);
+ }
+ });
+
+ return Container(
+ margin: EdgeInsets.only(bottom: 10),
+ child: RichText(
+ text: TextSpan(children: lines),
+ ),
+ );
+ },
+ imageWidgetHandler: (String imageUrl, slateObject, SlateNode node) {
+ return Container(
+ margin: EdgeInsets.only(top: 10.0, bottom: 10.0),
+ child: LimitedBox(
+ maxHeight: 300,
+ child: ImageWidget(url: imageUrl, slateObject: slateObject),
+ ),
+ );
+ },
+ videoWidgetHandler: (SlateNode node) {
+ return VideoElement(
+ url: node.data.src,
+ scaffoldKey: this.scaffoldKey,
+ );
+ },
+ youTubeWidgetHandler: (String youTubeUrl, SlateNode node) {
+ return YoutubeVideoEmbed(
+ url: youTubeUrl,
+ );
+ },
+ twitterEmbedHandler: (String embedUrl, SlateNode node) {
+ return EmbedWidget(
+ url: embedUrl,
+ );
+ },
+ strawpollHandler: (SlateNode node) {
+ return EmbedWidget(
+ url: node.data.src,
+ );
+ },
+ userQuoteHandler: (String username, List widgets, bool isChild, SlateNode node) {
+ return UserQuoteWidget(
+ username: username,
+ children: widgets,
+ isChild: isChild,
+ );
+ },
+ bulletedListHandler: (List listItemsContent, node) {
+ List listItems = List();
+ // Handle block nodes
+ listItemsContent.forEach((item) {
+ listItems.add(
+ Container(
+ margin: EdgeInsets.only(bottom: 5.0),
+ child: Row(
+ children: [
+ Container(
+ margin: EdgeInsets.only(right: 10.0),
+ height: 5.0,
+ width: 5.0,
+ decoration: new BoxDecoration(
+ color: Theme.of(context).textTheme.body1.color,
+ shape: BoxShape.circle,
+ ),
+ ),
+ Expanded(child: item)
+ ],
+ ),
+ ),
+ );
+ });
+
+ return Container(
+ margin: EdgeInsets.only(top: 10, bottom: 10),
+ child: Column(children: listItems),
+ );
+ },
+ numberedListHandler: (List listItemsContent, SlateNode node) {
+ List listItems = List();
+ // Handle block nodes
+ listItemsContent.forEach((item) {
+ listItems.add(
+ Container(
+ margin: EdgeInsets.only(bottom: 5.0),
+ child: Row(
+ children: [
+ Container(
+ margin: EdgeInsets.only(right: 10.0),
+ child: Text(
+ (listItems.length + 1).toString(),
+ ),
+ ),
+ Expanded(
+ child: item,
+ )
+ ],
+ ),
+ ),
+ );
+ });
+
+ return Container(
+ margin: EdgeInsets.only(bottom: 10),
+ child: Column(children: listItems),
+ );
+ },
+ quotesHandler:
+ (SlateNode node, Function inlineHandler, Function leafHandler) {
+ List lines = List();
+ // Handle block nodes
+ node.nodes.forEach((line) {
+ if (line.leaves != null) {
+ double headingSize = 14.0;
+
+ if (node.type.contains('-one')) {
+ headingSize = 30.0;
+ }
+
+ if (node.type.contains('-two')) {
+ headingSize = 20.0;
+ }
+
+ // Handle node leaves
+ lines.addAll(leafHandler(line.leaves, fontSize: headingSize));
+ }
+
+ // Handle inline element
+ if (line.object == 'inline') {
+ // Handle links
+ inlineHandler(node, line);
+ }
+ });
+
+ return Container(
+ margin: EdgeInsets.only(bottom: 10.0),
+ padding: EdgeInsets.all(10.0),
+ decoration: BoxDecoration(
+ border: Border(
+ left: BorderSide(color: Colors.blue, width: 3.0),
+ ),
+ color: Colors.grey,
+ ),
+ child: RichText(
+ text: TextSpan(children: lines),
+ ),
+ );
+ },
+ );
+ }
+}
diff --git a/lib/widget/Thread/ThreadPostItem.dart b/lib/widget/Thread/ThreadPostItem.dart
index f37598e..cd67223 100644
--- a/lib/widget/Thread/ThreadPostItem.dart
+++ b/lib/widget/Thread/ThreadPostItem.dart
@@ -1,6 +1,6 @@
+
import 'package:flutter/material.dart';
import 'package:knocky/models/thread.dart';
-import 'package:knocky/widget/SlateDocumentParser/SlateDocumentParser.dart';
import 'package:knocky/helpers/icons.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:knocky/widget/Thread/PostHeader.dart';
@@ -9,6 +9,7 @@ import 'package:knocky/state/authentication.dart';
import 'package:knocky/widget/Thread/RatePostContent.dart';
import 'package:knocky/widget/Thread/ViewUsersOfRatingsContent.dart';
import 'package:knocky/widget/Thread/PostBan.dart';
+import 'package:knocky/widget/Thread/PostContent.dart';
class ThreadPostItem extends StatelessWidget {
final ThreadPost postDetails;
@@ -119,7 +120,6 @@ class ThreadPostItem extends StatelessWidget {
}
List ownPostButtons(BuildContext context) {
- return [];
return [
FlatButton(
child: Text('Edit'),
@@ -184,13 +184,12 @@ class ThreadPostItem extends StatelessWidget {
),
Container(
padding: EdgeInsets.only(left: 10, right: 10),
- child: SlateDocumentParser(
- slateObject: postDetails.content,
- onPressSpoiler: (text) {
- onPressSpoiler(context, text);
- },
- context: context,
- ),
+ child: PostContent(
+ content: postDetails.content,
+ onTapSpoiler: (text) {
+ onPressSpoiler(context, text);
+ },
+ scaffoldKey: this.scaffoldKey),
),
Container(
padding:
diff --git a/pubspec.yaml b/pubspec.yaml
index 9241b18..a0cc8e8 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -11,7 +11,7 @@ description: A knockout! client.
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
-version: 1.2.6+20
+version: 1.3.0+21
environment:
sdk: ">=2.2.2 <3.0.0"
@@ -43,10 +43,13 @@ dependencies:
sticky_headers: "^0.1.8"
bbob_dart: ^0.1.1
event_bus: ^1.1.0
+ uni_links: ^0.2.0
hive:
git:
url: https://github.com/leisim/hive.git
path: hive
+ quick_actions: ^0.3.2+2
+ material_design_icons_flutter: 3.2.3895
flutter:
sdk: flutter