From f8df35e60c9d020b7e3b893ee0e38d4016f20061 Mon Sep 17 00:00:00 2001
From: miaolu <miaomiao.lu@highsoft.ltd>
Date: Fri, 23 Dec 2022 06:36:26 +0000
Subject: [PATCH] =?UTF-8?q?fix:=20#247=20=E5=88=9B=E5=BB=BA=E8=AE=AE?=
 =?UTF-8?q?=E9=A2=98=E9=A1=B5=E9=9D=A2=E6=A0=B7=E5=BC=8F=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 assets/images/create_issuue_add_icon.png      | Bin 0 -> 278 bytes
 assets/images/photo_icon.png                  | Bin 0 -> 1723 bytes
 lib/core/widgets/input_field_with_title.dart  |   2 +-
 lib/core/widgets/markdown_input_box.dart      |  25 ++-----
 lib/domain/description_content.dart           |  16 +++++
 lib/domain/issue_detail.dart                  |   8 +--
 lib/domain/note.dart                          |   6 +-
 lib/modules/issues/issue_creation_page.dart   |  52 +++++++-------
 lib/modules/issues/issue_creation_view.dart   |  66 ++++--------------
 lib/modules/issues/selectable.dart            |   4 +-
 test/domain/description_content_test.dart     |  28 ++++++++
 .../create_issue_in_sub_group_test.dart       |  11 ++-
 .../issues/issue_creation_page_test.dart      |  12 ++--
 13 files changed, 112 insertions(+), 118 deletions(-)
 create mode 100644 assets/images/create_issuue_add_icon.png
 create mode 100644 assets/images/photo_icon.png
 create mode 100644 lib/domain/description_content.dart
 create mode 100644 test/domain/description_content_test.dart

