This commit is contained in:
parent
c272d8c888
commit
e437f99a79
7 changed files with 486 additions and 11 deletions
55
lib/cubit/issues_bloc.dart
Normal file
55
lib/cubit/issues_bloc.dart
Normal file
|
@ -0,0 +1,55 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:gitea_client/model/issues.dart';
|
||||
import 'package:stream_transform/stream_transform.dart';
|
||||
|
||||
import '../service/gitea_service.dart';
|
||||
|
||||
part 'issues_event.dart';
|
||||
part 'issues_state.dart';
|
||||
|
||||
class IssuesBloc extends Bloc<IssuesEvent, IssueState> {
|
||||
final GiteaService giteaService;
|
||||
final String istate;
|
||||
final String repoFullName;
|
||||
IssuesBloc(this.giteaService,this.repoFullName,this.istate) : super(const IssueState()) {
|
||||
on<IssuesFetched>(_onIssuesFetched,
|
||||
transformer: (events, mapper) => events.switchMap(mapper),);
|
||||
}
|
||||
|
||||
Future<void> _onIssuesFetched(IssuesFetched event, Emitter<IssueState> emit) async {
|
||||
if (state.hasReachedMax) return;
|
||||
try {
|
||||
if (state.status == IssueStatus.initial) {
|
||||
final issues = await giteaService.getRepoIssues(repoFullname: repoFullName,state: istate,page: 1, limit: 10);
|
||||
return emit(state.copyWith(
|
||||
status: IssueStatus.success,
|
||||
issues: issues,
|
||||
loadedPages: 1,
|
||||
hasReachedMax: false,
|
||||
error_message: null,
|
||||
));
|
||||
}
|
||||
final issues = await giteaService.getRepoIssues(repoFullname: repoFullName,state: istate,page: state.loadedPages+1, limit: 10);
|
||||
final issueList = List.of(state.issues);
|
||||
issues.forEach((element) {
|
||||
if(issueList.where((selement) => selement.id == element.id).isEmpty) {
|
||||
issueList.add(element);
|
||||
}
|
||||
});
|
||||
emit(issues.isEmpty
|
||||
? state.copyWith(hasReachedMax: true)
|
||||
: state.copyWith(
|
||||
status: IssueStatus.success,
|
||||
issues: issueList,
|
||||
loadedPages: state.loadedPages+1,
|
||||
hasReachedMax: false,
|
||||
error_message: null,
|
||||
));
|
||||
} on Exception catch (e) {
|
||||
emit(state.copyWith(status: IssueStatus.failure,error_message: e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
9
lib/cubit/issues_event.dart
Normal file
9
lib/cubit/issues_event.dart
Normal file
|
@ -0,0 +1,9 @@
|
|||
part of 'issues_bloc.dart';
|
||||
|
||||
abstract class IssuesEvent extends Equatable {
|
||||
const IssuesEvent();
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class IssuesFetched extends IssuesEvent {}
|
39
lib/cubit/issues_state.dart
Normal file
39
lib/cubit/issues_state.dart
Normal file
|
@ -0,0 +1,39 @@
|
|||
part of 'issues_bloc.dart';
|
||||
|
||||
enum IssueStatus { initial, success, failure }
|
||||
|
||||
class IssueState extends Equatable {
|
||||
const IssueState({
|
||||
this.status = IssueStatus.initial,
|
||||
this.issues = const <Issue>[],
|
||||
this.loadedPages = 0,
|
||||
this.hasReachedMax = false,
|
||||
this.error_message = null
|
||||
});
|
||||
|
||||
final IssueStatus status;
|
||||
final List<Issue> issues;
|
||||
final int loadedPages;
|
||||
final bool hasReachedMax;
|
||||
final String? error_message;
|
||||
|
||||
IssueState copyWith({
|
||||
IssueStatus? status,
|
||||
List<Issue>? issues,
|
||||
int? loadedPages,
|
||||
bool? hasReachedMax,
|
||||
String? error_message,
|
||||
}) {
|
||||
return IssueState(
|
||||
status: status ?? this.status,
|
||||
issues: issues ?? this.issues,
|
||||
loadedPages: loadedPages ?? this.loadedPages,
|
||||
hasReachedMax: hasReachedMax ?? this.hasReachedMax,
|
||||
error_message: error_message ?? this.error_message,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
List<Object> get props => [status, issues, hasReachedMax];
|
||||
}
|
339
lib/model/issues.dart
Normal file
339
lib/model/issues.dart
Normal file
|
@ -0,0 +1,339 @@
|
|||
import 'package:gitea_client/model/user.dart';
|
||||
|
||||
class Issue {
|
||||
int? id;
|
||||
String? url;
|
||||
String? htmlUrl;
|
||||
int? number;
|
||||
_User? user;
|
||||
String? originalAuthor;
|
||||
int? originalAuthorId;
|
||||
String? title;
|
||||
String? body;
|
||||
String? ref;
|
||||
List<Labels>? labels;
|
||||
Milestone? milestone;
|
||||
_User? assignee;
|
||||
List<_User>? assignees;
|
||||
String? state;
|
||||
bool? isLocked;
|
||||
int? comments;
|
||||
String? createdAt;
|
||||
String? updatedAt;
|
||||
String? closedAt;
|
||||
String? dueDate;
|
||||
String? pullRequest;
|
||||
_Repository? repository;
|
||||
|
||||
Issue(
|
||||
{this.id,
|
||||
this.url,
|
||||
this.htmlUrl,
|
||||
this.number,
|
||||
this.user,
|
||||
this.originalAuthor,
|
||||
this.originalAuthorId,
|
||||
this.title,
|
||||
this.body,
|
||||
this.ref,
|
||||
this.labels,
|
||||
this.milestone,
|
||||
this.assignee,
|
||||
this.assignees,
|
||||
this.state,
|
||||
this.isLocked,
|
||||
this.comments,
|
||||
this.createdAt,
|
||||
this.updatedAt,
|
||||
this.closedAt,
|
||||
this.dueDate,
|
||||
this.pullRequest,
|
||||
this.repository});
|
||||
|
||||
Issue.fromJson(Map<String, dynamic> json) {
|
||||
id = json['id'];
|
||||
url = json['url'];
|
||||
htmlUrl = json['html_url'];
|
||||
number = json['number'];
|
||||
user = json['user'] != null ? _User.fromJson(json['user']) : null;
|
||||
originalAuthor = json['original_author'];
|
||||
originalAuthorId = json['original_author_id'];
|
||||
title = json['title'];
|
||||
body = json['body'];
|
||||
ref = json['ref'];
|
||||
if (json['labels'] != null) {
|
||||
labels = <Labels>[];
|
||||
json['labels'].forEach((v) {
|
||||
labels!.add(Labels.fromJson(v));
|
||||
});
|
||||
}
|
||||
milestone = json['milestone'] != null
|
||||
? Milestone.fromJson(json['milestone'])
|
||||
: null;
|
||||
assignee =
|
||||
json['assignee'] != null ? _User.fromJson(json['assignee']) : null;
|
||||
if (json['assignees'] != null) {
|
||||
assignees = <_User>[];
|
||||
json['assignees'].forEach((v) {
|
||||
assignees!.add(_User.fromJson(v));
|
||||
});
|
||||
}
|
||||
state = json['state'];
|
||||
isLocked = json['is_locked'];
|
||||
comments = json['comments'];
|
||||
createdAt = json['created_at'];
|
||||
updatedAt = json['updated_at'];
|
||||
closedAt = json['closed_at'];
|
||||
dueDate = json['due_date'];
|
||||
pullRequest = json['pull_request'];
|
||||
repository = json['repository'] != null
|
||||
? _Repository.fromJson(json['repository'])
|
||||
: null;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['id'] = id;
|
||||
data['url'] = url;
|
||||
data['html_url'] = htmlUrl;
|
||||
data['number'] = number;
|
||||
if (user != null) {
|
||||
data['user'] = user!.toJson();
|
||||
}
|
||||
data['original_author'] = originalAuthor;
|
||||
data['original_author_id'] = originalAuthorId;
|
||||
data['title'] = title;
|
||||
data['body'] = body;
|
||||
data['ref'] = ref;
|
||||
if (labels != null) {
|
||||
data['labels'] = labels!.map((v) => v.toJson()).toList();
|
||||
}
|
||||
if (milestone != null) {
|
||||
data['milestone'] = milestone!.toJson();
|
||||
}
|
||||
if (assignee != null) {
|
||||
data['assignee'] = assignee!.toJson();
|
||||
}
|
||||
if (assignees != null) {
|
||||
data['assignees'] = assignees!.map((v) => v.toJson()).toList();
|
||||
}
|
||||
data['state'] = state;
|
||||
data['is_locked'] = isLocked;
|
||||
data['comments'] = comments;
|
||||
data['created_at'] = createdAt;
|
||||
data['updated_at'] = updatedAt;
|
||||
data['closed_at'] = closedAt;
|
||||
data['due_date'] = dueDate;
|
||||
data['pull_request'] = pullRequest;
|
||||
if (repository != null) {
|
||||
data['repository'] = repository!.toJson();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class _User {
|
||||
int? id;
|
||||
String? login;
|
||||
String? fullName;
|
||||
String? email;
|
||||
String? avatarUrl;
|
||||
String? language;
|
||||
bool? isAdmin;
|
||||
String? lastLogin;
|
||||
String? created;
|
||||
bool? restricted;
|
||||
bool? active;
|
||||
bool? prohibitLogin;
|
||||
String? location;
|
||||
String? website;
|
||||
String? description;
|
||||
String? visibility;
|
||||
int? followersCount;
|
||||
int? followingCount;
|
||||
int? starredReposCount;
|
||||
String? username;
|
||||
|
||||
_User(
|
||||
{this.id,
|
||||
this.login,
|
||||
this.fullName,
|
||||
this.email,
|
||||
this.avatarUrl,
|
||||
this.language,
|
||||
this.isAdmin,
|
||||
this.lastLogin,
|
||||
this.created,
|
||||
this.restricted,
|
||||
this.active,
|
||||
this.prohibitLogin,
|
||||
this.location,
|
||||
this.website,
|
||||
this.description,
|
||||
this.visibility,
|
||||
this.followersCount,
|
||||
this.followingCount,
|
||||
this.starredReposCount,
|
||||
this.username});
|
||||
|
||||
_User.fromJson(Map<String, dynamic> json) {
|
||||
id = json['id'];
|
||||
login = json['login'];
|
||||
fullName = json['full_name'];
|
||||
email = json['email'];
|
||||
avatarUrl = json['avatar_url'];
|
||||
language = json['language'];
|
||||
isAdmin = json['is_admin'];
|
||||
lastLogin = json['last_login'];
|
||||
created = json['created'];
|
||||
restricted = json['restricted'];
|
||||
active = json['active'];
|
||||
prohibitLogin = json['prohibit_login'];
|
||||
location = json['location'];
|
||||
website = json['website'];
|
||||
description = json['description'];
|
||||
visibility = json['visibility'];
|
||||
followersCount = json['followers_count'];
|
||||
followingCount = json['following_count'];
|
||||
starredReposCount = json['starred_repos_count'];
|
||||
username = json['username'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['id'] = id;
|
||||
data['login'] = login;
|
||||
data['full_name'] = fullName;
|
||||
data['email'] = email;
|
||||
data['avatar_url'] = avatarUrl;
|
||||
data['language'] = language;
|
||||
data['is_admin'] = isAdmin;
|
||||
data['last_login'] = lastLogin;
|
||||
data['created'] = created;
|
||||
data['restricted'] = restricted;
|
||||
data['active'] = active;
|
||||
data['prohibit_login'] = prohibitLogin;
|
||||
data['location'] = location;
|
||||
data['website'] = website;
|
||||
data['description'] = description;
|
||||
data['visibility'] = visibility;
|
||||
data['followers_count'] = followersCount;
|
||||
data['following_count'] = followingCount;
|
||||
data['starred_repos_count'] = starredReposCount;
|
||||
data['username'] = username;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class Labels {
|
||||
int id;
|
||||
String name;
|
||||
String color;
|
||||
String description;
|
||||
String url;
|
||||
|
||||
Labels(
|
||||
{required this.id,
|
||||
required this.name,
|
||||
required this.color,
|
||||
required this.description,
|
||||
required this.url});
|
||||
|
||||
Labels.fromJson(Map<String, dynamic> json)
|
||||
: id = json['id'],
|
||||
name = json['name'],
|
||||
color = json['color'],
|
||||
description = json['description'],
|
||||
url = json['url'];
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['id'] = id;
|
||||
data['name'] = name;
|
||||
data['color'] = color;
|
||||
data['description'] = description;
|
||||
data['url'] = url;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class Milestone {
|
||||
int id;
|
||||
String title;
|
||||
String description;
|
||||
String state;
|
||||
int openIssues;
|
||||
int closedIssues;
|
||||
String createdAt;
|
||||
String updatedAt;
|
||||
String? closedAt;
|
||||
String? dueOn;
|
||||
|
||||
Milestone(
|
||||
{required this.id,
|
||||
required this.title,
|
||||
required this.description,
|
||||
required this.state,
|
||||
required this.openIssues,
|
||||
required this.closedIssues,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
this.closedAt,
|
||||
this.dueOn});
|
||||
|
||||
Milestone.fromJson(Map<String, dynamic> json)
|
||||
: id = json['id'],
|
||||
title = json['title'],
|
||||
description = json['description'],
|
||||
state = json['state'],
|
||||
openIssues = json['open_issues'],
|
||||
closedIssues = json['closed_issues'],
|
||||
createdAt = json['created_at'],
|
||||
updatedAt = json['updated_at'] {
|
||||
closedAt = json['closed_at'];
|
||||
dueOn = json['due_on'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['id'] = id;
|
||||
data['title'] = title;
|
||||
data['description'] = description;
|
||||
data['state'] = state;
|
||||
data['open_issues'] = openIssues;
|
||||
data['closed_issues'] = closedIssues;
|
||||
data['created_at'] = createdAt;
|
||||
data['updated_at'] = updatedAt;
|
||||
data['closed_at'] = closedAt;
|
||||
data['due_on'] = dueOn;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class _Repository {
|
||||
int id;
|
||||
String name;
|
||||
String owner;
|
||||
String fullName;
|
||||
|
||||
_Repository(
|
||||
{required this.id,
|
||||
required this.name,
|
||||
required this.owner,
|
||||
required this.fullName});
|
||||
|
||||
_Repository.fromJson(Map<String, dynamic> json)
|
||||
: id = json['id'],
|
||||
name = json['name'],
|
||||
owner = json['owner'],
|
||||
fullName = json['full_name'];
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['id'] = id;
|
||||
data['name'] = name;
|
||||
data['owner'] = owner;
|
||||
data['full_name'] = fullName;
|
||||
return data;
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ import 'dart:convert';
|
|||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:gitea_client/model/ApiAccess.dart';
|
||||
import 'package:gitea_client/model/issues.dart';
|
||||
import 'package:gitea_client/model/repository.dart';
|
||||
import 'package:gitea_client/model/user.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
@ -53,7 +54,31 @@ class GiteaService {
|
|||
}
|
||||
throw Exception('error fetching files');
|
||||
}
|
||||
//
|
||||
|
||||
Future<List<Issue>> getRepoIssues({required String repoFullname,required String state,int page = 1, int limit = 10}) async{
|
||||
if(state != "all" || state != "closed" || state != "open") {
|
||||
throw Exception("Wrong state provided!");
|
||||
}
|
||||
var response = await http.get(
|
||||
Uri.https(apiAccess.instance, "api/v1/user/repos/$repoFullname/issues", {
|
||||
"token": apiAccess.token,
|
||||
"state": state,
|
||||
"page" : page.toString(),
|
||||
"limit" : limit.toString(),
|
||||
}),
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
final body = json.decode(response.body) as List;
|
||||
var result = body.map((dynamic json) {
|
||||
return Issue.fromJson(json);
|
||||
}).toList();
|
||||
|
||||
result.sort((a,b) => -1*a.updatedAt!.compareTo(b.updatedAt!));
|
||||
return result;
|
||||
}
|
||||
throw Exception('error fetching posts');
|
||||
}
|
||||
|
||||
Future<List<Repository>> getUserRepositories([int page = 1, int limit = 10]) async{
|
||||
var response = await http.get(
|
||||
Uri.https(apiAccess.instance, "api/v1/user/repos", {
|
||||
|
@ -71,7 +96,7 @@ class GiteaService {
|
|||
result.sort((a,b) => -1*a.updatedAt!.compareTo(b.updatedAt!));
|
||||
return result;
|
||||
}
|
||||
throw Exception('error fetching posts');
|
||||
throw Exception('error fetching repos');
|
||||
}
|
||||
|
||||
Future<SearchResult> searchRepo({required String query,int page = 1, int limit = 10}) async{
|
||||
|
|
|
@ -6,6 +6,9 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||
url_launcher_linux
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
||||
foreach(plugin ${FLUTTER_PLUGIN_LIST})
|
||||
|
@ -14,3 +17,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST})
|
|||
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
|
||||
endforeach(plugin)
|
||||
|
||||
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
|
||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
|
||||
endforeach(ffi_plugin)
|
||||
|
|
18
pubspec.lock
18
pubspec.lock
|
@ -105,7 +105,7 @@ packages:
|
|||
name: collection
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.15.0"
|
||||
version: "1.16.0"
|
||||
convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -147,7 +147,7 @@ packages:
|
|||
name: fake_async
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
version: "1.3.0"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -258,7 +258,7 @@ packages:
|
|||
name: js
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.6.3"
|
||||
version: "0.6.4"
|
||||
json_annotation:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -307,7 +307,7 @@ packages:
|
|||
name: material_color_utilities
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.3"
|
||||
version: "0.1.4"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -335,7 +335,7 @@ packages:
|
|||
name: path
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.0"
|
||||
version: "1.8.1"
|
||||
path_drawing:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -501,7 +501,7 @@ packages:
|
|||
name: source_span
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.1"
|
||||
version: "1.8.2"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -543,7 +543,7 @@ packages:
|
|||
name: test_api
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.8"
|
||||
version: "0.4.9"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -613,7 +613,7 @@ packages:
|
|||
name: vector_math
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
version: "2.1.2"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -650,5 +650,5 @@ packages:
|
|||
source: hosted
|
||||
version: "3.1.0"
|
||||
sdks:
|
||||
dart: ">=2.16.2 <3.0.0"
|
||||
dart: ">=2.17.0-0 <3.0.0"
|
||||
flutter: ">=2.10.0"
|
||||
|
|
Loading…
Reference in a new issue