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