Bladeren bron

Merge remote-tracking branch 'origin/dev' into dev

liukai 1 week geleden
bovenliggende
commit
0c0ce0ba33

+ 1 - 1
packages/cpt_community/lib/modules/community/community_vm.g.dart

@@ -6,7 +6,7 @@ part of 'community_vm.dart';
 // RiverpodGenerator
 // **************************************************************************
 
-String _$communityVmHash() => r'15dfd5c8d9c3d01bab9fc789c21ebe61b292c6f3';
+String _$communityVmHash() => r'a967355f8abbdda59a2001d7766b42f8d7ebb6af';
 
 /// See also [CommunityVm].
 @ProviderFor(CommunityVm)

+ 278 - 5
packages/cpt_community/lib/modules/garagesale/garagesale_post/garagesale_post_page.dart

@@ -1,10 +1,23 @@
 import 'package:cpt_community/router/page/community_page_router.dart';
+import 'package:cs_resources/generated/l10n.dart';
 import 'package:cs_resources/theme/app_colors_theme.dart';
 import 'package:flutter/material.dart';
 import 'package:auto_route/auto_route.dart';
+import 'package:flutter_hooks/flutter_hooks.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:plugin_platform/engine/image/image_nine_grid.dart';
 import 'package:router/ext/auto_router_extensions.dart';
+import 'package:shared/utils/color_utils.dart';
 import 'package:widgets/my_appbar.dart';
