Skip to content
代码片段 群组 项目
提交 864cfda2 编辑于 作者: Raymond Liao's avatar Raymond Liao :nerd: 提交者: ling zhang
浏览文件

fix: #311 issue comments 显示不完整

上级 8c99b59a
No related branches found
No related tags found
无相关合并请求
import 'package:flutter/material.dart';
import 'package:jihu_gitlab_app/core/load_state.dart';
import 'package:jihu_gitlab_app/core/net/http_client.dart';
import 'package:jihu_gitlab_app/domain/discussions.dart';
......@@ -8,23 +9,65 @@ class DiscussionsModel {
// TODO: Should be removed
IssueDetail issueDetail = IssueDetail.empty();
List<Discussion> comments = [];
LoadState _loadState = LoadState.loadingState;
bool isLoading = false;
final int _size = 20;
int _page = 1;
bool _hasNextPage = false;
int _lastAllCommentsLength = 0;
List<Discussion> comments = [];
bool submittable = false;
int? selectedNoteId;
String selectedDiscussionId = "";
bool isLoading = false;
LoadState _discussionsLoadState = LoadState.loadingState;
late int _projectId;
late int _issueIid;
late String _pathWithNamespace;
config(TodoParam param) {
_projectId = param.projectId;
_issueIid = param.targetIid == 0 ? param.issueIid : param.targetIid;
_pathWithNamespace = param.pathWithNamespace;
}
Future<bool> refresh() async {
try {
_page = 1;
_lastAllCommentsLength = 0;
comments = await _getDiscussions();
_hasNextPage = _lastAllCommentsLength >= _size;
_discussionsLoadState = LoadState.successState;
return Future.value(true);
} catch (e) {
debugPrint("DiscussionsModel: $e");
_discussionsLoadState = LoadState.errorState;
return Future.value(false);
}
}
Future getDiscussions(TodoParam param) async {
int projectId = param.projectId;
int iid = param.targetIid == 0 ? param.issueIid : param.targetIid;
comments = [];
Future<bool> loadMore() async {
try {
var response = await HttpClient.instance().get<List<dynamic>>("/api/v4/projects/$projectId/issues/$iid/discussions");
var data = response.body().map((e) => Discussion.fromJson(e, param.pathWithNamespace)).toList();
comments = data.where((element) => element.notes[0].system == false).toList();
var issueResponse = await HttpClient.instance().get<Map<String, dynamic>>("/api/v4/projects/$projectId/issues/$iid");
var projectResponse = await HttpClient.instance().get<Map<String, dynamic>>("/api/v4/projects/$projectId");
_discussionsLoadState = LoadState.loadingState;
_page++;
List<Discussion> list = await _getDiscussions();
comments.addAll(list);
_hasNextPage = _lastAllCommentsLength >= _size;
_discussionsLoadState = LoadState.successState;
return Future.value(true);
} catch (e) {
_page--;
_discussionsLoadState = LoadState.errorState;
return Future.value(false);
}
}
Future getIssueInfo() async {
try {
var issueResponse = await HttpClient.instance().get<Map<String, dynamic>>("/api/v4/projects/$_projectId/issues/$_issueIid");
var projectResponse = await HttpClient.instance().get<Map<String, dynamic>>("/api/v4/projects/$_projectId");
issueDetail = IssueDetail.fromJson(issueResponse.body(), projectResponse.body());
_loadState = LoadState.successState;
} catch (e) {
......@@ -33,8 +76,15 @@ class DiscussionsModel {
}
}
LoadState get loadState {
return _loadState;
Future<List<Discussion>> _getDiscussions() async {
try {
var response = await HttpClient.instance().get<List<dynamic>>("/api/v4/projects/$_projectId/issues/$_issueIid/discussions?page=$_page&per_page=$_size");
var data = response.body().map((e) => Discussion.fromJson(e, _pathWithNamespace)).toList();
_lastAllCommentsLength = data.length;
return data.where((element) => element.notes[0].system == false).toList();
} catch (e) {
rethrow;
}
}
Future _saveComment(int projectId, int iid, String comments) async {
......@@ -65,4 +115,10 @@ class DiscussionsModel {
await _saveComment(issueDetail.projectId, issueDetail.iid, commentContent);
}
}
LoadState get loadState => _loadState;
LoadState get discussionsLoadState => _discussionsLoadState;
bool get hasNextPage => _hasNextPage;
}
......@@ -28,44 +28,65 @@ class DiscussionsPage extends StatefulWidget {
class _DiscussionsPageState extends State<DiscussionsPage> {
final TextEditingController _commentController = TextEditingController();
FocusNode commentFocusNode = FocusNode();
final DiscussionsModel _model = DiscussionsModel();
FocusNode commentFocusNode = FocusNode();
String placeholder = "Write a comment ...";
int _inputTextLength = 0;
late ScrollController _controller;
@override
void initState() {
super.initState();
_controller = ScrollController()..addListener(_loadMore);
_toggleLoading(true);
_model.getDiscussions(widget.param).then((value) => _toggleLoading(false)).catchError((e) => _toggleLoading(false));
_model.config(widget.param);
_model.getIssueInfo().then((value) => _toggleLoading(false)).catchError((e) => _toggleLoading(false));
_onRefresh();
}
@override
void dispose() {
_controller.removeListener(_loadMore);
super.dispose();
}
void _onRefresh() async {
await _model.refresh();
setState(() {});
}
void _loadMore() async {
if (_model.hasNextPage && _model.discussionsLoadState != LoadState.loadingState && _controller.position.extentAfter < 200) {
await _model.loadMore();
setState(() {});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: true,
appBar: _buildAppBar(context),
backgroundColor: const Color(0xF8F8FAFF),
body: SafeArea(
child: Stack(
children: <Widget>[
Offstage(
offstage: !(_model.loadState == LoadState.successState),
child: GestureDetector(
onTap: () {
setState(() {
placeholder = "Write a comment ...";
_model.selectedNoteId = null;
commentFocusNode.unfocus();
});
},
child: Column(children: [Expanded(child: SingleChildScrollView(child: _buildIssueDetailPageView())), _buildIssueReplyView()]),
)),
Offstage(offstage: !(_model.loadState == LoadState.loadingState), child: _buildLoadingView()),
],
),
),
);
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
placeholder = "Write a comment ...";
_model.selectedNoteId = null;
FocusScope.of(context).requestFocus(FocusNode());
},
child: Scaffold(appBar: _buildAppBar(context), backgroundColor: const Color(0xF8F8FAFF), body: SafeArea(child: _buildBody())));
}
Widget _buildBody() {
if (_model.loadState == LoadState.loadingState) return _buildLoadingView();
const double bottomActionBoxHeight = 76;
return Stack(children: [
Container(
padding: const EdgeInsets.only(bottom: bottomActionBoxHeight), child: SingleChildScrollView(controller: _controller, physics: const ScrollPhysics(), child: _buildIssueDetailPageView())),
_buildIssueReplyView(bottomActionBoxHeight)
]);
}
CommonAppBar _buildAppBar(BuildContext context) {
......@@ -125,12 +146,11 @@ class _DiscussionsPageState extends State<DiscussionsPage> {
child: Column(mainAxisSize: MainAxisSize.min, children: const [Text("Comments", style: TextStyle(color: Color(0xFF1A1B36), fontSize: 16, fontWeight: FontWeight.w600))]),
),
Container(
padding: _model.comments.isEmpty ? const EdgeInsets.all(0) : const EdgeInsets.fromLTRB(12, 12, 12, 0),
margin: _model.comments.isEmpty ? const EdgeInsets.all(0) : const EdgeInsets.fromLTRB(12, 0, 12, 0),
constraints: _model.comments.isEmpty ? const BoxConstraints(minHeight: 0) : const BoxConstraints(minHeight: 50),
decoration: const BoxDecoration(color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(4))),
child: _buildDiscussionList(),
),
padding: _model.comments.isEmpty ? const EdgeInsets.all(0) : const EdgeInsets.fromLTRB(12, 12, 12, 0),
margin: _model.comments.isEmpty ? const EdgeInsets.all(0) : const EdgeInsets.fromLTRB(12, 0, 12, 0),
constraints: _model.comments.isEmpty ? const BoxConstraints(minHeight: 0) : const BoxConstraints(minHeight: 50),
decoration: const BoxDecoration(color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(4))),
child: _buildDiscussionList()),
],
);
}
......@@ -144,7 +164,7 @@ class _DiscussionsPageState extends State<DiscussionsPage> {
_toggleLoading(true);
try {
await _model.saveComment(commentContent).then((value) => Toast.success(context, 'Successful')).catchError((e) => Toast.error(context, 'Failed'));
await _model.getDiscussions(widget.param);
_onRefresh();
} finally {
_toggleLoading(false);
}
......@@ -160,11 +180,12 @@ class _DiscussionsPageState extends State<DiscussionsPage> {
});
}
Widget _buildIssueReplyView() {
Widget _buildIssueReplyView(double bottomActionBoxHeight) {
return Align(
alignment: Alignment.bottomCenter,
child: Container(
width: double.infinity,
height: bottomActionBoxHeight,
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: const BoxDecoration(color: Color(0xF8F8FAFF), border: Border(top: BorderSide(width: 0.2, color: Color(0xFFCECECE)))),
child: Row(children: [
......@@ -223,42 +244,38 @@ class _DiscussionsPageState extends State<DiscussionsPage> {
}
Widget _buildIssueDetailView() {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.fromLTRB(12, 12, 12, 0),
constraints: const BoxConstraints(minHeight: 50),
decoration: const BoxDecoration(color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(4))),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Row(children: [
Expanded(child: Text.rich(TextSpan(text: _model.issueDetail.title, style: const TextStyle(fontSize: 18, color: Color(0xFF1A1B36), fontWeight: FontWeight.w600)))),
]),
Markdown(
padding: const EdgeInsets.only(left: 0, top: 12, right: 16, bottom: 16),
shrinkWrap: true,
data: _model.issueDetail.description,
selectable: true,
physics: const NeverScrollableScrollPhysics(),
),
Row(
children: [
Text(_model.issueDetail.recentActivities(), style: const TextStyle(color: Color(0xFF87878C), fontWeight: FontWeight.w400, fontSize: 12)),
ChangeableTime.show(() => setState(() {}), true, _model.issueDetail.recentActivityTimes(), const Color(0xFF87878C), FontWeight.w400, 12)
],
),
return Container(
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.fromLTRB(12, 12, 12, 0),
constraints: const BoxConstraints(minHeight: 50),
decoration: const BoxDecoration(color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(4))),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Row(children: [
Expanded(child: Text.rich(TextSpan(text: _model.issueDetail.title, style: const TextStyle(fontSize: 18, color: Color(0xFF1A1B36), fontWeight: FontWeight.w600)))),
]),
Markdown(
padding: const EdgeInsets.only(left: 0, top: 12, right: 16, bottom: 16),
shrinkWrap: true,
data: _model.issueDetail.description,
selectable: true,
physics: const NeverScrollableScrollPhysics(),
),
Row(
children: [
Text(_model.issueDetail.recentActivities(), style: const TextStyle(color: Color(0xFF87878C), fontWeight: FontWeight.w400, fontSize: 12)),
ChangeableTime.show(() => setState(() {}), true, _model.issueDetail.recentActivityTimes(), const Color(0xFF87878C), FontWeight.w400, 12)
],
),
)
],
],
),
);
}
Widget _buildDiscussionList() {
return ListView.separated(
key: const Key('DiscussionListView'),
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
Discussion comment = _model.comments[index];
......
......@@ -434,6 +434,13 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.6.2"
intl:
dependency: "direct main"
description:
name: intl
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.17.0"
io:
dependency: transitive
description:
......
此差异已折叠。
此差异已折叠。
Map<String, dynamic> issueData = {
"id": 265567,
"iid": 6,
"project_id": 72936,
"title": "回复评论",
"description": "内容\n\n@perity",
"state": "opened",
"created_at": "2022-11-25T14:19:30.461+08:00",
"updated_at": "2022-12-09T14:10:31.987+08:00",
"closed_at": null,
"closed_by": null,
"labels": [],
"milestone": null,
"assignees": [
{"id": 29355, "username": "perity", "name": "miaolu", "state": "active", "avatar_url": "https://jihulab.com/uploads/-/system/user/avatar/29355/avatar.png", "web_url": "https://jihulab.com/perity"}
],
"author": {
"id": 29355,
"username": "perity",
"name": "miaolu",
"state": "active",
"avatar_url": "https://jihulab.com/uploads/-/system/user/avatar/29355/avatar.png",
"web_url": "https://jihulab.com/perity"
},
"type": "ISSUE",
"assignee": {
"id": 29355,
"username": "perity",
"name": "miaolu",
"state": "active",
"avatar_url": "https://jihulab.com/uploads/-/system/user/avatar/29355/avatar.png",
"web_url": "https://jihulab.com/perity"
},
"user_notes_count": 21,
"merge_requests_count": 0,
"upvotes": 0,
"downvotes": 0,
"due_date": null,
"confidential": false,
"discussion_locked": null,
"issue_type": "issue",
"web_url": "https://jihulab.com/ultimate-plan/jihu-gitlab-app/demo-mr-test/-/issues/6",
"time_stats": {"time_estimate": 0, "total_time_spent": 0, "human_time_estimate": null, "human_total_time_spent": null},
"task_completion_status": {"count": 0, "completed_count": 0},
"weight": null,
"blocking_issues_count": 0,
"has_tasks": false,
"_links": {
"self": "https://jihulab.com/api/v4/projects/72936/issues/6",
"notes": "https://jihulab.com/api/v4/projects/72936/issues/6/notes",
"award_emoji": "https://jihulab.com/api/v4/projects/72936/issues/6/award_emoji",
"project": "https://jihulab.com/api/v4/projects/72936",
"closed_as_duplicate_of": null
},
"references": {"short": "#6", "relative": "#6", "full": "ultimate-plan/jihu-gitlab-app/demo-mr-test#6"},
"severity": "UNKNOWN",
"subscribed": true,
"moved_to_id": null,
"service_desk_reply_to": null,
"epic_iid": null,
"epic": null,
"iteration": null,
"health_status": null
};
Map<String, dynamic> projectData = {
"id": 72936,
"description": "内容\n\n@perity",
"name": "demo mr test",
"name_with_namespace": "旗舰版演示 / 极狐 GitLab App 产品线 / demo mr test",
"path": "demo-mr-test",
"path_with_namespace": "ultimate-plan/jihu-gitlab-app/demo-mr-test",
"created_at": "2022-11-23T20:47:10.076+08:00",
"default_branch": "main",
"tag_list": [],
"topics": [],
"ssh_url_to_repo": "git@jihulab.com:ultimate-plan/jihu-gitlab-app/demo-mr-test.git",
"http_url_to_repo": "https://jihulab.com/ultimate-plan/jihu-gitlab-app/demo-mr-test.git",
"web_url": "https://jihulab.com/ultimate-plan/jihu-gitlab-app/demo-mr-test",
"readme_url": "https://jihulab.com/ultimate-plan/jihu-gitlab-app/demo-mr-test/-/blob/main/README.md",
"avatar_url": null,
"forks_count": 0,
"star_count": 0,
"last_activity_at": "2022-12-09T14:10:32.321+08:00",
"namespace": {
"id": 88966,
"name": "极狐 GitLab App 产品线",
"path": "jihu-gitlab-app",
"kind": "group",
"full_path": "ultimate-plan/jihu-gitlab-app",
"parent_id": 14276,
"avatar_url": null,
"web_url": "https://jihulab.com/groups/ultimate-plan/jihu-gitlab-app"
},
"container_registry_image_prefix": "registry.jihulab.com/ultimate-plan/jihu-gitlab-app/demo-mr-test",
"_links": {
"self": "https://jihulab.com/api/v4/projects/72936",
"issues": "https://jihulab.com/api/v4/projects/72936/issues",
"merge_requests": "https://jihulab.com/api/v4/projects/72936/merge_requests",
"repo_branches": "https://jihulab.com/api/v4/projects/72936/repository/branches",
"labels": "https://jihulab.com/api/v4/projects/72936/labels",
"events": "https://jihulab.com/api/v4/projects/72936/events",
"members": "https://jihulab.com/api/v4/projects/72936/members",
"cluster_agents": "https://jihulab.com/api/v4/projects/72936/cluster_agents"
},
"packages_enabled": true,
"empty_repo": false,
"archived": false,
"visibility": "private",
"resolve_outdated_diff_discussions": false,
"container_expiration_policy": {"cadence": "1d", "enabled": false, "keep_n": 10, "older_than": "90d", "name_regex": ".*", "name_regex_keep": null, "next_run_at": "2022-11-24T20:47:10.096+08:00"},
"issues_enabled": true,
"merge_requests_enabled": true,
"wiki_enabled": true,
"jobs_enabled": true,
"snippets_enabled": true,
"container_registry_enabled": true,
"service_desk_enabled": true,
"service_desk_address": "contact-project+ultimate-plan-jihu-gitlab-app-demo-mr-test-72936-issue-@mg.jihulab.com",
"can_create_merge_request_in": true,
"issues_access_level": "private",
"repository_access_level": "private",
"merge_requests_access_level": "private",
"forking_access_level": "enabled",
"wiki_access_level": "private",
"builds_access_level": "private",
"snippets_access_level": "private",
"pages_access_level": "private",
"operations_access_level": "enabled",
"analytics_access_level": "private",
"container_registry_access_level": "private",
"security_and_compliance_access_level": "private",
"releases_access_level": "private",
"emails_disabled": false,
"shared_runners_enabled": true,
"lfs_enabled": true,
"creator_id": 23837,
"import_status": "none",
"open_issues_count": 13,
"ci_default_git_depth": 20,
"ci_forward_deployment_enabled": true,
"ci_job_token_scope_enabled": false,
"ci_separated_caches": true,
"ci_opt_in_jwt": false,
"ci_allow_fork_pipelines_to_run_in_parent_project": true,
"public_jobs": true,
"build_timeout": 3600,
"auto_cancel_pending_pipelines": "enabled",
"ci_config_path": "",
"shared_with_groups": [],
"only_allow_merge_if_pipeline_succeeds": false,
"allow_merge_on_skipped_pipeline": null,
"restrict_user_defined_variables": false,
"request_access_enabled": true,
"only_allow_merge_if_all_discussions_are_resolved": false,
"remove_source_branch_after_merge": true,
"printing_merge_request_link_enabled": true,
"merge_method": "merge",
"squash_option": "default_off",
"enforce_auth_checks_on_uploads": false,
"suggestion_commit_message": null,
"merge_commit_template": null,
"squash_commit_template": null,
"issue_branch_template": null,
"auto_devops_enabled": true,
"auto_devops_deploy_strategy": "continuous",
"autoclose_referenced_issues": true,
"keep_latest_artifact": true,
"runner_token_expiration_interval": null,
"approvals_before_merge": 0,
"mirror": false,
"external_authorization_classification_label": null,
"marked_for_deletion_at": null,
"marked_for_deletion_on": null,
"requirements_enabled": true,
"requirements_access_level": "private",
"security_and_compliance_enabled": true,
"compliance_frameworks": [],
"issues_template": null,
"merge_requests_template": null,
"merge_pipelines_enabled": false,
"merge_trains_enabled": false,
"only_allow_merge_if_all_status_checks_passed": false,
"permissions": {
"project_access": null,
"group_access": {"access_level": 30, "notification_level": 3}
}
};
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册