Committed by
GitHub
Merge pull request #1180 from navaronbracke/error_codes_cleanup
fix: Clean up error codes
Showing
14 changed files
with
152 additions
and
39 deletions
| @@ -120,12 +120,7 @@ class MobileScanner( | @@ -120,12 +120,7 @@ class MobileScanner( | ||
| 120 | } | 120 | } |
| 121 | 121 | ||
| 122 | if (!returnImage) { | 122 | if (!returnImage) { |
| 123 | - mobileScannerCallback( | ||
| 124 | - barcodeMap, | ||
| 125 | - null, | ||
| 126 | - null, | ||
| 127 | - null | ||
| 128 | - ) | 123 | + mobileScannerCallback(barcodeMap, null, null, null) |
| 129 | return@addOnSuccessListener | 124 | return@addOnSuccessListener |
| 130 | } | 125 | } |
| 131 | 126 |
android/src/main/kotlin/dev/steenbakker/mobile_scanner/objects/MobileScannerErrorCodes.kt
0 → 100644
| 1 | +package dev.steenbakker.mobile_scanner.objects | ||
| 2 | + | ||
| 3 | +class MobileScannerErrorCodes { | ||
| 4 | + companion object { | ||
| 5 | + const val ALREADY_STARTED_ERROR = "MOBILE_SCANNER_ALREADY_STARTED_ERROR" | ||
| 6 | + const val ALREADY_STARTED_ERROR_MESSAGE = "The scanner was already started." | ||
| 7 | + // The error code 'BARCODE_ERROR' does not have an error message, | ||
| 8 | + // because it uses the error message from the underlying error. | ||
| 9 | + const val BARCODE_ERROR = "MOBILE_SCANNER_BARCODE_ERROR" | ||
| 10 | + // The error code 'CAMERA_ACCESS_DENIED' does not have an error message, | ||
| 11 | + // because it is used for a boolean result. | ||
| 12 | + const val CAMERA_ACCESS_DENIED = "MOBILE_SCANNER_CAMERA_PERMISSION_DENIED" | ||
| 13 | + const val CAMERA_ERROR = "MOBILE_SCANNER_CAMERA_ERROR" | ||
| 14 | + const val CAMERA_ERROR_MESSAGE = "An error occurred when opening the camera." | ||
| 15 | + const val CAMERA_PERMISSIONS_REQUEST_ONGOING = "MOBILE_SCANNER_CAMERA_PERMISSION_REQUEST_PENDING" | ||
| 16 | + const val CAMERA_PERMISSIONS_REQUEST_ONGOING_MESSAGE = "Another request is ongoing and multiple requests cannot be handled at once." | ||
| 17 | + const val GENERIC_ERROR = "MOBILE_SCANNER_GENERIC_ERROR" | ||
| 18 | + const val GENERIC_ERROR_MESSAGE = "An unknown error occurred." | ||
| 19 | + const val INVALID_ZOOM_SCALE_ERROR_MESSAGE = "The zoom scale should be between 0 and 1 (both inclusive)" | ||
| 20 | + const val NO_CAMERA_ERROR = "MOBILE_SCANNER_NO_CAMERA_ERROR" | ||
| 21 | + const val NO_CAMERA_ERROR_MESSAGE = "No cameras available." | ||
| 22 | + const val SET_SCALE_WHEN_STOPPED_ERROR = "MOBILE_SCANNER_SET_SCALE_WHEN_STOPPED_ERROR" | ||
| 23 | + const val SET_SCALE_WHEN_STOPPED_ERROR_MESSAGE = "The zoom scale cannot be changed when the camera is stopped." | ||
| 24 | + } | ||
| 25 | +} |
| @@ -60,7 +60,7 @@ class _BarcodeScannerWithControllerState | @@ -60,7 +60,7 @@ class _BarcodeScannerWithControllerState | ||
| 60 | 60 | ||
| 61 | @override | 61 | @override |
| 62 | void didChangeAppLifecycleState(AppLifecycleState state) { | 62 | void didChangeAppLifecycleState(AppLifecycleState state) { |
| 63 | - if (!controller.value.isInitialized) { | 63 | + if (!controller.value.hasCameraPermission) { |
| 64 | return; | 64 | return; |
| 65 | } | 65 | } |
| 66 | 66 |
| @@ -6,6 +6,12 @@ | @@ -6,6 +6,12 @@ | ||
| 6 | // | 6 | // |
| 7 | import Foundation | 7 | import Foundation |
| 8 | 8 | ||
| 9 | +// TODO: decide if we should keep or discard this enum | ||
| 10 | +// When merging the iOS / MacOS implementations we should either keep the enum or remove it | ||
| 11 | + | ||
| 12 | +// This enum is a bit of a leftover from older parts of the iOS implementation. | ||
| 13 | +// It is used by the handler that throws these error codes, | ||
| 14 | +// while the plugin class intercepts these and converts them to `FlutterError()`s. | ||
| 9 | enum MobileScannerError: Error { | 15 | enum MobileScannerError: Error { |
| 10 | case noCamera | 16 | case noCamera |
| 11 | case alreadyStarted | 17 | case alreadyStarted |
ios/Classes/MobileScannerErrorCodes.swift
0 → 100644
| 1 | +// | ||
| 2 | +// MobileScannerErrorCodes.swift | ||
| 3 | +// mobile_scanner | ||
| 4 | +// | ||
| 5 | +// Created by Navaron Bracke on 28/05/2024. | ||
| 6 | +// | ||
| 7 | + | ||
| 8 | +import Foundation | ||
| 9 | + | ||
| 10 | +/// This struct defines the error codes and error messages for MobileScanner errors. | ||
| 11 | +/// | ||
| 12 | +/// These are used by `FlutterError` as error code and error message. | ||
| 13 | +/// | ||
| 14 | +/// This struct should not be confused with `MobileScannerError`, | ||
| 15 | +/// which is an implementation detail for the iOS implementation. | ||
| 16 | +struct MobileScannerErrorCodes { | ||
| 17 | + static let ALREADY_STARTED_ERROR = "MOBILE_SCANNER_ALREADY_STARTED_ERROR" | ||
| 18 | + static let ALREADY_STARTED_ERROR_MESSAGE = "The scanner was already started." | ||
| 19 | + // The error code 'BARCODE_ERROR' does not have an error message, | ||
| 20 | + // because it uses the error message from the undelying error. | ||
| 21 | + static let BARCODE_ERROR = "MOBILE_SCANNER_BARCODE_ERROR" | ||
| 22 | + // The error code 'CAMERA_ERROR' does not have an error message, | ||
| 23 | + // because it uses the error message from the underlying error. | ||
| 24 | + static let CAMERA_ERROR = "MOBILE_SCANNER_CAMERA_ERROR" | ||
| 25 | + static let GENERIC_ERROR = "MOBILE_SCANNER_GENERIC_ERROR" | ||
| 26 | + static let GENERIC_ERROR_MESSAGE = "An unknown error occurred." | ||
| 27 | + // This message is used with the 'GENERIC_ERROR' error code. | ||
| 28 | + static let INVALID_ZOOM_SCALE_ERROR_MESSAGE = "The zoom scale should be between 0 and 1 (both inclusive)" | ||
| 29 | + static let NO_CAMERA_ERROR = "MOBILE_SCANNER_NO_CAMERA_ERROR" | ||
| 30 | + static let NO_CAMERA_ERROR_MESSAGE = "No cameras available." | ||
| 31 | + static let SET_SCALE_WHEN_STOPPED_ERROR = "MOBILE_SCANNER_SET_SCALE_WHEN_STOPPED_ERROR" | ||
| 32 | + static let SET_SCALE_WHEN_STOPPED_ERROR_MESSAGE = "The zoom scale cannot be changed when the camera is stopped." | ||
| 33 | +} |
| @@ -258,9 +258,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin { | @@ -258,9 +258,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin { | ||
| 258 | let uiImage = UIImage(contentsOfFile: (call.arguments as! Dictionary<String, Any?>)["filePath"] as? String ?? "") | 258 | let uiImage = UIImage(contentsOfFile: (call.arguments as! Dictionary<String, Any?>)["filePath"] as? String ?? "") |
| 259 | 259 | ||
| 260 | if (uiImage == nil) { | 260 | if (uiImage == nil) { |
| 261 | - result(FlutterError(code: "MobileScanner", | ||
| 262 | - message: "No image found in analyzeImage!", | ||
| 263 | - details: nil)) | 261 | + result(nil) |
| 264 | return | 262 | return |
| 265 | } | 263 | } |
| 266 | 264 |
| 1 | +import 'package:flutter/services.dart'; | ||
| 1 | import 'package:mobile_scanner/src/mobile_scanner_controller.dart'; | 2 | import 'package:mobile_scanner/src/mobile_scanner_controller.dart'; |
| 2 | 3 | ||
| 3 | /// This enum defines the different error codes for the mobile scanner. | 4 | /// This enum defines the different error codes for the mobile scanner. |
| @@ -24,5 +25,25 @@ enum MobileScannerErrorCode { | @@ -24,5 +25,25 @@ enum MobileScannerErrorCode { | ||
| 24 | permissionDenied, | 25 | permissionDenied, |
| 25 | 26 | ||
| 26 | /// Scanning is unsupported on the current device. | 27 | /// Scanning is unsupported on the current device. |
| 27 | - unsupported, | 28 | + unsupported; |
| 29 | + | ||
| 30 | + /// Convert the given [PlatformException.code] to a [MobileScannerErrorCode]. | ||
| 31 | + factory MobileScannerErrorCode.fromPlatformException( | ||
| 32 | + PlatformException exception, | ||
| 33 | + ) { | ||
| 34 | + // The following error code mapping should be kept in sync with their native counterparts. | ||
| 35 | + // These are located in `MobileScannerErrorCodes.kt` and `MobileScannerErrorCodes.swift`. | ||
| 36 | + return switch (exception.code) { | ||
| 37 | + // In case the scanner was already started, report the right error code. | ||
| 38 | + // If the scanner is already starting, | ||
| 39 | + // this error code is a signal to the controller to just ignore the attempt. | ||
| 40 | + 'MOBILE_SCANNER_ALREADY_STARTED_ERROR' => | ||
| 41 | + MobileScannerErrorCode.controllerAlreadyInitialized, | ||
| 42 | + // In case no cameras are available, using the scanner is not supported. | ||
| 43 | + 'MOBILE_SCANNER_NO_CAMERA_ERROR' => MobileScannerErrorCode.unsupported, | ||
| 44 | + 'MOBILE_SCANNER_CAMERA_PERMISSION_DENIED' => | ||
| 45 | + MobileScannerErrorCode.permissionDenied, | ||
| 46 | + _ => MobileScannerErrorCode.genericError, | ||
| 47 | + }; | ||
| 48 | + } | ||
| 28 | } | 49 | } |
| @@ -187,8 +187,7 @@ class MethodChannelMobileScanner extends MobileScannerPlatform { | @@ -187,8 +187,7 @@ class MethodChannelMobileScanner extends MobileScannerPlatform { | ||
| 187 | throw const MobileScannerException( | 187 | throw const MobileScannerException( |
| 188 | errorCode: MobileScannerErrorCode.controllerAlreadyInitialized, | 188 | errorCode: MobileScannerErrorCode.controllerAlreadyInitialized, |
| 189 | errorDetails: MobileScannerErrorDetails( | 189 | errorDetails: MobileScannerErrorDetails( |
| 190 | - message: | ||
| 191 | - 'The scanner was already started. Call stop() before calling start() again.', | 190 | + message: 'The scanner was already started.', |
| 192 | ), | 191 | ), |
| 193 | ); | 192 | ); |
| 194 | } | 193 | } |
| @@ -204,7 +203,7 @@ class MethodChannelMobileScanner extends MobileScannerPlatform { | @@ -204,7 +203,7 @@ class MethodChannelMobileScanner extends MobileScannerPlatform { | ||
| 204 | ); | 203 | ); |
| 205 | } on PlatformException catch (error) { | 204 | } on PlatformException catch (error) { |
| 206 | throw MobileScannerException( | 205 | throw MobileScannerException( |
| 207 | - errorCode: MobileScannerErrorCode.genericError, | 206 | + errorCode: MobileScannerErrorCode.fromPlatformException(error), |
| 208 | errorDetails: MobileScannerErrorDetails( | 207 | errorDetails: MobileScannerErrorDetails( |
| 209 | code: error.code, | 208 | code: error.code, |
| 210 | details: error.details as Object?, | 209 | details: error.details as Object?, |
| @@ -240,17 +239,13 @@ class MethodChannelMobileScanner extends MobileScannerPlatform { | @@ -240,17 +239,13 @@ class MethodChannelMobileScanner extends MobileScannerPlatform { | ||
| 240 | startResult['currentTorchState'] as int? ?? -1, | 239 | startResult['currentTorchState'] as int? ?? -1, |
| 241 | ); | 240 | ); |
| 242 | 241 | ||
| 243 | - final Map<Object?, Object?>? sizeInfo = | ||
| 244 | - startResult['size'] as Map<Object?, Object?>?; | ||
| 245 | - final double? width = sizeInfo?['width'] as double?; | ||
| 246 | - final double? height = sizeInfo?['height'] as double?; | ||
| 247 | - | ||
| 248 | final Size size; | 242 | final Size size; |
| 249 | 243 | ||
| 250 | - if (width == null || height == null) { | ||
| 251 | - size = Size.zero; | ||
| 252 | - } else { | 244 | + if (startResult['size'] |
| 245 | + case {'width': final double width, 'height': final double height}) { | ||
| 253 | size = Size(width, height); | 246 | size = Size(width, height); |
| 247 | + } else { | ||
| 248 | + size = Size.zero; | ||
| 254 | } | 249 | } |
| 255 | 250 | ||
| 256 | return MobileScannerViewAttributes( | 251 | return MobileScannerViewAttributes( |
| @@ -281,8 +281,7 @@ class _MobileScannerState extends State<MobileScanner> | @@ -281,8 +281,7 @@ class _MobileScannerState extends State<MobileScanner> | ||
| 281 | 281 | ||
| 282 | @override | 282 | @override |
| 283 | void didChangeAppLifecycleState(AppLifecycleState state) { | 283 | void didChangeAppLifecycleState(AppLifecycleState state) { |
| 284 | - if (widget.controller != null) return; | ||
| 285 | - if (!controller.value.isInitialized) { | 284 | + if (widget.controller != null || !controller.value.hasCameraPermission) { |
| 286 | return; | 285 | return; |
| 287 | } | 286 | } |
| 288 | 287 |
| 1 | import 'package:mobile_scanner/src/enums/mobile_scanner_error_code.dart'; | 1 | import 'package:mobile_scanner/src/enums/mobile_scanner_error_code.dart'; |
| 2 | 2 | ||
| 3 | -/// This class represents an exception thrown by the mobile scanner. | 3 | +/// This class represents an exception thrown by the [MobileScannerController]. |
| 4 | class MobileScannerException implements Exception { | 4 | class MobileScannerException implements Exception { |
| 5 | const MobileScannerException({ | 5 | const MobileScannerException({ |
| 6 | required this.errorCode, | 6 | required this.errorCode, |
| @@ -16,9 +16,9 @@ class MobileScannerException implements Exception { | @@ -16,9 +16,9 @@ class MobileScannerException implements Exception { | ||
| 16 | @override | 16 | @override |
| 17 | String toString() { | 17 | String toString() { |
| 18 | if (errorDetails != null && errorDetails?.message != null) { | 18 | if (errorDetails != null && errorDetails?.message != null) { |
| 19 | - return "MobileScannerException: code ${errorCode.name}, message: ${errorDetails?.message}"; | 19 | + return 'MobileScannerException(${errorCode.name}, ${errorDetails?.message})'; |
| 20 | } | 20 | } |
| 21 | - return "MobileScannerException: ${errorCode.name}"; | 21 | + return 'MobileScannerException(${errorCode.name})'; |
| 22 | } | 22 | } |
| 23 | } | 23 | } |
| 24 | 24 | ||
| @@ -46,3 +46,22 @@ class MobileScannerErrorDetails { | @@ -46,3 +46,22 @@ class MobileScannerErrorDetails { | ||
| 46 | /// This exception type is only used internally, | 46 | /// This exception type is only used internally, |
| 47 | /// and is not part of the public API. | 47 | /// and is not part of the public API. |
| 48 | class PermissionRequestPendingException implements Exception {} | 48 | class PermissionRequestPendingException implements Exception {} |
| 49 | + | ||
| 50 | +/// This class represents an exception thrown by the [MobileScannerController] | ||
| 51 | +/// when a barcode scanning error occurs when processing an input frame. | ||
| 52 | +class MobileScannerBarcodeException implements Exception { | ||
| 53 | + /// Creates a new [MobileScannerBarcodeException] with the given error message. | ||
| 54 | + const MobileScannerBarcodeException(this.message); | ||
| 55 | + | ||
| 56 | + /// The error message of the exception. | ||
| 57 | + final String? message; | ||
| 58 | + | ||
| 59 | + @override | ||
| 60 | + String toString() { | ||
| 61 | + if (message?.isNotEmpty ?? false) { | ||
| 62 | + return 'MobileScannerBarcodeException($message)'; | ||
| 63 | + } | ||
| 64 | + | ||
| 65 | + return 'MobileScannerBarcodeException(Could not detect a barcode in the input image.)'; | ||
| 66 | + } | ||
| 67 | +} |
| 1 | import 'dart:ui'; | 1 | import 'dart:ui'; |
| 2 | 2 | ||
| 3 | import 'package:mobile_scanner/src/enums/camera_facing.dart'; | 3 | import 'package:mobile_scanner/src/enums/camera_facing.dart'; |
| 4 | +import 'package:mobile_scanner/src/enums/mobile_scanner_error_code.dart'; | ||
| 4 | import 'package:mobile_scanner/src/enums/torch_state.dart'; | 5 | import 'package:mobile_scanner/src/enums/torch_state.dart'; |
| 5 | import 'package:mobile_scanner/src/mobile_scanner_exception.dart'; | 6 | import 'package:mobile_scanner/src/mobile_scanner_exception.dart'; |
| 6 | 7 | ||
| @@ -43,7 +44,8 @@ class MobileScannerState { | @@ -43,7 +44,8 @@ class MobileScannerState { | ||
| 43 | 44 | ||
| 44 | /// Whether the mobile scanner has initialized successfully. | 45 | /// Whether the mobile scanner has initialized successfully. |
| 45 | /// | 46 | /// |
| 46 | - /// This is `true` if the camera is ready to be used. | 47 | + /// This does not indicate that the camera permission was granted. |
| 48 | + /// To check if the camera permission was granted, use [hasCameraPermission]. | ||
| 47 | final bool isInitialized; | 49 | final bool isInitialized; |
| 48 | 50 | ||
| 49 | /// Whether the mobile scanner is currently running. | 51 | /// Whether the mobile scanner is currently running. |
| @@ -60,6 +62,12 @@ class MobileScannerState { | @@ -60,6 +62,12 @@ class MobileScannerState { | ||
| 60 | /// The current zoom scale of the camera. | 62 | /// The current zoom scale of the camera. |
| 61 | final double zoomScale; | 63 | final double zoomScale; |
| 62 | 64 | ||
| 65 | + /// Whether permission to access the camera was granted. | ||
| 66 | + bool get hasCameraPermission { | ||
| 67 | + return isInitialized && | ||
| 68 | + error?.errorCode != MobileScannerErrorCode.permissionDenied; | ||
| 69 | + } | ||
| 70 | + | ||
| 63 | /// Create a copy of this state with the given parameters. | 71 | /// Create a copy of this state with the given parameters. |
| 64 | MobileScannerState copyWith({ | 72 | MobileScannerState copyWith({ |
| 65 | int? availableCameras, | 73 | int? availableCameras, |
| 1 | +// | ||
| 2 | +// MobileScannerErrorCodes.swift | ||
| 3 | +// mobile_scanner | ||
| 4 | +// | ||
| 5 | +// Created by Navaron Bracke on 27/05/2024. | ||
| 6 | +// | ||
| 7 | + | ||
| 8 | +import Foundation | ||
| 9 | + | ||
| 10 | +struct MobileScannerErrorCodes { | ||
| 11 | + static let ALREADY_STARTED_ERROR = "MOBILE_SCANNER_ALREADY_STARTED_ERROR" | ||
| 12 | + static let ALREADY_STARTED_ERROR_MESSAGE = "The scanner was already started." | ||
| 13 | + // The error code 'BARCODE_ERROR' does not have an error message, | ||
| 14 | + // because it uses the error message from the undelying error. | ||
| 15 | + static let BARCODE_ERROR = "MOBILE_SCANNER_BARCODE_ERROR" | ||
| 16 | + // The error code 'CAMERA_ERROR' does not have an error message, | ||
| 17 | + // because it uses the error message from the underlying error. | ||
| 18 | + static let CAMERA_ERROR = "MOBILE_SCANNER_CAMERA_ERROR" | ||
| 19 | + static let NO_CAMERA_ERROR = "MOBILE_SCANNER_NO_CAMERA_ERROR" | ||
| 20 | + static let NO_CAMERA_ERROR_MESSAGE = "No cameras available." | ||
| 21 | +} |
| @@ -273,7 +273,6 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, | @@ -273,7 +273,6 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, | ||
| 273 | 273 | ||
| 274 | let argReader = MapArgumentReader(call.arguments as? [String: Any]) | 274 | let argReader = MapArgumentReader(call.arguments as? [String: Any]) |
| 275 | 275 | ||
| 276 | - // let ratio: Int = argReader.int(key: "ratio") | ||
| 277 | let torch:Bool = argReader.bool(key: "torch") ?? false | 276 | let torch:Bool = argReader.bool(key: "torch") ?? false |
| 278 | let facing:Int = argReader.int(key: "facing") ?? 1 | 277 | let facing:Int = argReader.int(key: "facing") ?? 1 |
| 279 | let speed:Int = argReader.int(key: "speed") ?? 0 | 278 | let speed:Int = argReader.int(key: "speed") ?? 0 |
| @@ -327,7 +326,6 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, | @@ -327,7 +326,6 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, | ||
| 327 | videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue.main) | 326 | videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue.main) |
| 328 | captureSession!.addOutput(videoOutput) | 327 | captureSession!.addOutput(videoOutput) |
| 329 | for connection in videoOutput.connections { | 328 | for connection in videoOutput.connections { |
| 330 | - // connection.videoOrientation = .portrait | ||
| 331 | if position == .front && connection.isVideoMirroringSupported { | 329 | if position == .front && connection.isVideoMirroringSupported { |
| 332 | connection.isVideoMirrored = true | 330 | connection.isVideoMirrored = true |
| 333 | } | 331 | } |
| @@ -459,20 +457,14 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, | @@ -459,20 +457,14 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, | ||
| 459 | let symbologies:[VNBarcodeSymbology] = argReader.toSymbology() | 457 | let symbologies:[VNBarcodeSymbology] = argReader.toSymbology() |
| 460 | 458 | ||
| 461 | guard let filePath: String = argReader.string(key: "filePath") else { | 459 | guard let filePath: String = argReader.string(key: "filePath") else { |
| 462 | - // TODO: fix error code | ||
| 463 | - result(FlutterError(code: "MobileScanner", | ||
| 464 | - message: "No image found in analyzeImage!", | ||
| 465 | - details: nil)) | 460 | + result(nil) |
| 466 | return | 461 | return |
| 467 | } | 462 | } |
| 468 | 463 | ||
| 469 | let fileUrl = URL(fileURLWithPath: filePath) | 464 | let fileUrl = URL(fileURLWithPath: filePath) |
| 470 | 465 | ||
| 471 | guard let ciImage = CIImage(contentsOf: fileUrl) else { | 466 | guard let ciImage = CIImage(contentsOf: fileUrl) else { |
| 472 | - // TODO: fix error code | ||
| 473 | - result(FlutterError(code: "MobileScanner", | ||
| 474 | - message: "No image found in analyzeImage!", | ||
| 475 | - details: nil)) | 467 | + result(nil) |
| 476 | return | 468 | return |
| 477 | } | 469 | } |
| 478 | 470 |
-
Please register or login to post a comment