+import 'package:widgets/my_button.dart';
+import 'package:widgets/my_text_field.dart';
+import 'package:widgets/widget_export.dart';
+import 'package:widgets/shatter/picker_container.dart';
+import 'package:widgets/shatter/form_require_text.dart';
+import 'package:widgets/ext/ex_widget.dart';
+
+
+import 'garagesale_post_vm.dart';
 
 @RoutePage()
 class GaragesalePostPage extends HookConsumerWidget {
@@ -21,17 +34,277 @@ class GaragesalePostPage extends HookConsumerWidget {
 
   @override
   Widget build(BuildContext context, WidgetRef ref) {
-    // final viewModel = ref.watch(newsfeedPostVmProvider.notifier);
+    final vm = ref.read(garagesalePostVmProvider.notifier);
 
     return Scaffold(
       appBar: MyAppBar.appBar(
         context,
-        "Garage Sale Post",
+        "Garagesale Post",
         backgroundColor: context.appColors.whiteBG,
       ),
-      backgroundColor: context.appColors.backgroundDefault,
-      body: Center(
-        child: Text("GaragesalePost"),
+      backgroundColor: Colors.white,
+      body: Column(
+          children: [
+            Expanded(
+                child: SingleChildScrollView(
+                    scrollDirection: Axis.vertical,
+                    physics: const BouncingScrollPhysics(),
+                    clipBehavior: Clip.none,
+                    child: Column(
+                        children: [
+                          // sale 选择
+                          Container(
+                            margin: const EdgeInsets.only(left:15, right:15, top: 5),
+                            child: _buildSaleInputSelectCmp(context, ref),
+                          ),
+                          // category 选择
+                          Container(
+                            margin: const EdgeInsets.only(left:15, right:15, top: 5),
+                            child: _buildCategoryInputSelectCmp(context, ref),
+                          ),
+                          // title
+                          Container(
+                            margin: const EdgeInsets.only(left:15, right:15, top: 20),
+                            child: Column(
+                              crossAxisAlignment: CrossAxisAlignment.start,
+                              children: [
+                                FormRequireText(
+                                  text: "Title",
+                                  textColor: context.appColors.textBlack,
+                                  fontSize: 17,
+                                ).marginOnly(bottom: 14.5),
+                                _buildInputLayout(context, ref, 'title'),
+                              ]
+                            )
+                          ),
+                          // description
+                          Container(
+                            margin: const EdgeInsets.only(left:15, right:15,top: 20),
+                            child: Column(
+                                crossAxisAlignment: CrossAxisAlignment.start,
+                                children: [
+                                  FormRequireText(
+                                    text: "Description",
+                                    textColor: context.appColors.textBlack,
+                                    fontSize: 17,
+                                  ).marginOnly(bottom: 14.5),
+                                  _buildTextArea(context, ref, 'description'),
+                                ]
+                            )
+
+                          ),
+                          Container(
+                            width: double.infinity,
+                            height: 200,
+                            color: Colors.white,
+                            margin: const EdgeInsets.only(left:15, right:15, top: 10, bottom: 20),
+                            // 选择图片上传 控件
+                            child: _buildImageSelectCmp(context, ref,vm),
+                          ),
+                        ]
+                    )
+                )
+            ),
+            // 底部 按钮
+            Container(
+              child: Row(
+                  children: [
+                    Expanded(
+                      child: MyButton(
+                        text: "Add Card",
+                        radius: 0,
+                        minHeight: 50,
+                        backgroundColor: context.appColors.textPrimary,
+                        textColor: Colors.white,
+                        fontWeight: FontWeight.w500,
+                        fontSize: 16,
+                        onPressed: (){
+                          // Navigator.pop(context);
+                          vm.submitGaragesalePost();
+                        },
+                      ),
+                    ),
+                  ]
+              ),
+            )
+          ]
+      ),
+    );
+  }
+
+  /// textarea
+  Widget _buildTextArea(BuildContext context, WidgetRef ref, key){
+    final state = ref.watch(garagesalePostVmProvider);
+    final vm = ref.read(garagesalePostVmProvider.notifier);
+
+    return Container(
+      width: double.infinity,
+      height: 200,
+      padding: const EdgeInsets.all(15),
+      decoration: BoxDecoration(
+        color: context.appColors.authFiledBG,
+        borderRadius: BorderRadius.circular(5),
+        // boxShadow: [
+        //   BoxShadow(
+        //     color: Colors.grey.withOpacity(0.5),
+        //     spreadRadius: 1,
+        //     blurRadius: 1,
+        //     offset: const Offset(0, 3), // changes position of shadow
+        //   ),
+        // ]
+      ),
+      child: _buildTextAreaLayout(
+        context,
+        ref,
+        key,
+      ),
+    );
+  }
+  /// 选择图片上传组件
+  Widget _buildImageSelectCmp(BuildContext context, WidgetRef ref,vm,){
+    final state = ref.watch(garagesalePostVmProvider);
+    return ImageNineGrid(
+      isSelectEnable: true,
+      maxImages: 10,
+      spacing: 10,
+      aspectRatio: 108 / 80,
+      initialImages: state.imgList,
+      onImagesChanged: (list) {
+        vm.setImgList(list);
+      },
+    );
+  }
+
+  /// 多行输入框
+  Widget _buildTextAreaLayout(BuildContext context, ref, key){
+    final state = ref.watch(garagesalePostVmProvider);
+    final vm = ref.read(garagesalePostVmProvider.notifier);
+    final noteCount = useState(0);
+
+    return Stack(
+        children: [
+          TextField(
+            cursorColor: context.appColors.authFiledText,
+            cursorWidth: 1.5,
+            autofocus: false,
+            enabled: true,
+            maxLines: null,
+            focusNode: state.formData[key]!['focusNode'],
+            controller: state.formData[key]!['controller'],
+            decoration: InputDecoration(
+              isDense: true,
+              isCollapsed: true,
+              border: InputBorder.none,
+              hintText: state.formData[key]!['hintText'],
+              hintStyle: TextStyle(
+                color: context.appColors.authFiledHint,
+                fontSize: 16.0,
+                fontWeight: FontWeight.w500,
+              ),
+            ),
+            style: TextStyle(
+              color: context.appColors.authFiledText,
+              fontSize: 16.0,
+              fontWeight: FontWeight.w500,
+            ),
+            textInputAction: TextInputAction.done,
+            onSubmitted: (value) {
+              FocusScope.of(context).unfocus();
+            },
+            expands: true,
+            onChanged: (text) {
+              // 当文本改变时,更新字符数量
+              noteCount.value = text.length;
+            },
+          ),
+          Positioned(
+            bottom: 0.0,
+            right: 0.0,
+            child: Text(
+              S.current.characters(noteCount.value),
+              style: TextStyle(
+                color: context.appColors.textBlack,
+                fontSize: 15.0,
+              ),
+            ),
+          ),
+        ]
+    );
+  }
+
+  /// sale 选择
+  Widget _buildSaleInputSelectCmp(BuildContext context, WidgetRef ref, ){
+    final vm = ref.read(garagesalePostVmProvider.notifier);
+    final state = ref.watch(garagesalePostVmProvider);
+    return PickerContainer(
+      content: state.saleSelectedOption ?? "",
+      hint: S.current.choose_category,
+      margin: const EdgeInsets.only(top: 16),
+      onClick: vm.pickSaleCallback,
+    );
+  }
+
+  /// category 选择
+  Widget _buildCategoryInputSelectCmp(BuildContext context, WidgetRef ref, ){
+    final vm = ref.read(garagesalePostVmProvider.notifier);
+    final state = ref.watch(garagesalePostVmProvider);
+    return PickerContainer(
+      content: state.categorySelectedOption ?? "",
+      hint: S.current.choose_category,
+      margin: const EdgeInsets.only(top: 16),
+      onClick: vm.pickCategoryCallback,
+    );
+  }
+
+  /// 单行输入框
+  Widget _buildInputLayout(
+      BuildContext context,
+      WidgetRef ref,
+      String key,
+      {
+        double height = 48,
+        double maxLines = 1,
+        double marginTop = 0,
+        bool? showRightIcon = false, //是否展示右侧的布局
+        Widget? rightWidget, //右侧的布局
+        TextInputType textInputType = TextInputType.text,
+        String? errorText,
+        bool obscureText = false,
+        TextInputAction textInputAction = TextInputAction.done,
+        Function? onSubmit,
+      }) {
+    final state = ref.watch(garagesalePostVmProvider);
+    return IgnoreKeyboardDismiss(
+      child: MyTextField(
+        key,
+        fillBackgroundColor: context.appColors.authFiledBG,
+        state.formData[key]!['value'],
+        hintText: state.formData[key]!['hintText'],
+        hintStyle: TextStyle(
+          color: context.appColors.authFiledHint,
+          fontSize: 16.0,
+          fontWeight: FontWeight.w500,
+        ),
+        controller: state.formData[key]!['controller'],
+        focusNode: state.formData[key]!['focusNode'],
+        margin: EdgeInsets.only(top: marginTop),
+        padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 3),
+        showDivider: false,
+        height: height,
+        style: TextStyle(
+          color: context.appColors.authFiledText,
+          fontSize: 16.0,
+          fontWeight: FontWeight.w500,
+        ),
+        inputType: textInputType,
+        textInputAction: textInputAction,
+        onSubmit: onSubmit,
+        cursorColor: context.appColors.authFiledText,
+        obscureText: obscureText,
+        errorText: errorText,
+        showLeftIcon: true,
+        showRightIcon: showRightIcon,
+        rightWidget: rightWidget,
       ),
     );
   }

+ 84 - 0
packages/cpt_community/lib/modules/garagesale/garagesale_post/garagesale_post_state.dart

@@ -0,0 +1,84 @@
+import 'package:flutter/cupertino.dart';
+
+class GaragesalePostPageState {
+  //表单的错误信息展示
+  String? fieldErrorText;
+  //表单的校验与数据
+  final Map<String, Map<String, dynamic>> formData;
+
+  // 标题
+  final String? title;
+  // 描述
+  final String? description;
+
+  //sale 类型选项
+  final List<String> saleOptionsList = ["SALE1", "SALE2", "SALE3", "SALE4"];
+  String? saleSelectedOption;
+
+  //category 类型选项
+  final List<String> categoryOptionsList = ["Category1", "Category2", "Category3", "Category4"];
+  String? categorySelectedOption;
+
+  // 选择的图片
+  final List<String> imgList;
+
+  GaragesalePostPageState({
+    formData,
+    required this.imgList,
+    this.fieldErrorText,
+    this.title,
+    this.description,
+    this.saleSelectedOption,
+    this.categorySelectedOption,
+  }): formData = formData ?? {
+    'sale': {
+      'value': '',
+      'controller': TextEditingController(),
+      'hintText': '',
+      'focusNode': FocusNode(),
+      'obsecure': false,
+    },
+    'category': {
+      'value': '',
+      'controller': TextEditingController(),
+      'hintText': '',
+      'focusNode': FocusNode(),
+      'obsecure': false,
+    },
+    'title': {
+      'value': '',
+      'controller': TextEditingController(),
+      'hintText': '',
+      'focusNode': FocusNode(),
+      'obsecure': false,
+    },
+    'description': {
+      'value': '',
+      'controller': TextEditingController(),
+      'hintText': '',
+      'focusNode': FocusNode(),
+      'obsecure': false,
+    },
+  };
+
+  GaragesalePostPageState copyWith({
+    Map<String, Map<String, dynamic>>? formData,
+    List<String>? imgList,
+    String? fieldErrorText,
+    String? saleSelectedOption,
+    String? categorySelectedOption,
+    String? title,
+    String? description,
+  }) {
+    return GaragesalePostPageState(
+      formData: formData ?? this.formData,
+      title: title ?? this.title,
+      description: description ?? this.description,
+      imgList: imgList ?? this.imgList,
+      saleSelectedOption: saleSelectedOption ?? this.saleSelectedOption,
+      categorySelectedOption: categorySelectedOption ?? this.categorySelectedOption,
+      fieldErrorText: fieldErrorText ?? this.fieldErrorText,
+    );
+  }
+
+}

+ 205 - 0
packages/cpt_community/lib/modules/garagesale/garagesale_post/garagesale_post_vm.dart

@@ -0,0 +1,205 @@
+
+import 'package:cs_resources/generated/assets.dart';
+import 'package:flutter/material.dart';
+import 'package:plugin_platform/engine/toast/toast_engine.dart';
+import 'package:riverpod_annotation/riverpod_annotation.dart';
+import 'package:router/path/router_path.dart';
+import 'package:shared/utils/color_utils.dart';
+import 'package:shared/utils/log_utils.dart';
+import 'package:auto_route/auto_route.dart';
+import 'package:shared/utils/util.dart';
+import 'package:widgets/picker/option_pick_util.dart';
+
+
+import 'garagesale_post_page.dart';
+import 'garagesale_post_state.dart';
+
+part 'garagesale_post_vm.g.dart';
+
+@riverpod
+class GaragesalePostVm extends _$GaragesalePostVm {
+
+  GaragesalePostPageState initState() {
+    return GaragesalePostPageState(
+      fieldErrorText: '',
+      formData: {
+        'sale': {
+          'value': '',
+          'controller': TextEditingController(),
+          'hintText': '',
+          'focusNode': FocusNode(),
+          'obsecure': false,
+        },
+        'category': {
+          'value': '',
+          'controller': TextEditingController(),
+          'hintText': '',
+          'focusNode': FocusNode(),
+          'obsecure': false,
+        },
+        'title': {
+          'value': '',
+          'controller': TextEditingController(),
+          'hintText': '',
+          'focusNode': FocusNode(),
+          'obsecure': false,
+        },
+        'description': {
+          'value': '',
+          'controller': TextEditingController(),
+          'hintText': '',
+          'focusNode': FocusNode(),
+          'obsecure': false,
+        },
+      },
+      imgList: [],
+    );
+  }
+
+  @override
+  GaragesalePostPageState build() {
+    // 初始化状态
+    GaragesalePostPageState state = initState();
+
+    // 当前渲染完成后执行一次
+    WidgetsBinding.instance!.addPostFrameCallback((_) {
+      // 获取焦点
+      initListener(state);
+      ref.onDispose(() {
+        onDispose(state);
+      });
+    });
+
+    return state;
+  }
+
+  //catogery 选择选项
+  void pickCategoryCallback() {
+    _dismissKeyboard(keyStr: 'title');
+    _dismissKeyboard(keyStr: 'description');
+
+    OptionPickerUtil.showCupertinoOptionPicker(
+      items: state.categoryOptionsList,
+      initialSelectIndex: 0,
+      onPickerChanged: (_, index) {
+        state = state.copyWith(categorySelectedOption: state.categoryOptionsList[index]);
+      },
+    );
+  }
+
+  //sale 选择选项
+  void pickSaleCallback() {
+    _dismissKeyboard(keyStr: 'title');
+    _dismissKeyboard(keyStr: 'description');
+
+    OptionPickerUtil.showCupertinoOptionPicker(
+      items: state.saleOptionsList,
+      initialSelectIndex: 0,
+      onPickerChanged: (_, index) {
+        state = state.copyWith(saleSelectedOption: state.saleOptionsList[index]);
+      },
+    );
+  }
+
+  // 获取聚焦的node
+  FocusNode getFocusNode(Map<String, dynamic> formData, String keyStr) {
+    return formData![keyStr]!['focusNode'];
+  }
+
+  // 取消表单聚焦状态
+  void _dismissKeyboard({String? keyStr}) {
+    if(keyStr!.isNotEmpty){
+      Log.d("FeedbackCreateViewModel 取消单个表单 $keyStr 聚焦状态");
+      final FocusNode sigleItemFocusNode = state.formData[keyStr]!['focusNode'];
+      sigleItemFocusNode!.unfocus();
+    }else {
+      Log.d("FeedbackCreateViewModel 取消所有表单聚焦状态");
+      // 遍历 formData 的所有表单 然后逐一取消聚焦
+      Map<String, dynamic> formData = state.formData;
+      for(String key in formData.keys){
+        final FocusNode sigleItemFocusNode = formData[key]['focusNode'];
+        sigleItemFocusNode!.unfocus();
+      }
+    }
+  }
+
+  // 获取表单的值
+  dynamic _getFormFieldValue({List<String>? keys, String? keyStr}) {
+    if(keyStr!.isEmpty){
+      if(keys!.isNotEmpty){
+        // 遍历keys获取指定多个 keys 的表单value
+        Map<String, dynamic> resultValueMap = {};
+        for (String itemStr in keys!) {
+          TextEditingController sigleItemController = state.formData[itemStr]!['controller'];
+          resultValueMap[itemStr] = sigleItemController.text;
+        }
+        return resultValueMap;
+      }
+    }else if(keyStr!.isNotEmpty){
+      // 获取单个表单的value
+      final TextEditingController sigleItemController = state.formData[keyStr]!['controller'];
+      return sigleItemController.text;
+    }
+  }
+
+  ///提交反馈
+  void submitGaragesalePost() {
+    Log.d("GaragesalePostPageState 提交表单");
+    state = state.copyWith(fieldErrorText: null);
+
+    _dismissKeyboard();
+
+    // 获取表单的值
+    String  saleValue= _getFormFieldValue(keyStr: 'sale');
+    String  categoryValue= _getFormFieldValue(keyStr: 'category');
+    String  titleValue= _getFormFieldValue(keyStr: 'title');
+    String  descriptionValue= _getFormFieldValue(keyStr: 'description');
+
+    Log.d('当前待提交的 sale:$saleValue category:$categoryValue title:$titleValue description:$descriptionValue  imgList:${state.imgList}');
+
+
+    if (Utils.isEmpty(saleValue)) {
+      state = state.copyWith(fieldErrorText: "Title cannot be empty!");
+      return;
+    }
+
+    if (Utils.isEmpty(categoryValue)) {
+      state = state.copyWith(fieldErrorText: "category cannot be empty!");
+      return;
+    }
+
+    //去成功页面
+    // FeedbackCreateSuccessPage.startInstance();
+    // 返回上一页
+  }
+
+  //选中图片
+  void setImgList(List<String> list) {
+    state = state.copyWith(imgList: list);
+  }
+
+  //初始化监听
+  void initListener(GaragesalePostPageState initState) {
+
+    // 获取表单的焦点节点
+    final FocusNode focusNode = getFocusNode(state.formData, 'mind');
+
+    focusNode.addListener(() {
+      // 获取焦点的时候清空错误文本
+      if (focusNode.hasFocus) {
+        state = state.copyWith(fieldErrorText: null);
+      }
+    });
+  }
+
+  //销毁资源
+  void onDispose(GaragesalePostPageState initState) {
+    // 获取表单的焦点节点
+    final FocusNode focusNode = getFocusNode(state.formData, 'mind');
+    focusNode.dispose();
+
+    Log.d("GaragesalePostPageState 销毁 onDispose");
+  }
+
+
+}

+ 26 - 0
packages/cpt_community/lib/modules/garagesale/garagesale_post/garagesale_post_vm.g.dart

@@ -0,0 +1,26 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'garagesale_post_vm.dart';
+
+// **************************************************************************
+// RiverpodGenerator
+// **************************************************************************
+
+String _$garagesalePostVmHash() => r'71539a609fdc9da73ac1d354ea881f0ad43eea22';
+
+/// See also [GaragesalePostVm].
+@ProviderFor(GaragesalePostVm)
+final garagesalePostVmProvider = AutoDisposeNotifierProvider<GaragesalePostVm,
+    GaragesalePostPageState>.internal(
+  GaragesalePostVm.new,
+  name: r'garagesalePostVmProvider',
+  debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
+      ? null
+      : _$garagesalePostVmHash,
+  dependencies: null,
+  allTransitiveDependencies: null,
+);
+
+typedef _$GaragesalePostVm = AutoDisposeNotifier<GaragesalePostPageState>;
+// ignore_for_file: type=lint
+// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

+ 60 - 37
packages/cpt_community/lib/modules/newsfeed/newsfeed_page.dart

@@ -13,6 +13,7 @@ import 'package:widgets/ext/ex_widget.dart';
 import 'package:widgets/my_load_image.dart';
 import 'package:widgets/my_text_view.dart';
 import 'package:widgets/widget_export.dart';
+import 'package:widgets/my_button.dart';
 
 import '../../components/newfeed_card_header.dart';
 import '../../router/page/community_page_router.dart';
@@ -90,49 +91,71 @@ class NewsfeedPage extends HookConsumerWidget {
   Widget _buildNewsItem(BuildContext context, WidgetRef ref, item, vm){
     return Container(
       width: double.infinity,
-      // padding: const EdgeInsets.only(left: 15, right: 15,top: 14,bottom: 14),
-        color: Colors.yellow,
-      child: Container(
-        margin: const EdgeInsets.only(left: 15, right: 15,top: 14,bottom: 14),
-        color: Colors.white,
-        padding: const EdgeInsets.only(left: 15, right: 15,top: 17,bottom: 17),
-        height: 280,
-        child: Column(
-          mainAxisAlignment: MainAxisAlignment.center,
-          crossAxisAlignment: CrossAxisAlignment.start,
-          children: [
-            // 卡片头部(头像 标题 时间)
-            NewsFeedCardHeader(
-              title: item['title'],
-              avator: item['avator'],
-              time: item['time'],
+      //   color: Colors.yellow,
+      child: Stack(
+        children: [
+          Container(
+            margin: const EdgeInsets.only(left: 15, right: 15,top: 14,bottom: 14),
+            color: Colors.white,
+            padding: const EdgeInsets.only(left: 15, right: 15,top: 17,bottom: 17),
+            height: 280,
+            child: Column(
+                mainAxisAlignment: MainAxisAlignment.center,
+                crossAxisAlignment: CrossAxisAlignment.start,
+                children: [
+                  // 卡片头部(头像 标题 时间)
+                  NewsFeedCardHeader(
+                    title: item['title'],
+                    avator: item['avator'],
+                    time: item['time'],
+                  ),
+                  const SizedBox(height: 15),
+                  // 卡片中间 (文字和图片)
+                  Expanded(
+                    child: NewsFeedCardContent(
+                      content: item['content'],
+                      imageUrls: item['imageUrls'],
+                    ),
+                  ),
+                  const SizedBox(height: 26),
+                  // // 卡片底部 (点赞 评论 分享)
+                  NewsFeedCardFooter(
+                    isLike: item['isLike'],
+                  ),
+                ]
             ),
-            const SizedBox(height: 15),
-            // 卡片中间 (文字和图片)
-            Expanded(
-              child: NewsFeedCardContent(
-                content: item['content'],
-                imageUrls: item['imageUrls'],
+          ),
+          // 右上角 关注/取消关注 按钮
+          Positioned(
+            right: 20,
+            top: 20,
+            child: Container(
+              width: 100,
+              alignment: Alignment.center,
+              decoration: BoxDecoration(
+                color: item['isFollow'] ? ColorUtils.string2Color('#FFEBEE') : ColorUtils.string2Color('#F2F3F6'),
+                borderRadius: BorderRadius.circular(15),
               ),
-            ),
-            const SizedBox(height: 26),
-            // // 卡片底部 (点赞 评论 分享)
-            NewsFeedCardFooter(
-              isLike: item['isLike'],
-            ),
-          ]
-        ),
-      )
+              child: MyButton(
+                text: item['isFollow'] ? 'Following' : 'Follow',
+                textColor: item['isFollow'] ? ColorUtils.string2Color('#FF0000') : ColorUtils.string2Color('#000000'),
+                backgroundColor: item['isFollow'] ? ColorUtils.string2Color('#F2F3F6') : ColorUtils.string2Color('#FFEBEE'),
+                radius: 0,
+                minHeight: 50,
+                fontWeight: FontWeight.w500,
+                fontSize: 14,
+                onPressed: (){
+                  // Navigator.pop(context);
+                },
+              ),
+            )
+          )
+        ],
+      ),
     );
   }
 
   Widget _buildNesFeedList(BuildContext context, WidgetRef ref, vm){
-    // return Container(
-    //   height: 100,
-    //   color: Colors.blue,
-    // );
-    // List items = List.generate(20, (index) => "Item $index");
-    // List items = _vm.state.list.fromMap();
     final itemList = vm.state.list?? [];
     if(itemList.isEmpty){
       return const Center(child: Text('No Data'));

+ 155 - 61
packages/cpt_community/lib/modules/newsfeed/newsfeed_post/newsfeed_post_page.dart

@@ -1,9 +1,13 @@
 import 'package:cpt_community/router/page/community_page_router.dart';
+import 'package:cs_resources/generated/l10n.dart';
 import 'package:cs_resources/theme/app_colors_theme.dart';
 import 'package:flutter/material.dart';
 import 'package:auto_route/auto_route.dart';
+import 'package:flutter_hooks/flutter_hooks.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:plugin_platform/engine/image/image_nine_grid.dart';
 import 'package:router/ext/auto_router_extensions.dart';
+import 'package:shared/utils/color_utils.dart';
 import 'package:widgets/ext/ex_widget.dart';
 import 'package:widgets/my_appbar.dart';
 import 'package:widgets/my_text_field.dart';
@@ -27,8 +31,7 @@ class NewsfeedPostPage extends HookConsumerWidget {
 
   @override
   Widget build(BuildContext context, WidgetRef ref) {
-    // final viewModel = ref.watch(newsfeedPostVmProvider.notifier);
-    final state = ref.watch(newsfeedPostVmProvider);
+    final vm = ref.read(newsfeedPostVmProvider.notifier);
 
     return Scaffold(
       appBar: MyAppBar.appBar(
@@ -36,79 +39,170 @@ class NewsfeedPostPage extends HookConsumerWidget {
         "Create Post",
         backgroundColor: context.appColors.whiteBG,
       ),
-      backgroundColor: context.appColors.backgroundDefault,
+      backgroundColor: ColorUtils.string2Color("#F2F3F6"),
       body: Column(
-        children: [
-          Expanded(
-            child: SingleChildScrollView(
-              scrollDirection: Axis.vertical,
-              physics: const BouncingScrollPhysics(),
-              clipBehavior: Clip.none,
-              child: Column(
-                children: [
-                  Container(
-                    width: double.infinity,
-                    height: 200,
-                    decoration: BoxDecoration(
-                      border: Border.all(
-                        color: Colors.grey,
-                        width: 1
+          children: [
+            Expanded(
+                child: SingleChildScrollView(
+                    scrollDirection: Axis.vertical,
+                    physics: const BouncingScrollPhysics(),
+                    clipBehavior: Clip.none,
+                    child: Column(
+                        children: [
+                          // mind textearea
+                          Container(
+                            margin: const EdgeInsets.only(left:15, right:15,top: 20),
+                            child: _buildTextArea(context, ref),
+                          ),
+                          Container(
+                            width: double.infinity,
+                            height: 200,
+                            color: Colors.white,
+                            margin: const EdgeInsets.only(left:15, right:15, top: 20, bottom: 20),
+                            padding: const EdgeInsets.all(15),
+                            // 选择图片上传 控件
+                            child: _buildImageSelectCmp(context, ref,vm),
+                          ),
+                        ]
+                    )
+                )
+            ),
+            // 底部 按钮
+            Container(
+              child: Row(
+                  children: [
+                    Expanded(
+                      child: MyButton(
+                        text: "Add Card",
+                        radius: 0,
+                        minHeight: 50,
+                        backgroundColor: context.appColors.textPrimary,
+                        textColor: Colors.white,
+                        fontWeight: FontWeight.w500,
+                        fontSize: 16,
+                        onPressed: (){
+                          // Navigator.pop(context);
+                        },
                       ),
-                      borderRadius: BorderRadius.circular(10),
-                      boxShadow: [
-                        BoxShadow(
-                          color: Colors.grey.withOpacity(0.5),
-                          spreadRadius: 1,
-                          blurRadius: 1,
-                          offset: const Offset(0, 3), // changes position of shadow
-                        ),
-                      ]
                     ),
-                    child: _buildInputLayout(
-                      context,
-                      state,
-                      'mind'
-                    ),
-                  ).marginOnly(left:15, right:15, top: 20, bottom: 20),
-                  Container(
-                    width: double.infinity,
-                    height: 200,
-                    color: Colors.grey,
-                  ),
-                ]
-              )
+                  ]
+              ),
             )
+          ]
+      ),
+    );
+  }
+
+  /// textarea
+  Widget _buildTextArea(BuildContext context, WidgetRef ref){
+
+    final state = ref.watch(newsfeedPostVmProvider);
+    final vm = ref.read(newsfeedPostVmProvider.notifier);
+
+    return Container(
+      width: double.infinity,
+      height: 200,
+      padding: const EdgeInsets.all(15),
+      decoration: BoxDecoration(
+          color: Colors.white,
+          borderRadius: BorderRadius.circular(5),
+          // boxShadow: [
+          //   BoxShadow(
+          //     color: Colors.grey.withOpacity(0.5),
+          //     spreadRadius: 1,
+          //     blurRadius: 1,
+          //     offset: const Offset(0, 3), // changes position of shadow
+          //   ),
+          // ]
+      ),
+      child: _buildTextAreaLayout(
+          context,
+          ref,
+          'mind',
+      ),
+    );
+  }
+  /// 选择图片上传组件
+  Widget _buildImageSelectCmp(BuildContext context, WidgetRef ref,vm,){
+    final state = ref.watch(newsfeedPostVmProvider);
+    return ImageNineGrid(
+      isSelectEnable: true,
+      maxImages: 10,
+      spacing: 10,
+      aspectRatio: 108 / 80,
+      initialImages: state.imgList,
+      onImagesChanged: (list) {
+        vm.setImgList(list);
+      },
+    );
+  }
+
+  /// 多行输入框
+  Widget _buildTextAreaLayout(BuildContext context, ref, key){
+    final state = ref.watch(newsfeedPostVmProvider);
+    final vm = ref.read(newsfeedPostVmProvider.notifier);
+    final noteCount = useState(0);
+
+    return Stack(
+      children: [
+        TextField(
+          cursorColor: context.appColors.authFiledText,
+          cursorWidth: 1.5,
+          autofocus: false,
+          enabled: true,
+          maxLines: null,
+          focusNode: state.formData[key]!['focusNode'],
+          controller: state.formData[key]!['controller'],
+          decoration: InputDecoration(
+            isDense: true,
+            isCollapsed: true,
+            border: InputBorder.none,
+            hintText: state.formData[key]!['hintText'],
+            hintStyle: TextStyle(
+              color: context.appColors.authFiledHint,
+              fontSize: 16.0,
+              fontWeight: FontWeight.w500,
+            ),
           ),
-          Container(
-            child: Row(
-                children: [
-                  Expanded(
-                    child: MyButton(
-                      text: "Add Card",
-                      backgroundColor: context.appColors.textPrimary,
-                      textColor: Colors.white,
-                      fontWeight: FontWeight.w500,
-                      fontSize: 16,
-                      onPressed: (){
-                        // Navigator.pop(context);
-                      },
-                    ),
-                  ),
-                ]
+          style: TextStyle(
+            color: context.appColors.authFiledText,
+            fontSize: 16.0,
+            fontWeight: FontWeight.w500,
+          ),
+          textInputAction: TextInputAction.done,
+          onSubmitted: (value) {
+            FocusScope.of(context).unfocus();
+          },
+          expands: true,
+          onChanged: (text) {
+            // 当文本改变时,更新字符数量
+            noteCount.value = text.length;
+          },
+        ),
+        Positioned(
+          bottom: 0.0,
+          right: 0.0,
+          child: Text(
+            S.current.characters(noteCount.value),
+            style: TextStyle(
+              color: context.appColors.textBlack,
+              fontSize: 15.0,
             ),
-          )
-        ]
-      ),
+          ),
+        ),
+      ]
     );
   }
 
 
-  /// 输入框
+  /// 单行输入框
   Widget _buildInputLayout(
       BuildContext context,
       state,
       String key,
       {
+        double height = 48,
+        double maxLines = 1,
         double marginTop = 0,
         bool? showRightIcon = false, //是否展示右侧的布局
         Widget? rightWidget, //右侧的布局
@@ -134,7 +228,7 @@ class NewsfeedPostPage extends HookConsumerWidget {
         margin: EdgeInsets.only(top: marginTop),
         padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 3),
         showDivider: false,
-        height: 44,
+        height: height,
         style: TextStyle(
           color: context.appColors.authFiledText,
           fontSize: 16.0,

+ 33 - 3
packages/cpt_community/lib/modules/newsfeed/newsfeed_post/newsfeed_post_page_state.dart

@@ -1,8 +1,38 @@
+import 'package:flutter/cupertino.dart';
+
 class NewsfeedPostPageState {
   //表单的校验与数据
   final Map<String, Map<String, dynamic>> formData;
+  // 选择的图片
+  final List<String> imgList;
+
+  //表单的错误信息展示
+  String? mindFieldErrorText;
+
+  NewsfeedPostPageState({
+    formData,
+    required this.imgList,
+    this.mindFieldErrorText,
+  }): formData = formData ?? {
+    'mind': {
+      'value': '',
+      'controller': TextEditingController(),
+      'hintText': 'What\'s on your mind?',
+      'focusNode': FocusNode(),
+      'obsecure': false,
+    }
+  };
+
+  NewsfeedPostPageState copyWith({
+    Map<String, Map<String, dynamic>>? formData,
+    List<String>? imgList,
+    String? mindFieldErrorText,
+  }) {
+    return NewsfeedPostPageState(
+      formData: formData ?? this.formData,
+      imgList: imgList ?? this.imgList,
+      mindFieldErrorText: mindFieldErrorText ?? this.mindFieldErrorText,
+    );
+  }
 
-  const NewsfeedPostPageState({
-    required this.formData,
-  });
 }

+ 127 - 16
packages/cpt_community/lib/modules/newsfeed/newsfeed_post/newsfeed_post_vm.dart

@@ -7,6 +7,7 @@ import 'package:router/path/router_path.dart';
 import 'package:shared/utils/color_utils.dart';
 import 'package:shared/utils/log_utils.dart';
 import 'package:auto_route/auto_route.dart';
+import 'package:shared/utils/util.dart';
 
 
 import 'newsfeed_post_page_state.dart';
@@ -18,30 +19,140 @@ class NewsfeedPostVm extends _$NewsfeedPostVm {
 
   NewsfeedPostPageState initState() {
     return NewsfeedPostPageState(
-      formData: {
-        'mind': {
-          'value': '',
-          'controller': TextEditingController(),
-          'hintText': 'What\'s on your mind?',
-          'focusNode': FocusNode(),
-          'obsecure': false,
+        mindFieldErrorText: '',
+        formData: {
+          'mind': {
+            'value': '',
+            'controller': TextEditingController(),
+            'hintText': 'What\'s on your mind?',
+            'focusNode': FocusNode(),
+            'obsecure': false,
+          }
         },
-        'images': {
-          'value': '',
-          'controller': TextEditingController(),
-          'hintText': '',
-          'focusNode': FocusNode(),
-          'obsecure': false,
-        },
-      },
+        imgList: [],
     );
   }
+
   @override
-  NewsfeedPostPageState build(){
+  NewsfeedPostPageState build() {
     // 初始化状态
     NewsfeedPostPageState state = initState();
+
+    // 当前渲染完成后执行一次
+    WidgetsBinding.instance!.addPostFrameCallback((_) {
+      // 获取焦点
+      initListener(state);
+      ref.onDispose(() {
+        onDispose(state);
+      });
+    });
+
     return state;
   }
 
+  // //选择选项
+  // void pickCategory() {
+  //   _dismissKeyboard();
+  //
+  //   OptionPickerUtil.showCupertinoOptionPicker(
+  //     items: state.optionList,
+  //     initialSelectIndex: 0,
+  //     onPickerChanged: (_, index) {
+  //       state = state.copyWith(selectedOption: state.optionList[index]);
+  //     },
+  //   );
+  // }
+
+  // 获取聚焦的node
+  FocusNode getFocusNode(Map<String, dynamic> formData, String keyStr) {
+    return formData![keyStr]!['focusNode'];
+  }
+
+  // 取消表单聚焦状态
+  void _dismissKeyboard({String? keyStr}) {
+    if(keyStr!.isNotEmpty){
+      Log.d("FeedbackCreateViewModel 取消单个表单 $keyStr 聚焦状态");
+      final FocusNode sigleItemFocusNode = state.formData[keyStr]!['focusNode'];
+      sigleItemFocusNode!.unfocus();
+    }else {
+      Log.d("FeedbackCreateViewModel 取消所有表单聚焦状态");
+      // 遍历 formData 的所有表单 然后逐一取消聚焦
+      Map<String, dynamic> formData = state.formData;
+      for(String key in formData.keys){
+        final FocusNode sigleItemFocusNode = formData[key]['focusNode'];
+        sigleItemFocusNode!.unfocus();
+      }
+    }
+  }
+
+  // 获取表单的值
+  dynamic _getFormFieldValue({List<String>? keys, String? keyStr}) {
+    if(keyStr!.isEmpty){
+      if(keys!.isNotEmpty){
+        // 遍历keys获取指定多个 keys 的表单value
+        Map<String, dynamic> resultValueMap = {};
+        for (String itemStr in keys!) {
+          TextEditingController sigleItemController = state.formData[itemStr]!['controller'];
+          resultValueMap[itemStr] = sigleItemController.text;
+        }
+        return resultValueMap;
+      }
+    }else if(keyStr!.isNotEmpty){
+      // 获取单个表单的value
+      final TextEditingController sigleItemController = state.formData[keyStr]!['controller'];
+      return sigleItemController.text;
+    }
+  }
+
+  ///提交反馈
+  void submitNewsfeedPost() {
+    state = state.copyWith(mindFieldErrorText: null);
+
+    _dismissKeyboard();
+
+    // 获取表单的值
+    String mindValue = _getFormFieldValue(keyStr: 'mind');
+
+    Log.d('当前待提交的 mind:$mindValue  imgList:${state.imgList}');
+
+
+    if (Utils.isEmpty(mindValue)) {
+      state = state.copyWith(mindFieldErrorText: "Title cannot be empty!");
+      return;
+    }
+
+    //去成功页面
+    // FeedbackCreateSuccessPage.startInstance();
+    // 返回上一页
+  }
+
+  //选中图片
+  void setImgList(List<String> list) {
+    state = state.copyWith(imgList: list);
+  }
+
+  //初始化监听
+  void initListener(NewsfeedPostPageState initState) {
+
+    // 获取表单的焦点节点
+    final FocusNode focusNode = getFocusNode(state.formData, 'mind');
+
+    focusNode.addListener(() {
+      // 获取焦点的时候清空错误文本
+      if (focusNode.hasFocus) {
+        state = state.copyWith(mindFieldErrorText: null);
+      }
+    });
+  }
+
+  //销毁资源
+  void onDispose(NewsfeedPostPageState initState) {
+    // 获取表单的焦点节点
+    final FocusNode focusNode = getFocusNode(state.formData, 'mind');
+    focusNode.dispose();
+
+    Log.d("NewsfeedPostPageState 销毁 onDispose");
+  }
+
 
 }

+ 1 - 1
packages/cpt_community/lib/modules/newsfeed/newsfeed_post/newsfeed_post_vm.g.dart

@@ -6,7 +6,7 @@ part of 'newsfeed_post_vm.dart';
 // RiverpodGenerator
 // **************************************************************************
 
-String _$newsfeedPostVmHash() => r'3717034c813e1d4cf8b2f212aa15ab483c6f220e';
+String _$newsfeedPostVmHash() => r'cf718a80d333c12dc21a3fed0516d82bf44b7ad1';
 
 /// See also [NewsfeedPostVm].
 @ProviderFor(NewsfeedPostVm)

+ 4 - 1
packages/cpt_community/lib/modules/newsfeed/newsfeed_vm.dart

@@ -1,4 +1,5 @@
 
+import 'package:cpt_community/modules/newsfeed/newsfeed_post/newsfeed_post_page.dart';
 import 'package:cs_resources/generated/assets.dart';
 import 'package:flutter/material.dart';
 import 'package:plugin_platform/engine/toast/toast_engine.dart';
@@ -195,7 +196,9 @@ class NewsfeedVm extends _$NewsfeedVm {
   void handlerGotoPost(context){
     // ComponentServiceManager().communityService.startCommunityPage();
 
-    AutoRouter.of(context).pushNamed(RouterPath.newsFeedPost);
+    // AutoRouter.of(context).pushNamed(RouterPath.newsFeedPost);
+
+    NewsfeedPostPage.startInstance();
   }
 
   // 点击tab

+ 1 - 1
packages/cpt_community/lib/modules/newsfeed/newsfeed_vm.g.dart

@@ -6,7 +6,7 @@ part of 'newsfeed_vm.dart';
 // RiverpodGenerator
 // **************************************************************************
 
-String _$newsfeedVmHash() => r'8913ddbbe012f28caa65fbc8fdd824a8b818d4c6';
+String _$newsfeedVmHash() => r'dad5087f675a931b393ca884835482454cfd2700';
 
 /// See also [NewsfeedVm].
 @ProviderFor(NewsfeedVm)

+ 3 - 0
packages/cpt_property/lib/modules/ioan/property_ioan_page.dart

@@ -1,5 +1,6 @@
 import 'package:cpt_property/components/bottomDialog.dart';
 import 'package:cs_resources/generated/assets.dart';
+import 'package:cs_resources/theme/app_colors_theme.dart';
 import 'package:flutter/material.dart';
 import 'package:auto_route/auto_route.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -7,6 +8,7 @@ import 'package:plugin_platform/engine/toast/toast_engine.dart';
 import 'package:router/ext/auto_router_extensions.dart';
 import 'package:shared/utils/color_utils.dart';
 import 'package:widgets/ext/ex_widget.dart';
+import 'package:widgets/my_button.dart';
 import 'package:widgets/my_load_image.dart';
 import 'package:widgets/my_text_view.dart';
 
@@ -389,6 +391,7 @@ class PropertyIoanPage extends HookConsumerWidget {
                   "Request a Quote",
                   fontSize: 16,
                   textColor: Colors.white,
+                  isFontMedium: true,
                 ),
               ],
             ),

+ 1 - 1
packages/cpt_property/pubspec.yaml

@@ -49,7 +49,7 @@ dependencies:
   hooks_riverpod: ^2.5.1
 
   # freezed 注解
-  freezed_annotation: ^2.2.0
+#  freezed_annotation: ^2.2.0
 
 dev_dependencies:
   flutter_test: