Commit 6b980613 authored by DatHV's avatar DatHV
Browse files

update project structure

parent bfff9e47
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import '../../screen/membership/models/membership_level_model.dart';
part 'working_site_model.g.dart'; part 'working_site_model.g.dart';
@JsonSerializable() @JsonSerializable()
......
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:mypoint_flutter_app/extensions/datetime_extensions.dart'; import 'package:mypoint_flutter_app/core/utils/extensions/datetime_extensions.dart';
import 'package:mypoint_flutter_app/preference/data_preference.dart'; import 'package:mypoint_flutter_app/shared/preferences/data_preference.dart';
import 'package:mypoint_flutter_app/screen/personal/personal_gender.dart'; import 'package:mypoint_flutter_app/features/personal/personal_gender.dart';
import '../../configs/callbacks.dart'; import '../../app/config/callbacks.dart';
import '../location_address/location_address_viewmodel.dart'; import '../location_address/location_address_viewmodel.dart';
enum SectionPersonalEditType { enum SectionPersonalEditType {
......
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:mypoint_flutter_app/preference/data_preference.dart'; import 'package:mypoint_flutter_app/shared/preferences/data_preference.dart';
import 'package:mypoint_flutter_app/screen/personal/personal_edit_item_model.dart'; import 'package:mypoint_flutter_app/features/personal/personal_edit_item_model.dart';
import 'package:mypoint_flutter_app/screen/personal/personal_edit_viewmodel.dart'; import 'package:mypoint_flutter_app/features/personal/personal_edit_viewmodel.dart';
import 'package:mypoint_flutter_app/screen/personal/personal_gender.dart'; import 'package:mypoint_flutter_app/features/personal/personal_gender.dart';
import '../../base/base_screen.dart'; import '../../shared/widgets/base_view/base_screen.dart';
import '../../base/basic_state.dart'; import '../../shared/widgets/base_view/basic_state.dart';
import '../../resources/base_color.dart'; import '../../core/theme/base_color.dart';
import '../../widgets/alert/data_alert_model.dart'; import '../../shared/widgets/alert/data_alert_model.dart';
import '../../widgets/custom_navigation_bar.dart'; import '../../shared/widgets/custom_navigation_bar.dart';
class PersonalEditScreen extends BaseScreen { class PersonalEditScreen extends BaseScreen {
const PersonalEditScreen({super.key}); const PersonalEditScreen({super.key});
...@@ -141,7 +140,7 @@ class _PersonalEditScreenState extends BaseState<PersonalEditScreen> with BasicS ...@@ -141,7 +140,7 @@ class _PersonalEditScreenState extends BaseState<PersonalEditScreen> with BasicS
); );
} }
_onTapItemChangeValue(PersonalEditItemModel item) async { Future<void> _onTapItemChangeValue(PersonalEditItemModel item) async {
if (item.sectionType == SectionPersonalEditType.province || item.sectionType == SectionPersonalEditType.district) { if (item.sectionType == SectionPersonalEditType.province || item.sectionType == SectionPersonalEditType.district) {
viewModel.navigateToLocationScreen(item); viewModel.navigateToLocationScreen(item);
} else if (item.sectionType == SectionPersonalEditType.birthday) { } else if (item.sectionType == SectionPersonalEditType.birthday) {
...@@ -291,7 +290,7 @@ class _PersonalEditScreenState extends BaseState<PersonalEditScreen> with BasicS ...@@ -291,7 +290,7 @@ class _PersonalEditScreenState extends BaseState<PersonalEditScreen> with BasicS
onSelected(gender); onSelected(gender);
}, },
); );
}).toList(), }),
const Divider(height: 100), const Divider(height: 100),
], ],
); );
......
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:mypoint_flutter_app/extensions/string_extension.dart'; import 'package:mypoint_flutter_app/core/utils/extensions/string_extension.dart';
import 'package:mypoint_flutter_app/networking/restful_api_client_all_request.dart'; import 'package:mypoint_flutter_app/core/network/restful_api_client_all_request.dart';
import 'package:mypoint_flutter_app/screen/personal/personal_edit_item_model.dart'; import 'package:mypoint_flutter_app/features/personal/personal_edit_item_model.dart';
import 'package:mypoint_flutter_app/screen/personal/personal_gender.dart'; import 'package:mypoint_flutter_app/features/personal/personal_gender.dart';
import '../../networking/restful_api_viewmodel.dart'; import '../../core/network/restful_api_viewmodel.dart';
import '../../configs/constants.dart'; import '../../app/config/constants.dart';
import '../../preference/data_preference.dart'; import '../../shared/preferences/data_preference.dart';
import '../../shared/router_gage.dart'; import '../../shared/router_gage.dart';
import '../../utils/validation_utils.dart'; import '../../core/utils/validation_utils.dart';
import '../location_address/location_address_viewmodel.dart'; import '../location_address/location_address_viewmodel.dart';
class PersonalEditViewModel extends RestfulApiViewModel { class PersonalEditViewModel extends RestfulApiViewModel {
...@@ -26,33 +26,33 @@ class PersonalEditViewModel extends RestfulApiViewModel { ...@@ -26,33 +26,33 @@ class PersonalEditViewModel extends RestfulApiViewModel {
final profile = DataPreference.instance.profile; final profile = DataPreference.instance.profile;
if (profile == null) return; if (profile == null) return;
province.value = AddressBaseModel( province.value = AddressBaseModel(
code: profile?.workerSite?.locationProvinceCode, code: profile.workerSite?.locationProvinceCode,
name: profile?.workerSite?.locationProvinceName, name: profile.workerSite?.locationProvinceName,
); );
district.value = AddressBaseModel( district.value = AddressBaseModel(
code: profile?.workerSite?.locationDistrictCode, code: profile.workerSite?.locationDistrictCode,
name: profile?.workerSite?.locationDistrictName, name: profile.workerSite?.locationDistrictName,
); );
birthday = profile?.workerSite?.birthday?.toDateFormat('yyyy-MM-dd'); birthday = profile.workerSite?.birthday?.toDateFormat('yyyy-MM-dd');
gender = PersonalGender.from(profile.workerSite?.sex ?? "U"); gender = PersonalGender.from(profile.workerSite?.sex ?? "U");
var name = profile?.workerSite?.fullname ?? ""; var name = profile.workerSite?.fullname ?? "";
editDataModel.value = PersonalEditDataModel( editDataModel.value = PersonalEditDataModel(
name: name, name: name,
nickname: profile?.workerSite?.nickname, nickname: profile.workerSite?.nickname,
phone: profile?.workerSite?.phoneNumber, phone: profile.workerSite?.phoneNumber,
email: profile?.workerSite?.email, email: profile.workerSite?.email,
identificationNumber: profile?.workerSite?.identificationNumber, identificationNumber: profile.workerSite?.identificationNumber,
birthday: birthday, birthday: birthday,
gender: gender, gender: gender,
address: profile?.workerSite?.addressFull, address: profile.workerSite?.addressFull,
province: province.value, province: province.value,
district: district.value, district: district.value,
); );
isValidate.value = validate(); isValidate.value = validate();
} }
updateItemEditData(PersonalEditItemModel item, String value) { void updateItemEditData(PersonalEditItemModel item, String value) {
if (editDataModel.value == null) return; if (editDataModel.value == null) return;
switch (item.sectionType ?? SectionPersonalEditType.nickname) { switch (item.sectionType ?? SectionPersonalEditType.nickname) {
case SectionPersonalEditType.name: case SectionPersonalEditType.name:
...@@ -104,7 +104,7 @@ class PersonalEditViewModel extends RestfulApiViewModel { ...@@ -104,7 +104,7 @@ class PersonalEditViewModel extends RestfulApiViewModel {
}); });
} }
navigateToLocationScreen(PersonalEditItemModel item) async { Future<void> navigateToLocationScreen(PersonalEditItemModel item) async {
if (item.sectionType == null) return; if (item.sectionType == null) return;
if (item.sectionType == SectionPersonalEditType.province) { if (item.sectionType == SectionPersonalEditType.province) {
final result = await Get.toNamed( final result = await Get.toNamed(
...@@ -146,7 +146,7 @@ class PersonalEditViewModel extends RestfulApiViewModel { ...@@ -146,7 +146,7 @@ class PersonalEditViewModel extends RestfulApiViewModel {
if (model.birthday == null) { if (model.birthday == null) {
return false; return false;
} }
if (model.gender == null || model.gender == 'notAllowed') { if (model.gender == null || model.gender == PersonalGender.unknown) {
return false; return false;
} }
if ((model.email ?? '').isNotEmpty && !isValidEmail(model.email!)) { if ((model.email ?? '').isNotEmpty && !isValidEmail(model.email!)) {
......
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:mypoint_flutter_app/directional/directional_screen.dart'; import 'package:mypoint_flutter_app/shared/navigation/directional_screen.dart';
import 'package:mypoint_flutter_app/extensions/num_extension.dart'; import 'package:mypoint_flutter_app/core/utils/extensions/num_extension.dart';
import 'package:mypoint_flutter_app/preference/data_preference.dart'; import 'package:mypoint_flutter_app/shared/preferences/data_preference.dart';
import 'package:mypoint_flutter_app/preference/point/point_manager.dart'; import 'package:mypoint_flutter_app/shared/preferences/point/point_manager.dart';
import '../../base/base_screen.dart'; import '../../shared/widgets/base_view/base_screen.dart';
import '../../base/basic_state.dart'; import '../../shared/widgets/base_view/basic_state.dart';
import '../../directional/directional_action_type.dart'; import '../../app/routing/directional_action_type.dart';
import '../../preference/package_info.dart'; import '../../core/services/package_info.dart';
import '../../preference/point/header_home_model.dart'; import '../../core/theme/base_color.dart';
import '../../resources/base_color.dart';
import '../../shared/router_gage.dart'; import '../../shared/router_gage.dart';
import '../../services/logout_service.dart'; import '../../core/services/logout_service.dart';
import 'package:mypoint_flutter_app/web/web_helper.dart'; import '../../shared/widgets/alert/data_alert_model.dart';
import '../../widgets/alert/data_alert_model.dart';
import '../home/header_home_viewmodel.dart'; import '../home/header_home_viewmodel.dart';
import '../home/models/header_home_model.dart';
import '../popup_manager/popup_runner_helper.dart'; import '../popup_manager/popup_runner_helper.dart';
class PersonalScreen extends BaseScreen { class PersonalScreen extends BaseScreen {
......
import 'package:mypoint_flutter_app/directional/directional_screen.dart'; import 'package:mypoint_flutter_app/shared/navigation/directional_screen.dart';
class PipiSupportItemModel { class PipiSupportItemModel {
final String? id; final String? id;
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:mypoint_flutter_app/screen/pipi/pipi_detail_viewmodel.dart'; import 'package:mypoint_flutter_app/features/pipi/pipi_detail_viewmodel.dart';
import 'package:mypoint_flutter_app/widgets/image_loader.dart'; import 'package:mypoint_flutter_app/shared/widgets/image_loader.dart';
import '../home/models/pipi_detail_model.dart'; import 'model/pipi_detail_model.dart';
class PipiDetailScreen extends StatefulWidget { class PipiDetailScreen extends StatefulWidget {
const PipiDetailScreen({super.key}); const PipiDetailScreen({super.key});
......
import 'package:get/get_rx/src/rx_types/rx_types.dart'; import 'package:get/get_rx/src/rx_types/rx_types.dart';
import 'package:mypoint_flutter_app/networking/restful_api_client_all_request.dart'; import 'package:mypoint_flutter_app/core/network/restful_api_client_all_request.dart';
import '../../networking/restful_api_viewmodel.dart'; import '../../core/network/restful_api_viewmodel.dart';
import '../home/models/pipi_detail_model.dart'; import 'model/pipi_detail_model.dart';
class PipiDetailViewModel extends RestfulApiViewModel { class PipiDetailViewModel extends RestfulApiViewModel {
final RxList<PipiSupportItemModel> items = <PipiSupportItemModel>[].obs; final RxList<PipiSupportItemModel> items = <PipiSupportItemModel>[].obs;
...@@ -16,9 +16,9 @@ class PipiDetailViewModel extends RestfulApiViewModel { ...@@ -16,9 +16,9 @@ class PipiDetailViewModel extends RestfulApiViewModel {
await callApi<PipiDetailResponseModel>( await callApi<PipiDetailResponseModel>(
request: () => client.getPipiDetail(), request: () => client.getPipiDetail(),
onSuccess: (data, _) { onSuccess: (data, _) {
items.assignAll(data?.items ?? []); items.assignAll(data.items ?? []);
}, },
onFailure: (msg, _, __) async { onFailure: (msg, _, _) async {
items.clear(); items.clear();
}, },
); );
......
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:mypoint_flutter_app/directional/directional_screen.dart'; import 'package:mypoint_flutter_app/shared/navigation/directional_screen.dart';
part 'popup_manager_model.g.dart'; part 'popup_manager_model.g.dart';
@JsonSerializable() @JsonSerializable()
......
import 'dart:async'; import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mypoint_flutter_app/screen/popup_manager/popup_manager_model.dart'; import 'package:mypoint_flutter_app/features/popup_manager/popup_manager_model.dart';
import 'package:mypoint_flutter_app/screen/popup_manager/popup_manager_viewmodel.dart'; import 'package:mypoint_flutter_app/features/popup_manager/popup_manager_viewmodel.dart';
/// Logger tuỳ bạn hook vào hệ thống hiện có (Firebase, MoEngage, …) /// Logger tuỳ bạn hook vào hệ thống hiện có (Firebase, MoEngage, …)
void logPopupShowing({required String popupId, required String requestId}) { void logPopupShowing({required String popupId, required String requestId}) {
...@@ -31,7 +30,7 @@ Future<void> showPopupManagerScreen( ...@@ -31,7 +30,7 @@ Future<void> showPopupManagerScreen(
barrierDismissible: false, barrierDismissible: false,
barrierLabel: 'popup', barrierLabel: 'popup',
transitionDuration: const Duration(milliseconds: 220), transitionDuration: const Duration(milliseconds: 220),
pageBuilder: (_, __, ___) { pageBuilder: (_, _, _) {
return _BasePopupView( return _BasePopupView(
model: modelPopup, model: modelPopup,
initialCountdown: timeCountDown, initialCountdown: timeCountDown,
...@@ -40,7 +39,7 @@ Future<void> showPopupManagerScreen( ...@@ -40,7 +39,7 @@ Future<void> showPopupManagerScreen(
}, },
); );
}, },
transitionBuilder: (_, anim, __, child) { transitionBuilder: (_, anim, _, child) {
return Transform.scale(scale: 0.96 + (0.04 * anim.value), child: Opacity(opacity: anim.value, child: child)); return Transform.scale(scale: 0.96 + (0.04 * anim.value), child: Opacity(opacity: anim.value, child: child));
}, },
); );
......
import 'package:mypoint_flutter_app/core/network/restful_api_client_all_request.dart';
import 'package:mypoint_flutter_app/features/popup_manager/popup_manager_model.dart';
import '../../core/network/restful_api_viewmodel.dart';
class PopupManagerViewModel extends RestfulApiViewModel {
PopupManagerViewModel._();
static final PopupManagerViewModel instance = PopupManagerViewModel._();
final Set<String> _shownIds = {};
List<PopupManagerModel>? _popupData;
bool _loaded = false;
Future<void>? _loadingFuture;
Future<void> ensureLoaded() async {
if (_loaded) return;
if (_loadingFuture != null) {
return _loadingFuture;
}
_loadingFuture = _getPopupManagerDataInternal();
await _loadingFuture;
}
Future<void> _getPopupManagerDataInternal() async {
try {
final response = await client.getPopupManagerCommonScreen();
_popupData = response.data ?? [];
_loaded = true;
} catch (e) {
_popupData = [];
_loaded = true;
rethrow;
} finally {
_loadingFuture = null;
}
}
PopupManagerModel? getForScreen(String screenName) {
if (_popupData == null || _popupData!.isEmpty) return null;
final idx = _popupData!.indexWhere(
(e) => (e.screenToShow ?? '').trim().toUpperCase() == screenName.trim().toUpperCase(),
);
if (idx < 0) return null;
final found = _popupData![idx];
if (_shownIds.contains(found.id)) return null;
return found;
}
Future<void> markShownOnce(String popupId) async {
_shownIds.add(popupId);
}
Future<void> reset() async {
_shownIds.clear();
_popupData = [];
_loaded = false;
_loadingFuture = null;
}
}
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mypoint_flutter_app/screen/popup_manager/popup_manager_screen.dart'; import 'package:mypoint_flutter_app/features/popup_manager/popup_manager_screen.dart';
import '../../directional/directional_action_type.dart'; import '../../app/routing/directional_action_type.dart';
import 'popup_manager_viewmodel.dart'; import 'popup_manager_viewmodel.dart';
class PopupRunner { class PopupRunner {
......
import 'package:barcode_widget/barcode_widget.dart'; import 'package:barcode_widget/barcode_widget.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:mypoint_flutter_app/extensions/num_extension.dart'; import 'package:mypoint_flutter_app/core/services/deep_link_service.dart';
import 'package:mypoint_flutter_app/extensions/string_extension.dart'; import 'package:mypoint_flutter_app/core/utils/extensions/num_extension.dart';
import 'package:mypoint_flutter_app/preference/point/point_manager.dart'; import 'package:mypoint_flutter_app/shared/preferences/point/point_manager.dart';
import 'package:mypoint_flutter_app/screen/qr_code/qr_code_viewmodel.dart'; import 'package:mypoint_flutter_app/features/qr_code/qr_code_viewmodel.dart';
import 'package:mypoint_flutter_app/screen/qr_code/scan_code_screen.dart'; import 'package:mypoint_flutter_app/features/qr_code/scan_code_screen.dart';
import 'package:mypoint_flutter_app/widgets/custom_navigation_bar.dart'; import 'package:mypoint_flutter_app/shared/widgets/custom_navigation_bar.dart';
import 'package:mypoint_flutter_app/widgets/dashed_line.dart'; import 'package:mypoint_flutter_app/shared/widgets/dashed_line.dart';
import 'package:qr_flutter/qr_flutter.dart'; import 'package:qr_flutter/qr_flutter.dart';
import '../../base/base_screen.dart'; import '../../shared/widgets/base_view/base_screen.dart';
import '../../base/basic_state.dart'; import '../../shared/widgets/base_view/basic_state.dart';
import '../../preference/data_preference.dart'; import '../../shared/preferences/data_preference.dart';
class QRCodeScreen extends BaseScreen { class QRCodeScreen extends BaseScreen {
const QRCodeScreen({super.key}); const QRCodeScreen({super.key});
...@@ -43,7 +43,7 @@ class _QRCodeScreenState extends BaseState<QRCodeScreen> with BasicState { ...@@ -43,7 +43,7 @@ class _QRCodeScreenState extends BaseState<QRCodeScreen> with BasicState {
}; };
} }
_freshPoint() async { Future<void> _freshPoint() async {
_points.value = (await UserPointManager().fetchUserPoint()) ?? 0; _points.value = (await UserPointManager().fetchUserPoint()) ?? 0;
} }
...@@ -158,7 +158,7 @@ class _QRCodeScreenState extends BaseState<QRCodeScreen> with BasicState { ...@@ -158,7 +158,7 @@ class _QRCodeScreenState extends BaseState<QRCodeScreen> with BasicState {
); );
} }
_showAlertAndResumeScanCode(String message) { void _showAlertAndResumeScanCode(String message) {
showAlertError( showAlertError(
content: message, content: message,
showCloseButton: false, showCloseButton: false,
...@@ -172,18 +172,15 @@ class _QRCodeScreenState extends BaseState<QRCodeScreen> with BasicState { ...@@ -172,18 +172,15 @@ class _QRCodeScreenState extends BaseState<QRCodeScreen> with BasicState {
return ScanTabView( return ScanTabView(
controller: _scanCtl, controller: _scanCtl,
onCodeDetected: (code, format) async { onCodeDetected: (code, format) async {
final id = _viewModel.getResultCodeID(code) ?? ''; final bankId = _viewModel.getResultCodeID(code) ?? '';
// if (id.isEmpty) { if (bankId.isEmpty) {
// final url = code.toUri(); final directionSuccess = DeepLinkService().handleIncomingUri(code);
// if (url == null) { if (!directionSuccess) {
// _showAlertAndResumeScanCode('Mã QR không hợp lệ'); _showAlertAndResumeScanCode('Mã QR không hợp lệ. Vui lòng thử lại!');
// } else { }
// print('_buildScanQRCode $url'); return;
// _scanCtl.resume(); }
// } _viewModel.getDirectionFromId(bankId);
// return;
// }
_viewModel.getDirectionFromId(code);
}, },
); );
} }
......
import 'package:mypoint_flutter_app/configs/constants.dart'; import 'package:mypoint_flutter_app/app/config/constants.dart';
import 'package:mypoint_flutter_app/networking/restful_api_client_all_request.dart'; import 'package:mypoint_flutter_app/core/network/restful_api_client_all_request.dart';
import '../../networking/restful_api_viewmodel.dart'; import '../../core/network/restful_api_viewmodel.dart';
class QRPackageCodeValue { class QRPackageCodeValue {
final String type; final String type;
...@@ -13,13 +13,13 @@ class QRPackageCodeValue { ...@@ -13,13 +13,13 @@ class QRPackageCodeValue {
class QRCodeViewModel extends RestfulApiViewModel { class QRCodeViewModel extends RestfulApiViewModel {
void Function(String message)? onShowAlertError; void Function(String message)? onShowAlertError;
getDirectionFromId(String id) async { Future<void> getDirectionFromId(String id) async {
showLoading(); showLoading();
final response = await client.getDirectionOfflineBrand(id); final response = await client.getDirectionOfflineBrand(id);
final direction = response.data; final direction = response.data;
if (response.isSuccess && direction != null) { if (response.isSuccess && direction != null) {
hideLoading(); hideLoading();
final directionSuccess = await direction.begin(); final directionSuccess = direction.begin();
if (directionSuccess != true) { if (directionSuccess != true) {
onShowAlertError?.call(ErrorCodes.serverErrorMessage); onShowAlertError?.call(ErrorCodes.serverErrorMessage);
} else { } else {
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:mobile_scanner/mobile_scanner.dart'; import 'package:mobile_scanner/mobile_scanner.dart';
class ScanTabController { class ScanTabController {
...@@ -123,7 +122,7 @@ class _ScanTabViewState extends State<ScanTabView> { ...@@ -123,7 +122,7 @@ class _ScanTabViewState extends State<ScanTabView> {
child: Center( child: Center(
child: ValueListenableBuilder<MobileScannerState>( child: ValueListenableBuilder<MobileScannerState>(
valueListenable: _cam, valueListenable: _cam,
builder: (_, state, __) { builder: (_, state, _) {
final on = state.torchState == TorchState.on; final on = state.torchState == TorchState.on;
return InkWell( return InkWell(
onTap: () => _cam.toggleTorch(), onTap: () => _cam.toggleTorch(),
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../shared/widgets/back_button.dart';
import '../../configs/callbacks.dart';
import '../../widgets/back_button.dart';
class QuizCampaignHeader extends StatelessWidget { class QuizCampaignHeader extends StatelessWidget {
final int currentIndex; final int currentIndex;
......
import '../../widgets/alert/popup_data_model.dart'; import '../../shared/widgets/alert/popup_data_model.dart';
enum SurveyQuestionType { enum SurveyQuestionType {
textarea, textarea,
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment