Committed by
GitHub
Merge pull request #393 from WilliamCunhaCardoso/issue-389
Update extension structure
Showing
17 changed files
with
440 additions
and
5 deletions
@@ -15,6 +15,13 @@ packages: | @@ -15,6 +15,13 @@ packages: | ||
15 | url: "https://pub.dartlang.org" | 15 | url: "https://pub.dartlang.org" |
16 | source: hosted | 16 | source: hosted |
17 | version: "0.39.10" | 17 | version: "0.39.10" |
18 | + archive: | ||
19 | + dependency: transitive | ||
20 | + description: | ||
21 | + name: archive | ||
22 | + url: "https://pub.dartlang.org" | ||
23 | + source: hosted | ||
24 | + version: "2.0.13" | ||
18 | args: | 25 | args: |
19 | dependency: transitive | 26 | dependency: transitive |
20 | description: | 27 | description: |
@@ -154,7 +161,7 @@ packages: | @@ -154,7 +161,7 @@ packages: | ||
154 | name: crypto | 161 | name: crypto |
155 | url: "https://pub.dartlang.org" | 162 | url: "https://pub.dartlang.org" |
156 | source: hosted | 163 | source: hosted |
157 | - version: "2.1.5" | 164 | + version: "2.1.4" |
158 | csslib: | 165 | csslib: |
159 | dependency: transitive | 166 | dependency: transitive |
160 | description: | 167 | description: |
@@ -270,6 +277,13 @@ packages: | @@ -270,6 +277,13 @@ packages: | ||
270 | url: "https://pub.dartlang.org" | 277 | url: "https://pub.dartlang.org" |
271 | source: hosted | 278 | source: hosted |
272 | version: "3.1.4" | 279 | version: "3.1.4" |
280 | + image: | ||
281 | + dependency: transitive | ||
282 | + description: | ||
283 | + name: image | ||
284 | + url: "https://pub.dartlang.org" | ||
285 | + source: hosted | ||
286 | + version: "2.1.12" | ||
273 | io: | 287 | io: |
274 | dependency: transitive | 288 | dependency: transitive |
275 | description: | 289 | description: |
@@ -375,6 +389,13 @@ packages: | @@ -375,6 +389,13 @@ packages: | ||
375 | url: "https://pub.dartlang.org" | 389 | url: "https://pub.dartlang.org" |
376 | source: hosted | 390 | source: hosted |
377 | version: "1.9.0" | 391 | version: "1.9.0" |
392 | + petitparser: | ||
393 | + dependency: transitive | ||
394 | + description: | ||
395 | + name: petitparser | ||
396 | + url: "https://pub.dartlang.org" | ||
397 | + source: hosted | ||
398 | + version: "2.4.0" | ||
378 | pool: | 399 | pool: |
379 | dependency: transitive | 400 | dependency: transitive |
380 | description: | 401 | description: |
@@ -541,6 +562,13 @@ packages: | @@ -541,6 +562,13 @@ packages: | ||
541 | url: "https://pub.dartlang.org" | 562 | url: "https://pub.dartlang.org" |
542 | source: hosted | 563 | source: hosted |
543 | version: "1.1.0" | 564 | version: "1.1.0" |
565 | + xml: | ||
566 | + dependency: transitive | ||
567 | + description: | ||
568 | + name: xml | ||
569 | + url: "https://pub.dartlang.org" | ||
570 | + source: hosted | ||
571 | + version: "3.6.1" | ||
544 | yaml: | 572 | yaml: |
545 | dependency: transitive | 573 | dependency: transitive |
546 | description: | 574 | description: |
@@ -73,7 +73,7 @@ packages: | @@ -73,7 +73,7 @@ packages: | ||
73 | path: ".." | 73 | path: ".." |
74 | relative: true | 74 | relative: true |
75 | source: path | 75 | source: path |
76 | - version: "3.4.6" | 76 | + version: "3.5.1" |
77 | http_parser: | 77 | http_parser: |
78 | dependency: transitive | 78 | dependency: transitive |
79 | description: | 79 | description: |
1 | +import 'package:flutter/material.dart'; | ||
2 | +import 'package:flutter/widgets.dart'; | ||
3 | +import 'package:flutter/foundation.dart'; | ||
4 | + | ||
5 | +extension ContextExtensionss on BuildContext { | ||
6 | + /// The same of [MediaQuery.of(context).size] | ||
7 | + Size get mediaQuerySize => MediaQuery.of(this).size; | ||
8 | + | ||
9 | + /// The same of [MediaQuery.of(context).size.height] | ||
10 | + /// Note: updates when you rezise your screen (like on a browser or desktop window) | ||
11 | + double get height => mediaQuerySize.height; | ||
12 | + | ||
13 | + /// The same of [MediaQuery.of(context).size.width] | ||
14 | + /// Note: updates when you rezise your screen (like on a browser or desktop window) | ||
15 | + double get width => mediaQuerySize.width; | ||
16 | + | ||
17 | + /// Gives you the power to get a portion of the height. | ||
18 | + /// Useful for responsive applications. | ||
19 | + /// | ||
20 | + /// [dividedBy] is for when you want to have a portion of the value you would get | ||
21 | + /// like for example: if you want a value that represents a third of the screen | ||
22 | + /// you can set it to 3, and you will get a third of the height | ||
23 | + /// | ||
24 | + /// [reducedBy] is a percentage value of how much of the height you want | ||
25 | + /// if you for example want 46% of the height, then you reduce it by 56%. | ||
26 | + double heightTransformer({double dividedBy = 1, double reducedBy = 0.0}) { | ||
27 | + return (mediaQuerySize.height - | ||
28 | + ((mediaQuerySize.height / 100) * reducedBy)) / | ||
29 | + dividedBy; | ||
30 | + } | ||
31 | + | ||
32 | + /// Gives you the power to get a portion of the width. | ||
33 | + /// Useful for responsive applications. | ||
34 | + /// | ||
35 | + /// [dividedBy] is for when you want to have a portion of the value you would get | ||
36 | + /// like for example: if you want a value that represents a third of the screen | ||
37 | + /// you can set it to 3, and you will get a third of the width | ||
38 | + /// | ||
39 | + /// [reducedBy] is a percentage value of how much of the width you want | ||
40 | + /// if you for example want 46% of the width, then you reduce it by 56%. | ||
41 | + double widthTransformer({double dividedBy = 1, double reducedBy = 0.0}) { | ||
42 | + return (mediaQuerySize.width - ((mediaQuerySize.width / 100) * reducedBy)) / | ||
43 | + dividedBy; | ||
44 | + } | ||
45 | + | ||
46 | + /// Divide the height proportionally by the given value | ||
47 | + double ratio({ | ||
48 | + double dividedBy = 1, | ||
49 | + double reducedByW = 0.0, | ||
50 | + double reducedByH = 0.0, | ||
51 | + }) { | ||
52 | + return heightTransformer(dividedBy: dividedBy, reducedBy: reducedByH) / | ||
53 | + widthTransformer(dividedBy: dividedBy, reducedBy: reducedByW); | ||
54 | + } | ||
55 | + | ||
56 | + /// similar to [MediaQuery.of(context).padding] | ||
57 | + ThemeData get theme => Theme.of(this); | ||
58 | + | ||
59 | + /// similar to [MediaQuery.of(context).padding] | ||
60 | + TextTheme get textTheme => Theme.of(this).textTheme; | ||
61 | + | ||
62 | + /// similar to [MediaQuery.of(context).padding] | ||
63 | + EdgeInsets get mediaQueryPadding => MediaQuery.of(this).padding; | ||
64 | + | ||
65 | + /// similar to [MediaQuery.of(context).padding] | ||
66 | + MediaQueryData get mediaQuery => MediaQuery.of(this); | ||
67 | + | ||
68 | + /// similar to [MediaQuery.of(context).viewPadding] | ||
69 | + EdgeInsets get mediaQueryViewPadding => MediaQuery.of(this).viewPadding; | ||
70 | + | ||
71 | + /// similar to [MediaQuery.of(context).viewInsets] | ||
72 | + EdgeInsets get mediaQueryViewInsets => MediaQuery.of(this).viewInsets; | ||
73 | + | ||
74 | + /// similar to [MediaQuery.of(context).orientation] | ||
75 | + Orientation get orientation => MediaQuery.of(this).orientation; | ||
76 | + | ||
77 | + /// check if device is on landscape mode | ||
78 | + bool get isLandscape => orientation == Orientation.landscape; | ||
79 | + | ||
80 | + /// check if device is on portrait mode | ||
81 | + bool get isPortrait => orientation == Orientation.portrait; | ||
82 | + | ||
83 | + /// similar to [MediaQuery.of(this).devicePixelRatio] | ||
84 | + double get devicePixelRatio => MediaQuery.of(this).devicePixelRatio; | ||
85 | + | ||
86 | + /// similar to [MediaQuery.of(this).textScaleFactor] | ||
87 | + double get textScaleFactor => MediaQuery.of(this).textScaleFactor; | ||
88 | + | ||
89 | + /// get the shortestSide from screen | ||
90 | + double get mediaQueryShortestSide => mediaQuerySize.shortestSide; | ||
91 | + | ||
92 | + /// True if width be larger than 800 | ||
93 | + bool get showNavbar => (width > 800); | ||
94 | + | ||
95 | + /// True if the shortestSide is smaller than 600p | ||
96 | + bool get isPhone => (mediaQueryShortestSide < 600); | ||
97 | + | ||
98 | + /// True if the shortestSide is largest than 600p | ||
99 | + bool get isSmallTablet => (mediaQueryShortestSide >= 600); | ||
100 | + | ||
101 | + /// True if the shortestSide is largest than 720p | ||
102 | + bool get isLargeTablet => (mediaQueryShortestSide >= 720); | ||
103 | + | ||
104 | + /// True if the current device is Tablet | ||
105 | + bool get isTablet => isSmallTablet || isLargeTablet; | ||
106 | + | ||
107 | + /// Returns a specific value according to the screen size | ||
108 | + /// if the device width is higher than or equal to 1200 return [desktop] value. | ||
109 | + /// if the device width is higher than or equal to 600 and less than 1200 | ||
110 | + /// return [tablet] value. | ||
111 | + /// if the device width is less than 300 return [watch] value. | ||
112 | + /// in other cases return [mobile] value. | ||
113 | + T responsiveValue<T>({ | ||
114 | + T mobile, | ||
115 | + T tablet, | ||
116 | + T desktop, | ||
117 | + T watch, | ||
118 | + }) { | ||
119 | + double deviceWidth = mediaQuerySize.shortestSide; | ||
120 | + | ||
121 | + if (kIsWeb) { | ||
122 | + deviceWidth = mediaQuerySize.width; | ||
123 | + } | ||
124 | + if (deviceWidth >= 1200 && desktop != null) return desktop; | ||
125 | + if (deviceWidth >= 600 && tablet != null) return tablet; | ||
126 | + if (deviceWidth < 300 && watch != null) return watch; | ||
127 | + return mobile; | ||
128 | + } | ||
129 | +} |
1 | +import '../regex/get_utils.dart'; | ||
2 | + | ||
3 | +extension GetDynamicUtils on dynamic { | ||
4 | + /// It's This is overloading the IDE's options. Only the most useful and popular options will stay here. | ||
5 | + | ||
6 | + bool get isNull => GetUtils.isNull(this); | ||
7 | + bool get isNullOrBlank => GetUtils.isNullOrBlank(this); | ||
8 | + | ||
9 | + // bool get isOneAKind => GetUtils.isOneAKind(this); | ||
10 | + // bool isLengthLowerThan(int maxLength) => | ||
11 | + // GetUtils.isLengthLowerThan(this, maxLength); | ||
12 | + // bool isLengthGreaterThan(int maxLength) => | ||
13 | + // GetUtils.isLengthGreaterThan(this, maxLength); | ||
14 | + // bool isLengthGreaterOrEqual(int maxLength) => | ||
15 | + // GetUtils.isLengthGreaterOrEqual(this, maxLength); | ||
16 | + // bool isLengthLowerOrEqual(int maxLength) => | ||
17 | + // GetUtils.isLengthLowerOrEqual(this, maxLength); | ||
18 | + // bool isLengthEqualTo(int maxLength) => | ||
19 | + // GetUtils.isLengthEqualTo(this, maxLength); | ||
20 | + // bool isLengthBetween(int minLength, int maxLength) => | ||
21 | + // GetUtils.isLengthBetween(this, minLength, maxLength); | ||
22 | +} |
lib/src/utils/extensions/export.dart
0 → 100644
lib/src/utils/extensions/num_extensions.dart
0 → 100644
1 | +import '../regex/get_utils.dart'; | ||
2 | + | ||
3 | +extension GetStringUtils on String { | ||
4 | + bool get isNum => GetUtils.isNum(this); | ||
5 | + bool get isNumericOnly => GetUtils.isNumericOnly(this); | ||
6 | + bool get isAlphabetOnly => GetUtils.isAlphabetOnly(this); | ||
7 | + bool get isBool => GetUtils.isBool(this); | ||
8 | + bool get isVectorFileName => GetUtils.isVector(this); | ||
9 | + bool get isImageFileName => GetUtils.isImage(this); | ||
10 | + bool get isAudioFileName => GetUtils.isAudio(this); | ||
11 | + bool get isVideoFileName => GetUtils.isVideo(this); | ||
12 | + bool get isTxtFileName => GetUtils.isTxt(this); | ||
13 | + bool get isDocumentFileName => GetUtils.isDocument(this); | ||
14 | + bool get isExcelFileName => GetUtils.isExcel(this); | ||
15 | + bool get isPPTFileName => GetUtils.isPPT(this); | ||
16 | + bool get isAPKFileName => GetUtils.isAPK(this); | ||
17 | + bool get isPDFFileName => GetUtils.isPDF(this); | ||
18 | + bool get isHTMLFileName => GetUtils.isHTML(this); | ||
19 | + bool get isURL => GetUtils.isURL(this); | ||
20 | + bool get isEmail => GetUtils.isEmail(this); | ||
21 | + bool get isPhoneNumber => GetUtils.isPhoneNumber(this); | ||
22 | + bool get isDateTime => GetUtils.isDateTime(this); | ||
23 | + bool get isMD5 => GetUtils.isMD5(this); | ||
24 | + bool get isSHA1 => GetUtils.isSHA1(this); | ||
25 | + bool get isSHA256 => GetUtils.isSHA256(this); | ||
26 | + bool get isISBN => GetUtils.isISBN(this); | ||
27 | + bool get isBinary => GetUtils.isBinary(this); | ||
28 | + bool get isIPv4 => GetUtils.isIPv4(this); | ||
29 | + bool get isIPv6 => GetUtils.isIPv6(this); | ||
30 | + bool get isHexadecimal => GetUtils.isHexadecimal(this); | ||
31 | + bool get isPalindrom => GetUtils.isPalindrom(this); | ||
32 | + bool get isPassport => GetUtils.isPassport(this); | ||
33 | + bool get isCurrency => GetUtils.isCurrency(this); | ||
34 | + bool isCpf(String s) => GetUtils.isCpf(this); | ||
35 | + bool isCnpj(String s) => GetUtils.isCnpj(this); | ||
36 | + bool isCaseInsensitiveContains(String b) => | ||
37 | + GetUtils.isCaseInsensitiveContains(this, b); | ||
38 | + bool isCaseInsensitiveContainsAny(String b) => | ||
39 | + GetUtils.isCaseInsensitiveContainsAny(this, b); | ||
40 | + String capitalize(String s, {bool firstOnly = false}) => | ||
41 | + GetUtils.capitalize(this, firstOnly: firstOnly); | ||
42 | + String capitalizeFirst(String s) => GetUtils.capitalizeFirst(this); | ||
43 | + String removeAllWhitespace(String s) => GetUtils.removeAllWhitespace(this); | ||
44 | + String camelCase(String s) => GetUtils.camelCase(this); | ||
45 | + String numericOnly(String s, {bool firstWordOnly = false}) => | ||
46 | + GetUtils.numericOnly(this, firstWordOnly: firstWordOnly); | ||
47 | +} |
1 | +import 'package:flutter/widgets.dart'; | ||
2 | + | ||
3 | +extension WidgetPaddingX on Widget { | ||
4 | + Widget paddingAll(double padding) => | ||
5 | + Padding(padding: EdgeInsets.all(padding), child: this); | ||
6 | + | ||
7 | + Widget paddingSymmetric({double horizontal = 0.0, double vertical = 0.0}) => | ||
8 | + Padding( | ||
9 | + padding: | ||
10 | + EdgeInsets.symmetric(horizontal: horizontal, vertical: vertical), | ||
11 | + child: this); | ||
12 | + | ||
13 | + Widget paddingOnly({ | ||
14 | + double left = 0.0, | ||
15 | + double top = 0.0, | ||
16 | + double right = 0.0, | ||
17 | + double bottom = 0.0, | ||
18 | + }) => | ||
19 | + Padding( | ||
20 | + padding: EdgeInsets.only( | ||
21 | + top: top, left: left, right: right, bottom: bottom), | ||
22 | + child: this); | ||
23 | + | ||
24 | + Widget get paddingZero => Padding(padding: EdgeInsets.zero, child: this); | ||
25 | +} | ||
26 | + | ||
27 | +extension WidgetMarginX on Widget { | ||
28 | + Widget marginAll(double margin) => | ||
29 | + Container(margin: EdgeInsets.all(margin), child: this); | ||
30 | + | ||
31 | + Widget marginSymmetric({double horizontal = 0.0, double vertical = 0.0}) => | ||
32 | + Container( | ||
33 | + margin: | ||
34 | + EdgeInsets.symmetric(horizontal: horizontal, vertical: vertical), | ||
35 | + child: this); | ||
36 | + | ||
37 | + Widget marginOnly({ | ||
38 | + double left = 0.0, | ||
39 | + double top = 0.0, | ||
40 | + double right = 0.0, | ||
41 | + double bottom = 0.0, | ||
42 | + }) => | ||
43 | + Container( | ||
44 | + margin: EdgeInsets.only( | ||
45 | + top: top, left: left, right: right, bottom: bottom), | ||
46 | + child: this); | ||
47 | + | ||
48 | + Widget get marginZero => Container(margin: EdgeInsets.zero, child: this); | ||
49 | +} |
@@ -27,7 +27,7 @@ class GetUtils { | @@ -27,7 +27,7 @@ class GetUtils { | ||
27 | } | 27 | } |
28 | 28 | ||
29 | /// Checks if string consist only numeric. | 29 | /// Checks if string consist only numeric. |
30 | - /// Numeric only doesnt accepting "." which double data type have | 30 | + /// Numeric only doesn't accepting "." which double data type have |
31 | static bool isNumericOnly(String s) => | 31 | static bool isNumericOnly(String s) => |
32 | RegexValidation.hasMatch(s, regex.numericOnly); | 32 | RegexValidation.hasMatch(s, regex.numericOnly); |
33 | 33 |
1 | -export 'src/utils/context_extensions/extensions.dart'; | 1 | +export 'src/utils/extensions/export.dart'; |
2 | export 'src/utils/queue/get_queue.dart'; | 2 | export 'src/utils/queue/get_queue.dart'; |
3 | export 'src/utils/platform/platform.dart'; | 3 | export 'src/utils/platform/platform.dart'; |
4 | export 'src/utils/regex/get_utils.dart'; | 4 | export 'src/utils/regex/get_utils.dart'; |
5 | -export 'src/utils/regex/get_utils_extensions.dart'; |
test/src/extensions/num_extensions_test.dart
0 → 100644
1 | +import 'package:flutter_test/flutter_test.dart'; | ||
2 | +import 'package:get/utils.dart'; | ||
3 | + | ||
4 | +void main() { | ||
5 | + num x = 5; | ||
6 | + num y = 7; | ||
7 | + test('Test for var.isLowerThan(value)', () { | ||
8 | + expect(x.isLowerThan(y), true); | ||
9 | + expect(y.isLowerThan(x), false); | ||
10 | + }); | ||
11 | + test('Test for var.isGreaterThan(value)', () { | ||
12 | + expect(x.isGreaterThan(y), false); | ||
13 | + expect(y.isGreaterThan(x), true); | ||
14 | + }); | ||
15 | + test('Test for var.isGreaterThan(value)', () { | ||
16 | + expect(x.isEqual(y), false); | ||
17 | + expect(y.isEqual(x), false); | ||
18 | + expect(x.isEqual(5), true); | ||
19 | + expect(y.isEqual(7), true); | ||
20 | + }); | ||
21 | +} |
1 | +import 'package:flutter_test/flutter_test.dart'; | ||
2 | +import 'package:get/utils.dart'; | ||
3 | + | ||
4 | +void main() { | ||
5 | + group('Test group for extension: isNullOrBlank', () { | ||
6 | + String testString; | ||
7 | + test('String extension: isNullOrBlank', () { | ||
8 | + expect(testString.isNullOrBlank, equals(true)); | ||
9 | + }); | ||
10 | + test('String extension: isNullOrBlank', () { | ||
11 | + testString = 'Not null anymore'; | ||
12 | + expect(testString.isNullOrBlank, equals(false)); | ||
13 | + }); | ||
14 | + test('String extension: isNullOrBlank', () { | ||
15 | + testString = ''; | ||
16 | + expect(testString.isNullOrBlank, equals(true)); | ||
17 | + }); | ||
18 | + }); | ||
19 | +} |
1 | +import 'package:flutter/widgets.dart'; | ||
2 | +import 'package:flutter_test/flutter_test.dart'; | ||
3 | +import 'package:get/utils.dart'; | ||
4 | + | ||
5 | +void main() { | ||
6 | + group('Group test for PaddingX Extension', () { | ||
7 | + testWidgets('Test of paddingAll', (WidgetTester tester) async { | ||
8 | + Widget containerTest; | ||
9 | + | ||
10 | + expect(find.byType(Padding), findsNothing); | ||
11 | + | ||
12 | + await tester.pumpWidget(containerTest.paddingAll(16)); | ||
13 | + | ||
14 | + expect(find.byType(Padding), findsOneWidget); | ||
15 | + }); | ||
16 | + | ||
17 | + testWidgets('Test of paddingOnly', (WidgetTester tester) async { | ||
18 | + Widget containerTest; | ||
19 | + | ||
20 | + expect(find.byType(Padding), findsNothing); | ||
21 | + | ||
22 | + await tester.pumpWidget(containerTest.paddingOnly(top: 16)); | ||
23 | + | ||
24 | + expect(find.byType(Padding), findsOneWidget); | ||
25 | + }); | ||
26 | + | ||
27 | + testWidgets('Test of paddingSymmetric', (WidgetTester tester) async { | ||
28 | + Widget containerTest; | ||
29 | + | ||
30 | + expect(find.byType(Padding), findsNothing); | ||
31 | + | ||
32 | + await tester.pumpWidget(containerTest.paddingSymmetric(vertical: 16)); | ||
33 | + | ||
34 | + expect(find.byType(Padding), findsOneWidget); | ||
35 | + }); | ||
36 | + | ||
37 | + testWidgets('Test of paddingZero', (WidgetTester tester) async { | ||
38 | + Widget containerTest; | ||
39 | + | ||
40 | + expect(find.byType(Padding), findsNothing); | ||
41 | + | ||
42 | + await tester.pumpWidget(containerTest.paddingZero); | ||
43 | + | ||
44 | + expect(find.byType(Padding), findsOneWidget); | ||
45 | + }); | ||
46 | + }); | ||
47 | + | ||
48 | + group('Group test for MarginX Extension', () { | ||
49 | + testWidgets('Test of marginAll', (WidgetTester tester) async { | ||
50 | + Widget containerTest; | ||
51 | + | ||
52 | + await tester.pumpWidget(containerTest.marginAll(16)); | ||
53 | + | ||
54 | + expect(find.byType(Container), findsOneWidget); | ||
55 | + }); | ||
56 | + | ||
57 | + testWidgets('Test of marginOnly', (WidgetTester tester) async { | ||
58 | + Widget containerTest; | ||
59 | + | ||
60 | + await tester.pumpWidget(containerTest.marginOnly(top: 16)); | ||
61 | + | ||
62 | + expect(find.byType(Container), findsOneWidget); | ||
63 | + }); | ||
64 | + | ||
65 | + testWidgets('Test of marginSymmetric', (WidgetTester tester) async { | ||
66 | + Widget containerTest; | ||
67 | + | ||
68 | + await tester.pumpWidget(containerTest.marginSymmetric(vertical: 16)); | ||
69 | + | ||
70 | + expect(find.byType(Container), findsOneWidget); | ||
71 | + }); | ||
72 | + | ||
73 | + testWidgets('Test of marginZero', (WidgetTester tester) async { | ||
74 | + Widget containerTest; | ||
75 | + | ||
76 | + await tester.pumpWidget(containerTest.marginZero); | ||
77 | + | ||
78 | + expect(find.byType(Container), findsOneWidget); | ||
79 | + }); | ||
80 | + }); | ||
81 | +} |
-
Please register or login to post a comment