Skip to content
代码片段 群组 项目
issue_creation_view.dart 9.0 KB
更新 更旧
Neil Wang's avatar
Neil Wang 已提交
part of 'issue_creation_page.dart';

typedef CreateIssueCallback = void Function({required String title, String? description, String? labels});
Neil Wang's avatar
Neil Wang 已提交
class IssueCreationView extends StatefulWidget {
  const IssueCreationView({super.key, required this.projectId, required this.model, required this.onCreateIssueTapped, required this.issueTitleController, required this.issueDescriptionController});
  final int projectId;
Neil Wang's avatar
Neil Wang 已提交
  final IssueCreationModel model;
  final CreateIssueCallback onCreateIssueTapped;
  final TextEditingController issueTitleController;
  final TextEditingController issueDescriptionController;
Neil Wang's avatar
Neil Wang 已提交

  @override
  State<IssueCreationView> createState() => _IssueCreationViewState();
}

class _IssueCreationViewState extends State<IssueCreationView> {
  final double _bottomActionBoxHeight = 42;

  @override
  Widget build(BuildContext context) {
    final colorScheme = Theme.of(context).colorScheme;
    return Stack(children: [
      Container(
          padding: EdgeInsets.only(bottom: _bottomActionBoxHeight),
          child: SingleChildScrollView(
              physics: const ScrollPhysics(),
              child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
                InputFieldWithTitle(title: 'Title (required)', placeHolder: 'Write a issue title', controller: widget.issueTitleController),
                _buildTitle("Description"),
                _buildDescriptionTemplatesSelector(),
                MarkdownInputBox(projectId: widget.projectId, controller: widget.issueDescriptionController),
                _buildAssigneeSelector(),
                _buildTitle("Labels"),
                _buildSelectLabelView(),
Neil Wang's avatar
Neil Wang 已提交
              ]))),
      Align(
          alignment: Alignment.bottomCenter,
          child: SizedBox(
              width: double.infinity,
              height: _bottomActionBoxHeight,
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
Neil Wang's avatar
Neil Wang 已提交
                  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)),
                        ),
                      )),
Neil Wang's avatar
Neil Wang 已提交
                  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)),
                              ),
                            )),
  Padding _buildTitle(String title) {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: Text(title, style: const TextStyle(color: Color(0XFF03162F), fontSize: 16, fontWeight: FontWeight.w600)),
    );
  }

  Widget _buildSelectLabelView() {
    var iconButton = IconButton(
        key: const Key("goto-label-selector"),
        onPressed: () async {
          var result = await Navigator.of(context).pushNamed(LabelSelector.routeName, arguments: {"projectId": widget.projectId, "selectedIds": widget.model.selectedLabels.map((e) => e.id).toSet()});
          if (result != null) {
            List<Label> selectedLabels = result as List<Label>;
            setState(() {
              widget.model.onLabelSelect(selectedLabels);
            });
          }
        },
        icon: const Icon(Icons.add));
    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)));
    } else {
      for (var e in widget.model.selectedLabels) {
        children.add(_buildLabelView(e));
      }
    }
    children.add(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),
      padding: EdgeInsets.symmetric(vertical: widget.model.selectedLabels.isEmpty ? 0 : 8, horizontal: 8),
      margin: const EdgeInsets.only(left: 16, right: 16),
      child: Wrap(
        spacing: 8,
        runSpacing: 8,
        alignment: widget.model.selectedLabels.isEmpty ? WrapAlignment.spaceBetween : WrapAlignment.start,
        crossAxisAlignment: WrapCrossAlignment.center,
        direction: Axis.horizontal,
        children: children,
      ),
    );
  }

  Widget _buildLabelView(Label label) {
    return Container(
      padding: const EdgeInsets.only(left: 4),
      decoration: const BoxDecoration(borderRadius: BorderRadius.all(Radius.circular(4)), color: Color(0XFFFFFAF7)),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          Container(width: 16, height: 16, decoration: BoxDecoration(color: Color(label.formatColor()))),
          const VerticalDivider(width: 16),
          Text(label.name),
          IconButton(
              onPressed: () {
                setState(() {
                  widget.model.deleteSelectedLabel(label);
                });
              },
              icon: Icon(Icons.close, color: Theme.of(context).primaryColor))
        ],
      ),
    );
  }

  Widget _buildAssigneeSelector() {
    return Selectable<Member>(
        onSelected: (members) => widget.model.onAssigneesSelect(members),
        selectedItemBuilder: (member) => _buildAssigneeView(member),
        selectorItemBuilder: (BuildContext context, int index, Member member) => AvatarAndName(username: member.username, avatarUrl: member.avatarUrl),
        dataProviderProvider: () => MemberProvider(projectId: widget.projectId),
        keyMapper: (member) => member.id,
        placeHolder: "Assignees",
        multiSelection: () => widget.model.isMultiAssignees(),
        initialData: widget.model.selectedAssignees,
        filter: (keyword, e) => e.username.toUpperCase().contains(keyword.toUpperCase()));
  Widget _buildAssigneeView(Member member) {
    return AvatarAndName(username: member.username, avatarUrl: member.avatarUrl);
  Widget _buildDescriptionTemplatesSelector() {
    var iconButton = IconButton(
        key: const Key("goto-description-selector"),
        onPressed: () async {
          var result = await Navigator.of(context).pushNamed(DescriptionSelector.routeName, arguments: {
            "projectId": widget.projectId,
            "free": await widget.model.isFree(),
          }) as Map<String, dynamic>;
          var content = await result['content'];
          widget.model.templateName = result['templateName'];
          widget.issueDescriptionController.text = content;
          setState(() {});
        },
        icon: const Icon(Icons.add));
    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);
    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),
      padding: EdgeInsets.symmetric(vertical: widget.model.selectedAssignees.isEmpty ? 0 : 8, horizontal: 8),
      margin: const EdgeInsets.only(left: 16, right: 16),
      child: Wrap(
        spacing: 8,
        runSpacing: 8,
        alignment: widget.model.selectedAssignees.isEmpty ? WrapAlignment.spaceBetween : WrapAlignment.start,
        crossAxisAlignment: WrapCrossAlignment.center,
        direction: Axis.horizontal,
        children: children,
      ),
    );
  }