diff --git a/assets/images/create_issuue_add_icon.png b/assets/images/create_issuue_add_icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..4e8b2574f760955b1b89e487428582beb82496c7
GIT binary patch
literal 278
zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDB3?!H8JlO)`1o(uw8XKD!8XD{C8_3AW%E(BI
zii!yc2=egova_?Zu&@ACb#+Z_Z}0Eu=xcB90V2Uq6M2B5{3Stt!9W@YR2}lCF1pBg
z8^~|-ba4!+xRo4pl!3W^`w?4_ga$@2Hj5McPUi<6Vqo^+;I(I!$hJ?I_~ZGB0}2g#
zjSP#0nnRj4-U($mW|FkycOxUyzxT-(e)o&oq}}L`ePr%uT_APz(N6K*Pj!yHzvp;k
z|MdK{in<s^t;H4=%!MIL{VbR3*xLA+B2;GFwEvUu=3($iYZ}8Rzl?t(Yws=vxzN+q
K&t;ucLK6U*nOczm

literal 0
HcmV?d00001

diff --git a/assets/images/photo_icon.png b/assets/images/photo_icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..46e3b178a24cf9bd4e51e514c36fddf6cf49131f
GIT binary patch
literal 1723
zcmcIji$BwA7~jTn9mX)sow*#&+!=De4b#x+Q>2x!P#u?!Ivq|5ZL`^GVMl04D%XUN
z`joUpCAlQGNF){NQ@J#B-p#51;5?u2`@GNdeZSB1zQ6bPdFiAeUxc)}Gz0=c5d6GC
zz-%ELl48K`9g}K;Kw#aZ!0p}%3CR>n9ElVf6cj=v1`~-SfBzr?A<);C;Nt_}=jrL=
z=H~9|>fz$z?(FR1=;(~cJ2^N2;O*@J>}_rBt*opqEv+pqtSl@naX0`Ab90=jskw=X
zsj;!Kk&y`&Yh-8$fHg3{>gyZo>gwz2>gnj{X>03fYHDFHni?7a7<F|34K+1&G+Iqn
z6|JJ8s;sPxLaCroC?zG7qN0+bq9PKhghV106aW<D<>lq%<Piut1Og!|3jj<;Mn*WK
zrDdd~q@|>!Bqb#!Bmg9~2n{YS4j?WjCI*86z@ShlxRCE6D<~+atgPg4I6NK?#Cdsn
zbb2vJ*=#n0!3CbhV$taw8tp8TnM<Vt@*MEl*&M(#nM?-b42_mUrRHR3v#C@-oB<4j
z0R(IsjY+42B{rST%+AiC(Lf)d$pM~D2Qdr8qFNvmra&)BH+f+#U_=_Q1RzW|_Y%dR
zvoHk^)dDX(#!qZ2H@6TK0`X=yVad-{q9#Hf1e>)#WIt2Jk0!#ZO{J(gi2pkPxXAVO
z^|iG%fk5!>+qadK)vsT_E-rptSXh{xoSK-J9334W85!&EAMEYz>*?uz{J5vHvzyQF
zY;V8c+<f!;^&9o|P1mk9)YUaqS6{2FtSK+QQd)Ycq@<k3yI5RY%H<a2=bt}!?jJTg
zm&M|M8{Yh6aDdoKO*;6O{39Vjp<w<$3<`s9k&u*@MaU_rsA*udv~_g#^bL%E#aUWe
z+uGxuoZY>=1BoHOMUeMJM#sb*{5>i8=&=(gGqb2UEcV&ld~PwXq_n)e;!0(0L-VcM
zciQjqyPoz9489m186AH)J@e-6yLaywKYm(XSrx2pL@7N-gJ(KQ@N(aNY@{iFep|>s
z^aC6#6X!;o3ebJTtwE4<&6EPItpijwhU04Nh9>&8`?z7`w*l4>PiD-J{AO$JoS)LP
zxBE1N>hLDHh@$6FR8uSep_LbLrz4Fw>#~gJTvjlW)-%j$jLoLD^xhh<EAjnQmKTzN
zcKtG%D_druM7CK?3_{J!H}fDx;a3G!X{YAJ<Rz!jc@<__F*ww;xX$gU`ToL_bC+7v
zu|r&z?A-RtP@H|=enUoWEt?l}WcWKr_HAK!V~qp+ehMZd1`}UmaC5}$WXYk{Pr>^I
z1-S<Zb-NPAhIdO26pOnKK3{rOq@EJ*(^WyS3!~UURM>5&VaSG<1{S2%P5uPma`u8<
zqIjcS3mQJW^WdL{+qUc}Nb@OGgrs=N-JQ?9krtt!h~Jq+i4h!jy8BB#*Y6?*MhZC(
zO=xdyA$O5i?fuioZ3S+NiIHs*ln~Q+CgeavbP?Oh594W)3hm6$SVLx(;cW+UBEk+x
zT$no@Pp8C%?s!9VTqr!|@K8^}IJ9?y1v&B=xgOoxrJd^VP~iEh<MV~Ns(@te%&p4;
z)1}Ty1w6V(+tIn1Yp@(+{QVF8UB|H#u`{oi+KS6p4q?fkgEhkw7gK9&=E&<W_11Sk
z8<(H#?VMiRG0-vlTD7U<N%2~X^nFHQzz3hL6D_Z9uRZlNO|HLQhLOT5<_FitJt9IR
z_D#cY7S`IJR|dQDkM2K_`(+#U;S9WzjNTTR0eys3lj<;;h98bTq?Thtu+ob}zbAAi
zXHnDtS|4&pnP%M>zSntBHY<JCP$bD58aWo;A82HP?=E%o@2{IEuQEt|;kXf3Y&Hd-
z?PCR)3@8#Kup3c->x2jM1CqJ}pVd`A**H#vMQYi`hN|!@P%>4k&rQ|+4D+T@By@?H
z$C`K&%<n0Y4Ec}}6>xF&C@v*id6sfjIgH8Mb2f;N^-gp2+Yk&r)~@zia(MP&hk@|1
N61;=FYCZOz`WJ`;-<JRY

literal 0
HcmV?d00001

diff --git a/lib/core/widgets/input_field_with_title.dart b/lib/core/widgets/input_field_with_title.dart
index 1386c0362..9c194af68 100644
--- a/lib/core/widgets/input_field_with_title.dart
+++ b/lib/core/widgets/input_field_with_title.dart
@@ -28,7 +28,7 @@ class _InputFieldWithTitleState extends State<InputFieldWithTitle> {
           alignment: Alignment.center,
           child: TextField(
               controller: widget.controller,
-              style: textTheme.bodySmall,
+              style: const TextStyle(color: Color(0xFF03162F)),
               maxLines: widget.maxLines,
               cursorColor: const Color(0xFFFC6D26),
               decoration: InputDecoration(hintText: widget.placeHolder, border: const OutlineInputBorder(borderSide: BorderSide.none), contentPadding: const EdgeInsets.only(left: 8))))
diff --git a/lib/core/widgets/markdown_input_box.dart b/lib/core/widgets/markdown_input_box.dart
index 7ba9d7040..5c5162f06 100644
--- a/lib/core/widgets/markdown_input_box.dart
+++ b/lib/core/widgets/markdown_input_box.dart
@@ -52,6 +52,7 @@ class _MarkdownInputBoxState extends State<MarkdownInputBox> with TickerProvider
                   alignment: Alignment.centerLeft,
                   constraints: const BoxConstraints(maxWidth: 150),
                   height: 40,
+                  transform: Matrix4.translationValues(-16, 0, 0),
                   child: TabBar(
                     isScrollable: false,
                     padding: EdgeInsets.zero,
@@ -61,7 +62,7 @@ class _MarkdownInputBoxState extends State<MarkdownInputBox> with TickerProvider
                     labelColor: Colors.black,
                     labelStyle: textTheme.titleLarge,
                     unselectedLabelColor: Colors.black54,
-                    labelPadding: EdgeInsets.zero,
+                    labelPadding: const EdgeInsets.only(bottom: 8, top: 8),
                     splashFactory: NoSplash.splashFactory,
                     tabs: [for (String tab in MarkdownInputBox._tabs) Text(tab)],
                     onTap: (index) {
@@ -72,29 +73,15 @@ class _MarkdownInputBoxState extends State<MarkdownInputBox> with TickerProvider
                   )),
               const Flexible(fit: FlexFit.tight, child: SizedBox()),
               InkWell(
-                onTap: () => _onAtButtonPressed(),
-                child: Container(
-                  decoration: const BoxDecoration(color: Colors.white),
-                  alignment: Alignment.centerRight,
-                  height: 40,
-                  width: 50,
-                  child: const Icon(
-                    Icons.alternate_email,
-                    color: Colors.black26,
-                  ),
-                ),
-              ),
+                  onTap: () => _onAtButtonPressed(),
+                  child: Container(alignment: Alignment.centerRight, height: 40, width: 50, child: const Icon(Icons.alternate_email, color: Color(0xFF87878C), size: 19))),
               InkWell(
                 onTap: () => _onImageButtonPressed(),
                 child: Container(
-                  decoration: const BoxDecoration(color: Colors.white),
                   alignment: Alignment.centerRight,
                   height: 40,
-                  width: 50,
-                  child: const Icon(
-                    Icons.photo_outlined,
-                    color: Colors.black26,
-                  ),
+                  width: 30,
+                  child: Image.asset("assets/images/photo_icon.png", height: 16, width: 16),
                 ),
               ),
             ],
diff --git a/lib/domain/description_content.dart b/lib/domain/description_content.dart
new file mode 100644
index 000000000..3d1e990b8
--- /dev/null
+++ b/lib/domain/description_content.dart
@@ -0,0 +1,16 @@
+import 'package:jihu_gitlab_app/core/settings/settings_provider.dart';
+
+class DescriptionContent {
+  String content;
+  String pathWithNamespace;
+
+  DescriptionContent._(this.content, this.pathWithNamespace);
+
+  factory DescriptionContent.init(String content, String pathWithNamespace) {
+    return DescriptionContent._(content, pathWithNamespace);
+  }
+
+  String get value {
+    return content.replaceAll("](/uploads/", "](${SettingsProvider().baseUrl}/$pathWithNamespace/uploads/").replaceAll('<br>', '\n').replaceAll('<br/>', '\n');
+  }
+}
diff --git a/lib/domain/issue_detail.dart b/lib/domain/issue_detail.dart
index d77c88488..91120655f 100644
--- a/lib/domain/issue_detail.dart
+++ b/lib/domain/issue_detail.dart
@@ -1,5 +1,5 @@
-import 'package:jihu_gitlab_app/core/settings/settings_provider.dart';
 import 'package:jihu_gitlab_app/core/time.dart';
+import 'package:jihu_gitlab_app/domain/description_content.dart';
 
 import 'assignee.dart';
 
@@ -24,11 +24,7 @@ class IssueDetail {
   }
 
   factory IssueDetail.fromJson(Map<String, dynamic> issueJson, Map<String, dynamic> projectJson) {
-    var description = (issueJson['description'] ?? '')
-        .toString()
-        .replaceAll("[image](/uploads/", "[image](${SettingsProvider().baseUrl}/${projectJson['path_with_namespace']}/uploads/")
-        .replaceAll('<br>', '\n')
-        .replaceAll('<br/>', '\n');
+    String description = DescriptionContent.init(issueJson['description'] ?? '', projectJson['path_with_namespace']).value;
     return IssueDetail._(issueJson['id'] ?? 0, issueJson['iid'] ?? 0, issueJson['project_id'] ?? 0, issueJson['title'] ?? '', description, issueJson['created_at'] ?? '', projectJson['name'] ?? '',
         Assignee.fromJson(issueJson['author']));
   }
diff --git a/lib/domain/note.dart b/lib/domain/note.dart
index 9e10cd7f1..12cddacc0 100644
--- a/lib/domain/note.dart
+++ b/lib/domain/note.dart
@@ -1,5 +1,5 @@
-import 'package:jihu_gitlab_app/core/settings/settings_provider.dart';
 import 'package:jihu_gitlab_app/core/time.dart';
+import 'package:jihu_gitlab_app/domain/description_content.dart';
 
 import 'assignee.dart';
 
@@ -14,7 +14,7 @@ class Note {
   Note._(this.id, this.createdAt, this.body, this.system, this.author, this.type);
 
   factory Note.fromJson(Map<String, dynamic> json, String pathWithNamespace) {
-    var replaceAll = json['body'].toString().replaceAll("[image](/uploads/", "[image](${SettingsProvider().baseUrl}/$pathWithNamespace/uploads/");
-    return Note._(json['id'], Time.init(json['created_at']), replaceAll, json['system'], Assignee.fromJson(json['author']), json['type'] ?? "");
+    var description = DescriptionContent.init(json['body'], pathWithNamespace).value;
+    return Note._(json['id'], Time.init(json['created_at']), description, json['system'], Assignee.fromJson(json['author']), json['type'] ?? "");
   }
 }
diff --git a/lib/modules/issues/issue_creation_page.dart b/lib/modules/issues/issue_creation_page.dart
index 195d79dfa..27ca8e0c5 100644
--- a/lib/modules/issues/issue_creation_page.dart
+++ b/lib/modules/issues/issue_creation_page.dart
@@ -2,7 +2,6 @@ import 'dart:async';
 import 'dart:core';
 
 import 'package:flutter/material.dart';
-import 'package:jihu_gitlab_app/core/load_state.dart';
 import 'package:jihu_gitlab_app/core/loader.dart';
 import 'package:jihu_gitlab_app/core/token_provider.dart';
 import 'package:jihu_gitlab_app/core/widgets/avatar_and_name.dart';
@@ -90,35 +89,40 @@ class _IssueCreationPageState extends State<IssueCreationPage> {
                       Navigator.pop(context, true);
                     }
                   }),
+              actions: [
+                Container(
+                  transform: Matrix4.translationValues(-12, 2, 0),
+                  child: TextButton(
+                      onPressed: () {
+                        var title = _issueTitleController.text;
+                        var description = _issueDescriptionController.text;
+                        var labels = _model.selectedLabels.map((e) => e.name).toList().join(",");
+                        Loader.showProgress(context);
+                        _model.create(widget.projectId, title, description: description, labels: labels).then((value) {
+                          Loader.hideProgress(context);
+                          if (value) {
+                            Toast.success(context, 'Created successfully');
+                            if (widget.from == 'group') {
+                              Navigator.pop(context);
+                              Navigator.pop(context, true);
+                            } else {
+                              Navigator.pop(context, true);
+                            }
+                          } else {
+                            Toast.error(context, 'Failed to create');
+                          }
+                        });
+                      },
+                      child: Text('Create', style: TextStyle(color: Theme.of(context).primaryColor))),
+                ),
+              ],
             ),
             body: SafeArea(child: Consumer(
               builder: (context, tokenProvider, child) {
                 if (!TokenProvider.authorized) {
                   return const UnauthorizedView();
                 }
-                return IssueCreationView(
-                  projectId: widget.projectId,
-                  model: _model,
-                  issueTitleController: _issueTitleController,
-                  issueDescriptionController: _issueDescriptionController,
-                  onCreateIssueTapped: ({required String title, String? description, String? labels}) async {
-                    Loader.showProgress(context);
-                    _model.create(widget.projectId, title, description: description, labels: labels).then((value) {
-                      Loader.hideProgress(context);
-                      if (value) {
-                        Toast.success(context, 'Created successfully');
-                        if (widget.from == 'group') {
-                          Navigator.pop(context);
-                          Navigator.pop(context, true);
-                        } else {
-                          Navigator.pop(context, true);
-                        }
-                      } else {
-                        Toast.error(context, 'Failed to create');
-                      }
-                    });
-                  },
-                );
+                return IssueCreationView(projectId: widget.projectId, model: _model, issueTitleController: _issueTitleController, issueDescriptionController: _issueDescriptionController);
               },
             ))));
   }
diff --git a/lib/modules/issues/issue_creation_view.dart b/lib/modules/issues/issue_creation_view.dart
index ee6b2b9f9..f35fa96e5 100644
--- a/lib/modules/issues/issue_creation_view.dart
+++ b/lib/modules/issues/issue_creation_view.dart
@@ -3,11 +3,10 @@ part of 'issue_creation_page.dart';
 typedef CreateIssueCallback = void Function({required String title, String? description, String? labels});
 
 class IssueCreationView extends StatefulWidget {
-  const IssueCreationView({super.key, required this.projectId, required this.model, required this.onCreateIssueTapped, required this.issueTitleController, required this.issueDescriptionController});
+  const IssueCreationView({super.key, required this.projectId, required this.model, required this.issueTitleController, required this.issueDescriptionController});
 
   final int projectId;
   final IssueCreationModel model;
-  final CreateIssueCallback onCreateIssueTapped;
   final TextEditingController issueTitleController;
   final TextEditingController issueDescriptionController;
 
@@ -20,7 +19,6 @@ class _IssueCreationViewState extends State<IssueCreationView> {
 
   @override
   Widget build(BuildContext context) {
-    final colorScheme = Theme.of(context).colorScheme;
     return Stack(children: [
       Container(
           padding: EdgeInsets.only(bottom: _bottomActionBoxHeight),
@@ -28,60 +26,24 @@ class _IssueCreationViewState extends State<IssueCreationView> {
               physics: const ScrollPhysics(),
               child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
                 InputFieldWithTitle(title: 'Title (required)', placeHolder: 'Write a issue title', controller: widget.issueTitleController),
-                _buildTitle("Description"),
+                _buildTitle("Template"),
                 _buildDescriptionTemplatesSelector(),
+                _buildDescriptionTitle("Description"),
                 MarkdownInputBox(projectId: widget.projectId, controller: widget.issueDescriptionController),
-                // _buildTitle("Assignees"),
-                // _buildAssigneeSelector(),
                 _buildAssigneeSelector(),
                 _buildTitle("Labels"),
                 _buildSelectLabelView(),
               ]))),
-      Align(
-          alignment: Alignment.bottomCenter,
-          child: SizedBox(
-              width: double.infinity,
-              height: _bottomActionBoxHeight,
-              child: Row(
-                mainAxisAlignment: MainAxisAlignment.spaceBetween,
-                children: [
-                  const Spacer(flex: 1),
-                  Expanded(
-                      flex: 10,
-                      child: InkWell(
-                        onTap: () {
-                          Navigator.pop(context);
-                        },
-                        child: Container(
-                          decoration: const BoxDecoration(color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(4))),
-                          alignment: Alignment.center,
-                          child: Text('Cancel', style: TextStyle(color: colorScheme.secondary, fontSize: 14, fontWeight: FontWeight.w600)),
-                        ),
-                      )),
-                  const Spacer(flex: 2),
-                  Expanded(
-                      flex: 14,
-                      child: widget.model.createIssueState == LoadState.loadingState
-                          ? const Center(child: CircularProgressIndicator())
-                          : InkWell(
-                              onTap: () {
-                                var title = widget.issueTitleController.text;
-                                var description = widget.issueDescriptionController.text;
-                                var labels = widget.model.selectedLabels.map((e) => e.name).toList().join(",");
-                                widget.onCreateIssueTapped(title: title, description: description, labels: labels);
-                              },
-                              child: Container(
-                                alignment: Alignment.center,
-                                decoration: const BoxDecoration(color: Color(0xFFFC6D26), borderRadius: BorderRadius.all(Radius.circular(4))),
-                                child: const Text('Create issue', style: TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.w600)),
-                              ),
-                            )),
-                  const Spacer(flex: 1)
-                ],
-              )))
     ]);
   }
 
+  Padding _buildDescriptionTitle(String title) {
+    return Padding(
+      padding: const EdgeInsets.fromLTRB(16, 16, 16, 0),
+      child: Text(title, style: const TextStyle(color: Color(0XFF03162F), fontSize: 16, fontWeight: FontWeight.w600)),
+    );
+  }
+
   Padding _buildTitle(String title) {
     return Padding(
       padding: const EdgeInsets.all(16.0),
@@ -101,7 +63,7 @@ class _IssueCreationViewState extends State<IssueCreationView> {
             });
           }
         },
-        icon: const Icon(Icons.add));
+        icon: const Icon(Icons.add, size: 22, color: Color(0xFF87878C)));
     final List<Widget> children = [];
     if (widget.model.selectedLabels.isEmpty) {
       children.add(const Text("Select label(s)", style: TextStyle(color: Color(0XFF66696D), fontSize: 14, fontWeight: FontWeight.w400)));
@@ -110,7 +72,7 @@ class _IssueCreationViewState extends State<IssueCreationView> {
         children.add(_buildLabelView(e));
       }
     }
-    children.add(iconButton);
+    children.add(Container(transform: Matrix4.translationValues(12, 0, 0), child: iconButton));
     return Container(
       width: double.infinity,
       decoration: const BoxDecoration(border: Border.fromBorderSide(BorderSide(color: Color(0XFFEAEAEA), width: 1)), borderRadius: BorderRadius.all(Radius.circular(4)), color: Colors.white),
@@ -179,10 +141,10 @@ class _IssueCreationViewState extends State<IssueCreationView> {
           widget.issueDescriptionController.text = content;
           setState(() {});
         },
-        icon: const Icon(Icons.add));
+        icon: const Icon(Icons.add, size: 22, color: Color(0xFF87878C)));
     final List<Widget> children = [];
     children.add(Text(widget.model.templateName ?? 'Choose a template', style: const TextStyle(color: Color(0XFF66696D), fontSize: 14, fontWeight: FontWeight.w400)));
-    children.add(iconButton);
+    children.add(Container(transform: Matrix4.translationValues(12, 0, 0), child: iconButton));
     return Container(
       width: double.infinity,
       decoration: const BoxDecoration(border: Border.fromBorderSide(BorderSide(color: Color(0XFFEAEAEA), width: 1)), borderRadius: BorderRadius.all(Radius.circular(4)), color: Colors.white),
diff --git a/lib/modules/issues/selectable.dart b/lib/modules/issues/selectable.dart
index 7356563ca..3bc650944 100644
--- a/lib/modules/issues/selectable.dart
+++ b/lib/modules/issues/selectable.dart
@@ -70,7 +70,7 @@ class _SelectableState<T> extends State<Selectable<T>> {
             _onSelectedCallback();
           }
         },
-        icon: const Icon(Icons.add));
+        icon: const Icon(Icons.add, size: 22, color: Color(0xFF87878C)));
     final List<Widget> children = [];
     if (hasNoData) {
       children.add(const Text("Select assignee(s)", style: TextStyle(color: Color(0XFF66696D), fontSize: 14, fontWeight: FontWeight.w400)));
@@ -79,7 +79,7 @@ class _SelectableState<T> extends State<Selectable<T>> {
         children.add(_buildSelectedItem(e));
       }
     }
-    children.add(iconButton);
+    children.add(Container(transform: Matrix4.translationValues(12, 0, 0), child: iconButton));
     return Container(
       width: double.infinity,
       decoration: const BoxDecoration(border: Border.fromBorderSide(BorderSide(color: Color(0XFFEAEAEA), width: 1)), borderRadius: BorderRadius.all(Radius.circular(4)), color: Colors.white),
diff --git a/test/domain/description_content_test.dart b/test/domain/description_content_test.dart
new file mode 100644
index 000000000..f1b6e9d80
--- /dev/null
+++ b/test/domain/description_content_test.dart
@@ -0,0 +1,28 @@
+import 'package:flutter_test/flutter_test.dart';
+import 'package:jihu_gitlab_app/domain/description_content.dart';
+
+void main() {
+  test("Include uploads no image", () {
+    String content = "![创建议题页面样式问题](/uploads/ab1ce029b7a1f057e80105d04ef7e0ae/创建议题页面样式问题.png)";
+    var descriptionContent = DescriptionContent.init(content, "123");
+    expect(descriptionContent.value, "![创建议题页面样式问题](/123/uploads/ab1ce029b7a1f057e80105d04ef7e0ae/创建议题页面样式问题.png)");
+  });
+
+  test("Include uploads has image", () {
+    String content = "![image](/uploads/ab1ce029b7a1f057e80105d04ef7e0ae/创建议题页面样式问题.png)";
+    var descriptionContent = DescriptionContent.init(content, "123");
+    expect(descriptionContent.value, "![image](/123/uploads/ab1ce029b7a1f057e80105d04ef7e0ae/创建议题页面样式问题.png)");
+  });
+
+  test("Include uploads content", () {
+    String content = "/uploads";
+    var descriptionContent = DescriptionContent.init(content, "123");
+    expect(descriptionContent.value, "/uploads");
+  });
+
+  test("Include uploads with https", () {
+    String content = "![image](https://jihulab.com/123/uploads/ab1ce029b7a1f057e80105d04ef7e0ae/创建议题页面样式问题.png)";
+    var descriptionContent = DescriptionContent.init(content, "123");
+    expect(descriptionContent.value, "![image](https://jihulab.com/123/uploads/ab1ce029b7a1f057e80105d04ef7e0ae/创建议题页面样式问题.png)");
+  });
+}
diff --git a/test/integration_tests/create_issue_in_sub_group_test.dart b/test/integration_tests/create_issue_in_sub_group_test.dart
index 213970762..7a8684cc9 100644
--- a/test/integration_tests/create_issue_in_sub_group_test.dart
+++ b/test/integration_tests/create_issue_in_sub_group_test.dart
@@ -72,9 +72,8 @@ void main() {
     await tester.tap(find.text('旗舰版演示 / Demo / Go Demo'));
     await tester.pumpAndSettle();
     expect(find.byType(IssueCreationPage), findsOneWidget);
-    expect(find.text('Create issue'), findsNWidgets(2));
+    expect(find.text('Create'), findsOneWidget);
     expect(find.text('Title (required)'), findsOneWidget);
-    expect(find.text('Cancel'), findsOneWidget);
 
     // 用户填入了一些信息
     await tester.enterText(
@@ -89,7 +88,7 @@ void main() {
         'Demo description');
 
     // 用户点击了创建issue按钮
-    await tester.tap(find.widgetWithText(Container, 'Create issue'));
+    await tester.tap(find.text('Create'));
     await tester.pumpAndSettle(const Duration(seconds: 1));
 
     // 创建成功,返回了上一个页面
@@ -131,9 +130,8 @@ void main() {
     await tester.tap(find.text('旗舰版演示 / Demo / Go Demo'));
     await tester.pumpAndSettle();
     expect(find.byType(IssueCreationPage), findsOneWidget);
-    expect(find.text('Create issue'), findsNWidgets(2));
+    expect(find.text('Create'), findsOneWidget);
     expect(find.text('Title (required)'), findsOneWidget);
-    expect(find.text('Cancel'), findsOneWidget);
 
     // 用户填入了一些信息
     await tester.enterText(
@@ -183,9 +181,8 @@ void main() {
       await tester.tap(find.text('旗舰版演示 / Demo / Go Demo'));
       await tester.pumpAndSettle();
       expect(find.byType(IssueCreationPage), findsOneWidget);
-      expect(find.text('Create issue'), findsNWidgets(2));
+      expect(find.text('Create'), findsOneWidget);
       expect(find.text('Title (required)'), findsOneWidget);
-      expect(find.text('Cancel'), findsOneWidget);
 
       // 用户添加description的template
       await tester.tap(find.byKey(const Key("goto-description-selector")));
diff --git a/test/modules/issues/issue_creation_page_test.dart b/test/modules/issues/issue_creation_page_test.dart
index 844f2c939..22303c085 100644
--- a/test/modules/issues/issue_creation_page_test.dart
+++ b/test/modules/issues/issue_creation_page_test.dart
@@ -57,10 +57,9 @@ void main() {
       ),
     ));
     await tester.pumpAndSettle();
-    expect(find.text('Create issue'), findsNWidgets(2));
+    expect(find.text('Create'), findsOneWidget);
     expect(find.text('Title (required)'), findsOneWidget);
     expect(find.text('Write a issue title'), findsOneWidget);
-    expect(find.text('Cancel'), findsOneWidget);
     expect(find.byType(MarkdownInputBox), findsOneWidget);
 
     UserProvider().fullReset();
@@ -70,6 +69,8 @@ void main() {
   testWidgets('Should create issue with title and description success', (WidgetTester tester) async {
     await TokenProvider().reset(Tester.token());
     UserProvider().reset(Tester.user());
+    when(client.get<Map<String, dynamic>>("/api/v4/user")).thenAnswer((_) => Future(() => Response.of<Map<String, dynamic>>({'id': 5882})));
+    HttpClient.setInstance(client);
     await tester.pumpWidget(const MaterialApp(
       home: Scaffold(
         body: IssueCreationPage(projectId: 10000, from: 'group', groupId: 0),
@@ -90,8 +91,11 @@ void main() {
           (widget) => widget is TextField && widget.decoration?.hintText == 'Write a comment here...',
         ),
         issueDescription);
-    await tester.tap(find.text('Create issue').at(1));
-    await tester.pumpAndSettle();
+
+    await tester.tap(find.text('Create'));
+    for (int i = 0; i < 5; i++) {
+      await tester.pumpAndSettle(const Duration(seconds: 1));
+    }
 
     UserProvider().fullReset();
     TokenProvider().fullReset();
-- 
GitLab