preface
It has been two months since I introduced OpenGit_Flutter in my last article. Within two months, FLUTTER has been completed in version 1.1.0, version 1.2.0, and version 1.3.0, which I will introduce soon. Click here to see the update notes. In v1.3.0, the overall UI was modified to adopt a card-like style; – The login interface has been changed, the UI mainly refers to flutter- uI-nice; Optimized edit issue, comment related logic, and added tag function; Revamped the profile page and added organizational logic. The UI mainly refers to flutter- UI-nice. Added sharing function and so on. Compared with previous versions, v1.3.0 has made major changes in the experience. The following describes the related content of the client.
The main architectures involved in this project can be referred to the attempts of MVC, MVP, BloC and Redux on Flutter.
The main code for the card style in the project is shown below
InkWell(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: _postCard(context, item),
),
onTap: () {
NavigatorUtil.goWebView(context, item.title, item.originalUrl);
},
)
Widget _postCard(BuildContext context) {
return Card(
elevation: 2.0, child: ...... ) ; }Copy the code
Program entrance
void main() {
final store = Store<AppState>(
appReducer,
initialState: AppState.initial(),
middleware: [
LoginMiddleware(),
UserMiddleware(),
AboutMiddleware(),
],
);
runZoned(() {
runApp(OpenGitApp(store));
}, onError: (Object obj, StackTrace trace) {
print(obj);
print(trace);
});
}
Copy the code
In the main method of the program entry, redux-related initialization operations are carried out, and the OpenGitApp page is started. RunZoned captures information such as global exceptions in the running environment to facilitate problem analysis.
Let’s look at the code for the OpenGitApp page, as shown below
class OpenGitApp extends StatefulWidget {
final Store<AppState> store;
OpenGitApp(this.store) {
final router = Router();
AppRoutes.configureRoutes(router);
Application.router = router;
}
@override
State<StatefulWidget> createState() {
return_OpenGitAppState(); }}Copy the code
In the OpenGitApp constructor, the initialization of Fluro routes is completed. Fluro will be introduced later. The definition of a splash page is shown in the code below
static final splash = '/';
router.define(
splash,
handler: splashHandler,
transitionType: TransitionType.cupertino,
);
var splashHandler = Handler(
handlerFunc: (BuildContext context, Map<String.List<String>> params) {
return SplashPage();
});
Copy the code
After the OpenGitApp page initialization function is loaded, the SplashPage page is started by default, and the _OpenGitAppState class initializes the related data, as shown in the following code
class _OpenGitAppState extends State<OpenGitApp> {
static final String TAG = "OpenGitApp";
@override
void initState() {
super.initState(); widget.store.dispatch(InitAction()); }}Copy the code
Initiate the redux initialization data directive InitAction in initState, and when the directive is issued, UserMiddleware receives it and acts on it, as shown in the code below
Future<Null> _init(Store<AppState> store, NextDispatcher next) async {
// The sp initialization is complete
await SpUtil.instance.init();
// Initialize the database and delete it
CacheProvider provider = CacheProvider();
await provider.delete();
/ / theme
int theme = SpUtil.instance.getInt(SP_KEY_THEME_COLOR);
if(theme ! =0) {
Color color = Color(theme);
next(RefreshThemeDataAction(AppTheme.changeTheme(color)));
}
/ / language
int locale = SpUtil.instance.getInt(SP_KEY_LANGUAGE_COLOR);
if(locale ! =0) {
next(RefreshLocalAction(LocaleUtil.changeLocale(store.state, locale)));
}
// User information
String token = SpUtil.instance.getString(SP_KEY_TOKEN);
UserBean userBean = null;
var user = SpUtil.instance.getObject(SP_KEY_USER_INFO);
if(user ! =null) {
LoginManager.instance.setUserBean(user, false);
userBean = UserBean.fromJson(user);
}
LoginManager.instance.setToken(token, false);
/ / guide page
String version =
SpUtil.instance.getString(SP_KEY_SHOW_GUIDE_VERSION);
StringcurrentVersion = Config.SHOW_GUIDE_VERSION; next(InitCompleteAction(token, userBean, currentVersion ! = version));// Initialize local data
ReposManager.instance.initLanguageColors();
}
Copy the code
The splash screen page
After entering the splash screen, redux starts the page countdown operation, as shown in the code below
store.dispatch(StartCountdownAction(context));
Copy the code
When a countdown command is issued, UserMiddleware receives it and acts on it, as shown in the code below
void startCountdown(
Store<AppState> store, NextDispatcher next, BuildContext context) {
TimerUtil.startCountdown(5, (int count) {
next(CountdownAction(count));
if (count == 0) { _jump(context, store.state.userState.status, store.state.userState.isGuide); }}); }Copy the code
Start a 5s countdown with TimerUtil and synchronize the countdown time to the SplashPage page to refresh the countdown time. The TimerUtil tool class is not detailed. See the common library summary of the OpenGit_Flutter project for more details. When the countdown runs out, the user initializes the data state and performs the page jump operation, as shown in the code below
void _jump(BuildContext context, LoginStatus status, bool isShowGuide) {
if (isShowGuide) {
NavigatorUtil.goGuide(context);
} else if (status == LoginStatus.success) {
NavigatorUtil.goMain(context);
} else if(status == LoginStatus.error) { NavigatorUtil.goLogin(context); }}Copy the code
When the user operates the application for the first time, the boot page is displayed. If you have logged in, jump to the main page; if you have not logged in; The login page is displayed.
Guide page
Refer to the animation in flutter_gallery for the code related to the guide page. When you click Play Now, the code looks like this
void _onExperience(BuildContext context) {
Store<AppState> store = StoreProvider.of(context);
LoginStatus status = store.state.userState.status;
if (status == LoginStatus.success) {
NavigatorUtil.goMain(context);
} else if(status == LoginStatus.error) { NavigatorUtil.goLogin(context); }}Copy the code
First, check the login status of the user through redux. If the user is logged in, the home page is redirected; if not, the user is logged in. The login page is displayed.
The login page
The login process includes authorization and obtaining user information. The apis involved are as follows
- Authorized API
POST /authorizations
- Get user profile API
GET /user
When a user does not have an account, he can register an account, as shown in the code below
NavigatorUtil.goWebView(
context,
AppLocalizations.of(context).currentlocal.sign_up,
'https://github.com/');
Copy the code
When the user has an account, complete the account and password input, click login, as shown in the code below
store.dispatch(FetchLoginAction(context, name, password));
Copy the code
When LoginMiddleware receives this command, it triggers a login, as shown in the code below
Future<void> _doLogin(NextDispatcher next, BuildContext context,
String userName, String password) async {
next(RequestingLoginAction());
try {
LoginBean loginBean =
await LoginManager.instance.login(userName, password);
if(loginBean ! =null) {
String token = loginBean.token;
LoginManager.instance.setToken(loginBean.token, true);
UserBean userBean = await LoginManager.instance.getMyUserInfo();
if(userBean ! =null) {
next(InitCompleteAction(token, userBean, false));
next(ReceivedLoginAction(token, userBean));
NavigatorUtil.goMain(context);
} else {
ToastUtil.showMessgae('Login failed please login again');
LoginManager.instance.setToken(null.true); }}else {
ToastUtil.showMessgae('Login failed please login again'); next(ErrorLoadingLoginAction()); }}catch (e) {
LogUtil.v(e, tag: TAG);
ToastUtil.showMessgae('Login failed please login again'); next(ErrorLoadingLoginAction()); }}Copy the code
When logging in, redux issues the command RequestingLoginAction to load the loading interface. After the login is successful, the token information is cached, and the user information is obtained. If the user information is obtained successfully, the login is successful, and the main page is displayed.
The home page
The page loading of the home page adopts TabBar+PageView(careful use of TabBarView) to load the four pages of HOME, REPO, Event and Issue, and the key codes are shown as follows
TabBar(
controller: _tabController,
labelPadding: EdgeInsets.all(8.0),
indicatorColor: Colors.white,
tabs: choices.map((Choice choice) {
returnTab( text: choice.title, ); }).toList(), onTap: (index) { _pageController.jumpTo(ScreenUtil.getScreenWidth(context) * index); },)// Be careful with TabBarView. If you have four tabs, if you first enter the app,
// Click issue TAB and dynamic TAB will also trigger data loading and immediate destructionPageView( controller: _pageController, physics: NeverScrollableScrollPhysics(), children: <Widget>[ BlocProvider<HomeBloc>( child: HomePage(), bloc: _homeBloc, ), BlocProvider<ReposBloc>( child: ReposPage(PageType.repos), bloc: _reposBloc, ), BlocProvider<EventBloc>( child: EventPage(PageType.received_event), bloc: _eventBloc, ), BlocProvider<IssueBloc>( child: IssuePage(), bloc: _issueBloc, ), ], onPageChanged: (index) { _tabController.animateTo(index); },)Copy the code
Home page
The data displayed on the home page is to obtain the list of nuggets to be loaded with flutter. The related API is shown below
GET the timeline – merger – Ms. Juejin. Im/v1 / get_tag_… ‘src=web&tagId=5a96291f6fb9a0535b535438&page=$page&pageSize=20&sort=rankIndex
The relevant code involved is shown below
Future _fetchHomeList() async {
LogUtil.v('_fetchHomeList', tag: TAG);
try {
var result = await JueJinManager.instance.getJueJinList(page);
if (bean.data == null) {
bean.data = List(a); }if (page == 1) {
bean.data.clear();
}
noMore = true;
if(result ! =null) {
bean.isError = false; noMore = result.length ! = Config.PAGE_SIZE; bean.data.addAll(result); }else {
bean.isError = true;
}
sink.add(bean);
} catch (_) {
if(page ! =1) { page--; }}}Copy the code
Click item to jump to the corresponding H5 page, as shown in the code below
NavigatorUtil.goWebView(context, item.title, item.originalUrl)
Copy the code
Project page
The project page displays a list of its own exposed projects, as shown in the API below
GET /users/:username/repos
The relevant code involved is shown below
///repo_bloc.dart
Future _fetchReposList() async {
LogUtil.v('_fetchReposList', tag: TAG);
try {
var result = await fetchRepos(page);
if (bean.data == null) {
bean.data = List(a); }if (page == 1) {
bean.data.clear();
}
noMore = true;
if(result ! =null) {
bean.isError = false; noMore = result.length ! = Config.PAGE_SIZE; bean.data.addAll(result); }else {
bean.isError = true;
}
sink.add(bean);
} catch (_) {
if(page ! =1) { page--; }}}///repo_main_bloc.dart
@override
fetchRepos(int page) async {
return await ReposManager.instance
.getUserRepos(userName, page, null.false);
}
Copy the code
The above code encapsulates the interface related to the request item. The main logic is already handled in repo_bloc. Dart, and the subclass simply implements the fetchRepos method.
Click item to jump to the project details page, as shown in the code below
NavigatorUtil.goReposDetail(context, item.owner.login, item.name);
Copy the code
Dynamic pages
The dynamic page displays a dynamic list of received data, as shown in the API below
GET /users/:username/received_events
The relevant code involved is shown below
///event_bloc.dart
Future _fetchEventList() async {
LogUtil.v('_fetchEventList', tag: TAG);
try {
var result = await fetchEvent(page);
if (bean.data == null) {
bean.data = List(a); }if (page == 1) {
bean.data.clear();
}
noMore = true;
if(result ! =null) {
bean.isError = false; noMore = result.length ! = Config.PAGE_SIZE; bean.data.addAll(result); }else {
bean.isError = true;
}
sink.add(bean);
} catch (_) {
if(page ! =1) { page--; }}}///received_event_Bloc
@override
fetchEvent(int page) async {
return await EventManager.instance.getEventReceived(userName, page);
}
Copy the code
Click item to distinguish different events. If it is related to issue, it will jump to the issue details page; if it is related to project, it will jump to the project details page, as shown in the code below
if(item.payload ! =null&& item.payload.issue ! =null) {
NavigatorUtil.goIssueDetail(context, item.payload.issue);
} else if(item.repo ! =null&& item.repo.name ! =null) {
String repoUser, repoName;
if (item.repo.name.isNotEmpty && item.repo.name.contains("/")) {
List<String> repos = TextUtil.split(item.repo.name, '/');
repoUser = repos[0];
repoName = repos[1];
}
NavigatorUtil.goReposDetail(context, repoUser, repoName);
}
Copy the code
Problem page
The questions page displays a list of received questions, and the API is shown below
GET /issues? filter=:filter&state=:state&sort=:sort&direction=:direction
The relevant code involved is shown below
Future _fetchIssueList() async {
LogUtil.v('_fetchIssueList', tag: TAG);
try {
var result = await IssueManager.instance
.getIssue(filter, state, sort, direction, page);
if (bean.data == null) {
bean.data = List(a); }if (page == 1) {
bean.data.clear();
}
noMore = true;
if(result ! =null) {
bean.isError = false; noMore = result.length ! = Config.PAGE_SIZE; bean.data.addAll(result); }else {
bean.isError = true;
}
sink.add(bean);
} catch (_) {
if(page ! =1) { page--; }}}Copy the code
Click item to jump to the problem details page, as shown in the code below
NavigatorUtil.goIssueDetail(context, item);
Copy the code
Project Details page
When entering the project details page for the first time, the project details and the status of STAR and Watch will be queried. The relevant API is shown below
- Project details
GET /repos/:owner/:repo
- Star status
GET /user/starred/:owner/:repo
- Watch a state
GET /user/subscriptions/:owner/:repo
The relevant code involved is shown below
Future _fetchReposDetail() async {
final repos =
await ReposManager.instance.getReposDetail(reposOwner, reposName);
bean.data.repos = repos;
if (repos == null) {
bean.isError = true;
} else {
bean.isError = false;
}
sink.add(bean);
_fetchStarStatus();
_fetchWatchStatus();
}
Future _fetchStarStatus() async {
final response =
await ReposManager.instance.getReposStar(reposOwner, reposName);
bean.data.starStatus =
response.result ? ReposStatus.active : ReposStatus.inactive;
sink.add(bean);
}
Future _fetchWatchStatus() async {
final response =
await ReposManager.instance.getReposWatcher(reposOwner, reposName);
bean.data.watchStatus =
response.result ? ReposStatus.active : ReposStatus.inactive;
sink.add(bean);
}
Copy the code
Change the status of star and watch. Add relevant apis as follows
- Star status
PUT /user/starred/:owner/:repo
- Watch a state
PUT /user/subscriptions/:owner/:repo
delete
- Star status
DELETE /user/starred/:owner/:repo
- Watch a state
DELETE /user/subscriptions/:owner/:repo
The relevant code involved is shown below
void changeStarStatus() async {
bool isEnable = bean.data.starStatus == ReposStatus.active;
bean.data.starStatus = ReposStatus.loading;
sink.add(bean);
final response = await ReposManager.instance
.doReposStarAction(reposOwner, reposName, isEnable);
if (response.result) {
if (isEnable) {
bean.data.starStatus = ReposStatus.inactive;
} else {
bean.data.starStatus = ReposStatus.active;
}
}
sink.add(bean);
}
void changeWatchStatus() async {
bool isEnable = bean.data.watchStatus == ReposStatus.active;
bean.data.watchStatus = ReposStatus.loading;
sink.add(bean);
final response = await ReposManager.instance
.doReposWatcherAction(reposOwner, reposName, isEnable);
if (response.result) {
if (isEnable) {
bean.data.watchStatus = ReposStatus.inactive;
} else {
bean.data.watchStatus = ReposStatus.active;
}
}
sink.add(bean);
}
Copy the code
The above code requests DELETE if isEnable is true, and PUT if not
List of project stars users
The related apis are shown below
GET /repos/:owner/:repo/stargazers
The relevant code involved is shown below
///user_bloc.dart
Future _fetchUserList() async {
LogUtil.v('_fetchUserList', tag: TAG);
try {
var result = await fetchList(page);
if (bean.data == null) {
bean.data = List(a); }if (page == 1) {
bean.data.clear();
}
noMore = true;
if(result ! =null) {
bean.isError = false; noMore = result.length ! = Config.PAGE_SIZE; bean.data.addAll(result); }else {
bean.isError = true;
}
sink.add(bean);
} catch (_) {
if(page ! =1) { page--; }}}///stargazer_bloc.dart
@override
fetchList(int page) async {
return await UserManager.instance.getStargazers(url, page);
}
Copy the code
The above code encapsulates the requested user-specific interface. The main logic is already processed in user_Bloc. Dart, and subclass Stargazer_bloc inherits user_Bloc, simply implementing the fetchList method
List of Project Issues
The related apis are shown below
GET repos/:owner/:repo/issues
The relevant code involved is shown below
Future _fetchIssueList() async {
LogUtil.v('_fetchIssueList', tag: TAG);
try {
var result = await IssueManager.instance.getRepoIssues(owner, repo, page);
if (bean.data == null) {
bean.data = List(a); }if (page == 1) {
bean.data.clear();
}
noMore = true;
if(result ! =null) {
bean.isError = false; noMore = result.length ! = Config.PAGE_SIZE; bean.data.addAll(result); }else {
bean.isError = true;
}
sink.add(bean);
} catch (_) {
if(page ! =1) { page--; }}}Copy the code
List of project Forks users
The related apis are shown below
GET repos/:owner/:repo/forks
The relevant code involved is shown below
@override
fetchList(int page) async {
return await ReposManager.instance.getRepoForks(owner, repo, page);
}
Copy the code
Since repo_fork_bloc inherits user_bloc, all you need to do is implement the fetchList, as you can see above
Project Watchers uses lists
The related apis are shown below
GET repos/:owner/:repo/subscribers
The relevant code involved is shown below
@override
fetchList(int page) async {
return await UserManager.instance.getSubscribers(url, page);
}
Copy the code
Since subscriber_bloc inherits user_bloc, only fetchList can be implemented. The details can be seen above
List of project language trends
The related apis are shown below
GET search/repositories? q=language:$language&sort=stars
The relevant code involved is shown below
Future _fetchTrendList() async {
try {
var result = await ReposManager.instance.getLanguages(language, page);
if (bean.data == null) {
bean.data = List(a); }if (page == 1) {
bean.data.clear();
}
noMore = true;
if(result ! =null) {
bean.isError = false; noMore = result.length ! = Config.PAGE_SIZE; bean.data.addAll(result); }else {
bean.isError = true;
}
sink.add(bean);
} catch (_) {
if(page ! =1) { page--; }}}Copy the code
Dynamic list of items
The related apis are shown below
GET networks/:owner/:repo/events
The relevant code involved is shown below
Future _fetchEventList() async {
try {
var result = await ReposManager.instance
.getReposEvents(reposOwner, reposName, page);
if (bean.data == null) {
bean.data = List(a); }if (page == 1) {
bean.data.clear();
}
noMore = true;
if(result ! =null) {
bean.isError = false; noMore = result.length ! = Config.PAGE_SIZE; bean.data.addAll(result); }else {
bean.isError = true;
}
sink.add(bean);
} catch (_) {
if(page ! =1) { page--; }}}Copy the code
List of project contributor users
The related apis are shown below
GET repos/:owner/:repo/contributors
The relevant code involved is shown below
@override
fetchList(int page) async {
return await UserManager.instance.getContributors(url, page);
}
Copy the code
Since contributor_bloc inherits user_bloc, you can simply implement the fetchList, the details of which can be seen above
Project Branch list
The related apis are shown below
GET repos/repo/branches
The relevant code involved is shown below
void fetchBranches() async {
final response =
await ReposManager.instance.getBranches(reposOwner, reposName);
bean.data.branchs = response;
sink.add(bean);
}
Copy the code
List of Project Details
The related apis are shown below
GET repos/:owner/:repo/contents:path
The relevant code involved is shown below
Future _fetchSourceFile() async {
String path = _getPath();
final result = await ReposManager.instance
.getReposFileDir(reposOwner, reposName, path: path, branch: branch);
if (bean.data == null) {
bean.data = List(a); } bean.data.clear();if(result ! =null) {
bean.isError = false;
bean.data.addAll(result);
} else {
bean.isError = true;
}
sink.add(bean);
}
Copy the code
Click the details list to distinguish folders, pictures and file details, as shown in the code below
void _onItemClick(BuildContext context, SourceFileBean item) {
bool isImage = ImageUtil.isImage(item.name);
if (item.type == "dir") {
RepoFileBloc bloc = BlocProvider.of<RepoFileBloc>(context);
bloc.fetchNextDir(item.name);
} else if (isImage) {
NavigatorUtil.goPhotoView(context, item.name, item.htmlUrl + "? raw=true");
} else{ NavigatorUtil.goReposSourceCode(context, item.name, ImageUtil.isImage(item.url) ? item.downloadUrl : item.url); }}Copy the code
If it is a folder, refresh the directory file list. If there is an image state, call the image load page. See the summary of common libraries for OpenGit_Flutter projects. If it is in the details state, jump to the details page for processing, as shown below
Project Details page
The relevant code involved is shown below
getCodeDetail(url) async {
final response =
await _getFileAsStream(url, {"Accept": 'application/vnd.github.html'});
String data = CodeDetailUtil.resolveHtmlFile(response, "java");
String result = Uri.dataFromString(data,
mimeType: 'text/html', encoding: Encoding.getByName("utf-8"))
.toString();
return result;
}
Widget build(BuildContext context) {
if (data == null) {
return Scaffold(
appBar:CommonUtil.getAppBar(widget.title),
body: Container(
alignment: Alignment.center,
child: Center(
child: SpinKitCircle(
color: Theme.of(context).primaryColor,
size: 25.0(), (), (), (); }return Scaffold(
appBar: CommonUtil.getAppBar(widget.title),
body: WebView(
initialUrl: data,
javascriptMode: JavascriptMode.unrestricted,
),
);
}
Copy the code
The project README
The related apis are shown below
GET repos/:owner/:repo/readme
The relevant code involved is shown below
void fetchReadme() async {
final response =
await ReposManager.instance.getReadme("$reposOwner/$reposName".null);
bean.data.readme = response.data;
sink.add(bean);
}
Copy the code
Browser open
The relevant code involved is shown below
@override
void openWebView(BuildContext context) {
RepoDetailBloc bloc = BlocProvider.of<RepoDetailBloc>(context);
NavigatorUtil.goWebView(
context, bloc.reposName, bloc.bean.data.repos.htmlUrl);
}
Copy the code
share
The relevant code involved is shown below
void _share(BuildContext context) {
ShareUtil.share(getShareText(context));
}
@override
String getShareText(BuildContext context) {
RepoDetailBloc bloc = BlocProvider.of<RepoDetailBloc>(context);
return bloc.bean.data.repos.htmlUrl;
}
Copy the code
Issue Details page
When you enter the issue details page for the first time, the issue details and the comment list are queried, as shown in the following API
- Details on the problem
GET /repos/:owner/:repo/issues/:issue_number
- Comment on the list
GET /repos/:owner/:repo/issues/:issue_number/comments
The relevant code involved is shown below
void _fetchIssueComment() async {
IssueBean result =
await IssueManager.instance.getSingleIssue(url, num);
bean.data.issueBean = result;
}
Future _fetchIssueComments() async {
try {
var result = await IssueManager.instance
.getIssueComment(url, num, page);
if (bean.data == null) {
bean.data.comments = List(a); }if (page == 1) {
bean.data.comments.clear();
}
noMore = true;
if(result ! =null) { noMore = result.length ! = Config.PAGE_SIZE; bean.data.comments.addAll(result); }else {
bean.isError = true;
}
sink.add(bean);
} catch (_) {
if(page ! =1) { page--; }}}Copy the code
Add comments
The related apis are shown below
POST /repos/:owner/:repo/issues/:issue_number/comments
The relevant code involved is shown below
_editIssueComment() async {
IssueBean result = null;
_showLoading();
if(! widget.isAdd) { result =await IssueManager.instance.editIssueComment(
widget.repoUrl, widget.id, _controller.text.toString());
} else {
result = await IssueManager.instance.addIssueComment(
widget.repoUrl, widget.id, _controller.text.toString());
}
_hideLoading();
if(result ! =null) { Navigator.pop(context, result); }}Copy the code
When adding comments, widget.isAdd = true
Editorial comment
The related apis are shown below
PATCH /repos/:owner/:repo/issues/:issue_number/comments
The relevant code involved is shown below
_editIssueComment() async {
IssueBean result = null;
_showLoading();
if(! widget.isAdd) { result =await IssueManager.instance.editIssueComment(
widget.repoUrl, widget.id, _controller.text.toString());
} else {
result = await IssueManager.instance.addIssueComment(
widget.repoUrl, widget.id, _controller.text.toString());
}
_hideLoading();
if(result ! =null) { Navigator.pop(context, result); }}Copy the code
Widget.isadd = false when editing comments
Delete the comment
The related apis are shown below
DELETE /repos/:owner/:repo/issues/:issue_number/comments
The relevant code involved is shown below
void deleteIssueComment(IssueBean item) async {
showLoading();
int comment_id = item.id;
final response =
await IssueManager.instance.deleteIssueComment(url, comment_id);
if(response ! =null && response.result) {
bean.data.comments.remove(item);
sink.add(bean);
}
hideLoading();
}
Copy the code
Editing problems
The related apis are shown below
PATCH /repos/:owner/:repo/issues/:issue_number
The relevant code involved is shown below
_editIssue() async {
_showLoading();
final result = await IssueManager.instance.editIssue(widget.url, widget.num,
_titleController.text.toString(), _bodyController.text.toString());
_hideLoading();
if(result ! =null) { Navigator.pop(context, result); }}Copy the code
Reactions
Question Face List
The related apis are shown below
GET /repos/:owner/:repo/issues/:issue_number/reactions
The relevant code involved is shown below
_queryIssueCommentReaction(IssueBean item, comment, isIssue) async {
int id;
if (isIssue) {
id = item.number;
} else {
id = item.id;
}
final response = await IssueManager.instance
.getCommentReactions(url, id, comment, 1, isIssue);
ReactionDetailBean findReaction = null;
if(response ! =null) {
UserBean userBean = LoginManager.instance.getUserBean();
for (int i = 0; i < response.length; i++) {
ReactionDetailBean reactionDetailBean = response[i];
if(reactionDetailBean ! =null&& reactionDetailBean.content == comment && userBean ! =null&& reactionDetailBean.user ! =null &&
userBean.login == reactionDetailBean.user.login) {
findReaction = reactionDetailBean;
break; }}}if(findReaction ! =null) {
return await _deleteIssueCommentReaction(item, findReaction, comment);
} else {
return await_createIssueCommentReaction(item, comment, isIssue); }}Copy the code
Add a Problem Reaction
The related apis are shown below
POST /repos/:owner/:repo/issues/:issue_number/reactions
The relevant code involved is shown below
_createIssueCommentReaction(IssueBean item, comment, isIssue) async {
int id;
if (isIssue) {
id = item.number;
} else {
id = item.id;
}
final response =
await IssueManager.instance.editReactions(url, id, comment, isIssue);
if(response ! =null && response.result) {
_addIssueBean(item, comment);
sink.add(bean);
}
return response;
}
IssueBean _addIssueBean(IssueBean issueBean, String comment) {
if (issueBean.reaction == null) {
issueBean.reaction = ReactionBean(' '.0.0.0.0.0.0.0.0.0);
}
if ("+ 1" == comment) {
issueBean.reaction.like++;
} else if ("1" == comment) {
issueBean.reaction.noLike++;
} else if ("hooray" == comment) {
issueBean.reaction.hooray++;
} else if ("eyes" == comment) {
issueBean.reaction.eyes++;
} else if ("laugh" == comment) {
issueBean.reaction.laugh++;
} else if ("confused" == comment) {
issueBean.reaction.confused++;
} else if ("rocket" == comment) {
issueBean.reaction.rocket++;
} else if ("heart" == comment) {
issueBean.reaction.heart++;
}
return issueBean;
}
Copy the code
Comment Reactions List
The related apis are shown below
GET /repos/:owner/:repo/issues/comments/:comment_id/reactions
The relevant code involved is shown below
_queryIssueCommentReaction(IssueBean item, comment, isIssue) async {
int id;
if (isIssue) {
id = item.number;
} else {
id = item.id;
}
final response = await IssueManager.instance
.getCommentReactions(url, id, comment, 1, isIssue);
ReactionDetailBean findReaction = null;
if(response ! =null) {
UserBean userBean = LoginManager.instance.getUserBean();
for (int i = 0; i < response.length; i++) {
ReactionDetailBean reactionDetailBean = response[i];
if(reactionDetailBean ! =null&& reactionDetailBean.content == comment && userBean ! =null&& reactionDetailBean.user ! =null &&
userBean.login == reactionDetailBean.user.login) {
findReaction = reactionDetailBean;
break; }}}if(findReaction ! =null) {
return await _deleteIssueCommentReaction(item, findReaction, comment);
} else {
return await_createIssueCommentReaction(item, comment, isIssue); }}Copy the code
Add a comment Reaction
The related apis are shown below
POST /repos/:owner/:repo/issues/comments/:comment_id/reactions
The relevant code involved is shown below
_createIssueCommentReaction(IssueBean item, comment, isIssue) async {
int id;
if (isIssue) {
id = item.number;
} else {
id = item.id;
}
final response =
await IssueManager.instance.editReactions(url, id, comment, isIssue);
if(response ! =null && response.result) {
_addIssueBean(item, comment);
sink.add(bean);
}
return response;
}
IssueBean _addIssueBean(IssueBean issueBean, String comment) {
if (issueBean.reaction == null) {
issueBean.reaction = ReactionBean(' '.0.0.0.0.0.0.0.0.0);
}
if ("+ 1" == comment) {
issueBean.reaction.like++;
} else if ("1" == comment) {
issueBean.reaction.noLike++;
} else if ("hooray" == comment) {
issueBean.reaction.hooray++;
} else if ("eyes" == comment) {
issueBean.reaction.eyes++;
} else if ("laugh" == comment) {
issueBean.reaction.laugh++;
} else if ("confused" == comment) {
issueBean.reaction.confused++;
} else if ("rocket" == comment) {
issueBean.reaction.rocket++;
} else if ("heart" == comment) {
issueBean.reaction.heart++;
}
return issueBean;
}
Copy the code
Delete the Reaction
The related apis are shown below
DELETE /reactions/:reaction_id
The relevant code involved is shown below
_deleteIssueCommentReaction(
IssueBean issueBean, ReactionDetailBean item, content) async {
final response = await IssueManager.instance.deleteReactions(item.id);
_subtractionIssueBean(issueBean, content);
sink.add(bean);
return response;
}
IssueBean _subtractionIssueBean(IssueBean issueBean, String comment) {
if ("+ 1" == comment) {
issueBean.reaction.like--;
} else if ("1" == comment) {
issueBean.reaction.noLike--;
} else if ("hooray" == comment) {
issueBean.reaction.hooray--;
} else if ("eyes" == comment) {
issueBean.reaction.eyes--;
} else if ("laugh" == comment) {
issueBean.reaction.laugh--;
} else if ("confused" == comment) {
issueBean.reaction.confused--;
} else if ("rocket" == comment) {
issueBean.reaction.rocket--;
} else if ("heart" == comment) {
issueBean.reaction.heart--;
}
return issueBean;
}
Copy the code
Browser open
The relevant code involved is shown below
@override
voidopenWebView(BuildContext context) { IssueDetailBloc bloc = BlocProvider.of<IssueDetailBloc>(context); NavigatorUtil.goWebView( context, bloc.getTitle(), bloc.bean.data? .issueBean? .htmlUrl); }Copy the code
share
The relevant code involved is shown below
void _share(BuildContext context) {
ShareUtil.share(getShareText(context));
}
@override
voidopenWebView(BuildContext context) { IssueDetailBloc bloc = BlocProvider.of<IssueDetailBloc>(context); NavigatorUtil.goWebView( context, bloc.getTitle(), bloc.bean.data? .issueBean? .htmlUrl); }Copy the code
The label
Query the item label list
The related apis are shown below
GET /repos/:owner/:repo/labels
The relevant code involved is shown below
Future _fetchLabelList() async {
LogUtil.v('_fetchLabelList', tag: TAG);
try {
var result = await IssueManager.instance.getLabel(owner, repo, page);
if (bean.data == null) {
bean.data = List(a); }if (page == 1) {
bean.data.clear();
}
noMore = true;
if(result ! =null) {
bean.isError = false; noMore = result.length ! = Config.PAGE_SIZE; bean.data.addAll(result); }else {
bean.isError = true;
}
sink.add(bean);
} catch (_) {
if(page ! =1) { page--; }}}Copy the code
Add tags
The related apis are shown below
POST /repos/:owner/:repo/labels
The relevant code involved is shown below
_editOrCreateLabel() async {
String name = _nameController.text.toString();
if (TextUtil.isEmpty(name)) {
ToastUtil.showMessgae('Name cannot be empty');
return;
}
String desc = _descController.text.toString() ?? ' ';
UserBean userBean = LoginManager.instance.getUserBean();
Stringowner = userBean? .login;String color = ColorUtil.color2RGB(_currentColor);
_showLoading();
var response;
if (_isCreate) {
response = await IssueManager.instance
.createLabel(owner, widget.repo, name, color, desc);
} else {
response = await IssueManager.instance
.updateLabel(owner, widget.repo, widget.item.name, name, color, desc);
}
if(response ! =null&& response.result) { Labels labels = Labels(widget.item? .id, widget.item? .nodeId, widget.item? .url, name, desc, color, widget.item? .default_); Navigator.pop(context, labels); }else {
ToastUtil.showMessgae('Operation failed, please try again');
}
_hideLoading();
}
Copy the code
Edit the label
The related apis are shown below
PATCH /repos/:owner/:repo/labels/:current_name
The relevant code involved is shown below
_editOrCreateLabel() async {
String name = _nameController.text.toString();
if (TextUtil.isEmpty(name)) {
ToastUtil.showMessgae('Name cannot be empty');
return;
}
String desc = _descController.text.toString() ?? ' ';
UserBean userBean = LoginManager.instance.getUserBean();
Stringowner = userBean? .login;String color = ColorUtil.color2RGB(_currentColor);
_showLoading();
var response;
if (_isCreate) {
response = await IssueManager.instance
.createLabel(owner, widget.repo, name, color, desc);
} else {
response = await IssueManager.instance
.updateLabel(owner, widget.repo, widget.item.name, name, color, desc);
}
if(response ! =null&& response.result) { Labels labels = Labels(widget.item? .id, widget.item? .nodeId, widget.item? .url, name, desc, color, widget.item? .default_); Navigator.pop(context, labels); }else {
ToastUtil.showMessgae('Operation failed, please try again');
}
_hideLoading();
}
Copy the code
Remove the label
The related apis are shown below
DELETE /repos/:owner/:repo/labels/:name
The relevant code involved is shown below
void _deleteLabel() async {
UserBean userBean = LoginManager.instance.getUserBean();
Stringowner = userBean? .login; _showLoading();var response = await IssueManager.instance
.deleteLabel(owner, widget.repo, widget.item.name);
if(response ! =null && response.result) {
widget.item.id = - 1;
Navigator.pop(context, widget.item);
} else {
ToastUtil.showMessgae('Operation failed, please try again');
}
_hideLoading();
}
Copy the code
Add a label for a problem
The related apis are shown below
POST /repos/:owner/:repo/issues/:issue_number/labels
The relevant code involved is shown below
void addIssueLabel(Labels label) async {
showLoading();
var result = await IssueManager.instance
.addIssueLabel(owner, repo, issueNum, label.name);
if(result ! =null && result.result) {
if (labels == null) {
labels = [];
}
labels.add(label);
} else {
ToastUtil.showMessgae('Operation failed, please try again');
}
hideLoading();
}
Copy the code
Remove the label of a problem
The related apis are shown below
DELETE /repos/:owner/:repo/issues/:issue_number/labels/:name
The relevant code involved is shown below
void deleteIssueLabel(String name) async {
showLoading();
var result = await IssueManager.instance
.deleteIssueLabel(owner, repo, issueNum, name);
if(result ! =null && result.result) {
if(labels ! =null) {
int deleteIndex = - 1;
for (int i = 0; i < labels.length; i++) {
Labels item = labels[i];
if(TextUtil.equals(item.name, name)) { deleteIndex = i; }}if(deleteIndex ! =null) { labels.removeAt(deleteIndex); }}}else {
ToastUtil.showMessgae('Operation failed, please try again');
}
hideLoading();
}
Copy the code
User profile page
The user profile page shows the user’s nickname, profile, project list, STAR project list, follow list, follow list, dynamic, organization, company, address, email, blog and other information. When you enter the user profile page for the first time, the system queries the details and status of the user, as shown in the following API
- The user data
GET /users/:name
- Pay attention to state
GET /user/following/:name
The relevant code involved is shown below
Future _fetchProfile() async {
final result = await UserManager.instance.getUserInfo(name);
bean.data = result;
if (result == null) {
bean.isError = true;
} else {
bean.isError = false;
}
}
Future _fetchFollow() async {
if(! UserManager.instance.isYou(name) && bean.data ! =null) {
final response = await UserManager.instance.isFollow(name);
bool isFollow = false;
if(response ! =null && response.result) {
isFollow = true; } bean.data.isFollow = isFollow; }}Copy the code
When querying concern status, you need to check whether the user is the user. If yes, no query operation is performed
Focus on the user
The related apis are shown below
PUT /user/following/:username
The relevant code involved is shown below
Future _follow() async {
final response = await UserManager.instance.follow(name);
if(response ! =null && response.result) {
bean.data.isFollow = true;
sink.add(bean);
} else {
ToastUtil.showMessgae('Operation failed please try again'); }}Copy the code
Unfollow a User
The related apis are shown below
DELETE /user/following/:username
The relevant code involved is shown below
Future _follow() async {
final response = await UserManager.instance.follow(name);
if(response ! =null && response.result) {
bean.data.isFollow = true;
sink.add(bean);
} else {
ToastUtil.showMessgae('Operation failed please try again'); }}Copy the code
A list of items
See project page above
List of STAR Projects
The related apis are shown below
GET /users/:username/starred
The relevant code involved is shown below
@override
fetchRepos(int page) async {
return await ReposManager.instance.getUserRepos(userName, page, null.true);
}
Copy the code
Since repo_user_star_bloc. Dart inherits from repo_bloc. Dart, all you need to do is implement fetchRepos, as you can see above
Pay attention to the list
The related apis are shown below
GET /users/:username/following
The relevant code involved is shown below
@override
fetchList(int page) async {
return await UserManager.instance.getUserFollower(userName, page);
}
Copy the code
Since following_bloc inherits user_bloc, you only need to implement fetchList. Details can be seen above
Watch list
The related apis are shown below
GET /users/:username/followers
The relevant code involved is shown below
@override
fetchList(int page) async {
return await UserManager.instance.getUserFollowing(userName, page);
}
Copy the code
Since followers_bloc inherits user_Bloc, you only need to implement fetchList. Details can be seen above
dynamic
The related apis are shown below
GET users/:userName/events
The relevant code involved is shown below
@override
fetchEvent(int page) async {
return await EventManager.instance.getEvent(userName, page);
}
Copy the code
Since user_event_bloc inherits event_bloc, you only need to implement fetchEvent, as you can see above
organization
The related apis are shown below
GET users/:userName/orgs
The relevant code involved is shown below
Future _fetchProfile() async {
final result = await UserManager.instance.getOrgs(name, page);
if (bean.data == null) {
bean.data = List(a); }if (page == 1) {
bean.data.clear();
}
noMore = true;
if(result ! =null) {
bean.isError = false; noMore = result.length ! = Config.PAGE_SIZE; bean.data.addAll(result); }else {
bean.isError = true;
}
sink.add(bean);
}
Copy the code
Edit data
The edit profile page supports the editing of nicknames, email addresses, blogs, companies, locations, and profiles, as shown in the API below
PATCH /user
The relevant code involved is shown below
void _editProfile() async {
String name = _name.text;
String email = _email.text;
String blog = _blog.text;
String company = _company.text;
String location = _location.text;
String bio = _bio.text;
if (TextUtil.equals(name, _userBean.name) &&
TextUtil.equals(email, _userBean.email) &&
TextUtil.equals(blog, _userBean.blog) &&
TextUtil.equals(company, _userBean.company) &&
TextUtil.equals(location, _userBean.location) &&
TextUtil.equals(bio, _userBean.bio)) {
ToastUtil.showMessgae('No changes have been made, please try again');
return;
}
_showLoading();
var response = await UserManager.instance
.updateProfile(name, email, blog, company, location, bio);
if(response ! =null && response.result) {
_userBean.name = name;
_userBean.email = email;
_userBean.blog = blog;
_userBean.company = company;
_userBean.location = location;
_userBean.bio = bio;
LoginManager.instance.setUserBean(_userBean.toJson, true);
Navigator.pop(context);
} else {
ToastUtil.showMessgae('Operation failed, please try again');
}
_hideLoading();
}
Copy the code
Search page
Search project page
The related apis are shown below
GET search/repositories? q=:query
The relevant code involved is shown below
void startSearch(String text) async {
searchText = text;
showLoading();
await _searchText();
hideLoading();
refreshStatusEvent();
}
Future _searchText() async {
final response =
await SearchManager.instance.getIssue(type, searchText, page);
if(response ! =null&& response.result) { dealResult(response.data); }}@override
void dealResult(result) {
if (bean.data == null) {
bean.data = List(a); }if (page == 1) {
bean.data.clear();
}
noMore = true;
if(result ! =null && result.length > 0) {
var items = result["items"]; noMore = items.length ! = Config.PAGE_SIZE;for (int i = 0; i < items.length; i++) {
var dataItem = items[i];
Repository repository = Repository.fromJson(dataItem);
repository.description =
ReposUtil.getGitHubEmojHtml(repository.description ?? "No description yet"); bean.data.add(repository); }}else {
bean.isError = true;
}
sink.add(bean);
}
Copy the code
Search user page
The related apis are shown below
GET search/users? q=:query
The relevant code involved is shown below
void startSearch(String text) async {
searchText = text;
showLoading();
await _searchText();
hideLoading();
refreshStatusEvent();
}
Future _searchText() async {
final response =
await SearchManager.instance.getIssue(type, searchText, page);
if(response ! =null&& response.result) { dealResult(response.data); }}@override
void dealResult(result) {
if (bean.data == null) {
bean.data = List(a); }if (page == 1) {
bean.data.clear();
}
noMore = true;
if(result ! =null && result.length > 0) {
var items = result["items"]; noMore = items.length ! = Config.PAGE_SIZE;for (int i = 0; i < items.length; i++) {
vardataItem = items[i]; UserBean user = UserBean.fromJson(dataItem); bean.data.add(user); }}else {
bean.isError = true;
}
sink.add(bean);
}
Copy the code
Search questions page
The related apis are shown below
GET search/issues? q=:query
The relevant code involved is shown below
void startSearch(String text) async {
searchText = text;
showLoading();
await _searchText();
hideLoading();
refreshStatusEvent();
}
Future _searchText() async {
final response =
await SearchManager.instance.getIssue(type, searchText, page);
if(response ! =null&& response.result) { dealResult(response.data); }}@override
void dealResult(result) {
if (bean.data == null) {
bean.data = List(a); }if (page == 1) {
bean.data.clear();
}
noMore = true;
if(result ! =null && result.length > 0) {
var items = result["items"]; noMore = items.length ! = Config.PAGE_SIZE;for (int i = 0; i < items.length; i++) {
vardataItem = items[i]; IssueBean issue = IssueBean.fromJson(dataItem); bean.data.add(issue); }}else {
bean.isError = true;
}
sink.add(bean);
}
Copy the code
Trend page
The trend page is divided into project and user trends and supports filtering by time and language type. The API mainly refers to Github-trending API
Project page
The related apis are shown below
GET lot – trending – API. Now. Sh/repositorie…
The relevant code involved is shown below
Future _fetchTrendList() async {
LogUtil.v('_fetchTrendList', tag: TAG);
try {
var result = await TrendingManager.instance.getRepos(language, since);
if (bean.data == null) {
bean.data = List(a); } bean.data.clear();if(result ! =null) {
bean.isError = false;
bean.data.addAll(result);
} else {
bean.isError = true;
}
sink.add(bean);
} catch(_) {}}Copy the code
The user page
The related apis are shown below
GET github-trending-api.now.sh/developers? …
The relevant code involved is shown below
Future _fetchTrendList() async {
LogUtil.v('_fetchTrendList', tag: TAG);
try {
var result = await TrendingManager.instance.getUser(language, since);
if (bean.data == null) {
bean.data = List(a); } bean.data.clear();if(result ! =null) {
bean.isError = false;
bean.data.addAll(result);
} else {
bean.isError = true;
}
sink.add(bean);
} catch(_) {}}Copy the code
Language list page
The language page can be found in the article Flutter SideBar control -SideBar
The related apis are shown below
GET github-trending-api.now.sh/languages
The relevant code involved is shown below
Future _fetchTrendList() async {
LogUtil.v('_fetchTrendList', tag: TAG);
try {
var result = await TrendingManager.instance.getUser(language, since);
if (bean.data == null) {
bean.data = List(a); } bean.data.clear();if(result ! =null) {
bean.isError = false;
bean.data.addAll(result);
} else {
bean.isError = true;
}
sink.add(bean);
} catch(_) {}}Copy the code
Android installation package:
Click on the download
Scan code to download
The project address
OpenGit client
flutter_common_lib
About the author
-
Personal blog
-
Github
-
The Denver nuggets
-
Jane’s book
-
CSDN