From cf3a39bbcca6fdaf18d342a8812a1f47d4294377 Mon Sep 17 00:00:00 2001 From: Kosh Date: Sun, 19 Feb 2017 02:26:43 +0800 Subject: [PATCH] initializing repo. --- .gitignore | 13 + app/.gitignore | 2 + app/build.gradle | 118 ++++ app/proguard-rules.pro | 25 + app/src/main/AndroidManifest.xml | 115 ++++ app/src/main/assets/fonts/app_font.ttf | Bin 0 -> 114624 bytes .../main/assets/highlight/js/lang-Splus.js | 18 + app/src/main/assets/highlight/js/lang-aea.js | 18 + app/src/main/assets/highlight/js/lang-agc.js | 18 + .../main/assets/highlight/js/lang-apollo.js | 18 + .../main/assets/highlight/js/lang-basic.js | 18 + app/src/main/assets/highlight/js/lang-cbm.js | 18 + app/src/main/assets/highlight/js/lang-cl.js | 18 + app/src/main/assets/highlight/js/lang-clj.js | 17 + app/src/main/assets/highlight/js/lang-css.js | 18 + app/src/main/assets/highlight/js/lang-dart.js | 19 + app/src/main/assets/highlight/js/lang-el.js | 18 + app/src/main/assets/highlight/js/lang-erl.js | 18 + .../main/assets/highlight/js/lang-erlang.js | 18 + app/src/main/assets/highlight/js/lang-fs.js | 18 + app/src/main/assets/highlight/js/lang-go.js | 17 + app/src/main/assets/highlight/js/lang-hs.js | 18 + .../main/assets/highlight/js/lang-lasso.js | 19 + .../assets/highlight/js/lang-lassoscript.js | 19 + .../main/assets/highlight/js/lang-latex.js | 17 + app/src/main/assets/highlight/js/lang-lgt.js | 18 + app/src/main/assets/highlight/js/lang-lisp.js | 18 + app/src/main/assets/highlight/js/lang-ll.js | 17 + app/src/main/assets/highlight/js/lang-llvm.js | 17 + .../main/assets/highlight/js/lang-logtalk.js | 18 + app/src/main/assets/highlight/js/lang-ls.js | 19 + app/src/main/assets/highlight/js/lang-lsp.js | 18 + app/src/main/assets/highlight/js/lang-lua.js | 18 + .../main/assets/highlight/js/lang-matlab.js | 29 + app/src/main/assets/highlight/js/lang-ml.js | 18 + .../main/assets/highlight/js/lang-mumps.js | 18 + app/src/main/assets/highlight/js/lang-n.js | 19 + .../main/assets/highlight/js/lang-nemerle.js | 19 + .../main/assets/highlight/js/lang-pascal.js | 18 + .../main/assets/highlight/js/lang-proto.js | 17 + app/src/main/assets/highlight/js/lang-r.js | 18 + app/src/main/assets/highlight/js/lang-rd.js | 17 + app/src/main/assets/highlight/js/lang-rkt.js | 18 + app/src/main/assets/highlight/js/lang-rust.js | 20 + app/src/main/assets/highlight/js/lang-s.js | 18 + .../main/assets/highlight/js/lang-scala.js | 18 + app/src/main/assets/highlight/js/lang-scm.js | 18 + app/src/main/assets/highlight/js/lang-sql.js | 18 + app/src/main/assets/highlight/js/lang-ss.js | 18 + .../main/assets/highlight/js/lang-swift.js | 16 + app/src/main/assets/highlight/js/lang-tcl.js | 18 + app/src/main/assets/highlight/js/lang-tex.js | 17 + app/src/main/assets/highlight/js/lang-vb.js | 19 + app/src/main/assets/highlight/js/lang-vbs.js | 19 + app/src/main/assets/highlight/js/lang-vhd.js | 19 + app/src/main/assets/highlight/js/lang-vhdl.js | 19 + app/src/main/assets/highlight/js/lang-wiki.js | 18 + app/src/main/assets/highlight/js/lang-xq.js | 19 + .../main/assets/highlight/js/lang-xquery.js | 19 + app/src/main/assets/highlight/js/lang-yaml.js | 18 + app/src/main/assets/highlight/js/lang-yml.js | 18 + app/src/main/assets/highlight/js/prettify.js | 46 ++ .../main/assets/highlight/styles/prettify.css | 117 ++++ app/src/main/assets/md/github.css | 557 ++++++++++++++++++ app/src/main/assets/md/intercept-touch.js | 26 + app/src/main/java/com/fastaccess/App.java | 41 ++ .../fastaccess/data/dao/AccessTokenModel.java | 16 + .../data/dao/CommentRequestModel.java | 29 + .../fastaccess/data/dao/CommentsModel.java | 219 +++++++ .../data/dao/CommitFileListModel.java | 9 + .../fastaccess/data/dao/CommitFileModel.java | 75 +++ .../fastaccess/data/dao/CommitListModel.java | 9 + .../com/fastaccess/data/dao/CommitModel.java | 158 +++++ .../fastaccess/data/dao/CreateGistModel.java | 43 ++ .../com/fastaccess/data/dao/EventsModel.java | 83 +++ .../com/fastaccess/data/dao/FileModel.java | 71 +++ .../fastaccess/data/dao/FilesListModel.java | 62 ++ .../data/dao/FragmentPagerAdapterModel.java | 140 +++++ .../data/dao/GistHubErrorsModel.java | 15 + .../com/fastaccess/data/dao/GistsModel.java | 207 +++++++ .../data/dao/GitCommitListModel.java | 9 + .../fastaccess/data/dao/GitCommitModel.java | 62 ++ .../data/dao/GitHubErrorResponse.java | 19 + .../fastaccess/data/dao/GithubFileModel.java | 11 + .../com/fastaccess/data/dao/GithubState.java | 42 ++ .../data/dao/IssueEventAdapterModel.java | 67 +++ .../fastaccess/data/dao/IssueEventModel.java | 79 +++ .../com/fastaccess/data/dao/IssueModel.java | 170 ++++++ .../data/dao/IssueRequestModel.java | 73 +++ .../fastaccess/data/dao/LabelListModel.java | 9 + .../com/fastaccess/data/dao/LabelModel.java | 42 ++ .../com/fastaccess/data/dao/LicenseModel.java | 52 ++ .../com/fastaccess/data/dao/LoginModel.java | 163 +++++ .../fastaccess/data/dao/MarkdownModel.java | 39 ++ .../data/dao/MergeRequestModel.java | 43 ++ .../data/dao/MergeResponseModel.java | 43 ++ .../fastaccess/data/dao/MilestoneModel.java | 83 +++ .../com/fastaccess/data/dao/NameParser.java | 41 ++ .../com/fastaccess/data/dao/Pageable.java | 55 ++ .../com/fastaccess/data/dao/PayloadModel.java | 46 ++ .../data/dao/PullRequestAdapterModel.java | 67 +++ .../fastaccess/data/dao/PullRequestModel.java | 223 +++++++ .../data/dao/PullsIssuesParser.java | 77 +++ .../data/dao/ReleasesAssetsListModel.java | 9 + .../data/dao/ReleasesAssetsModel.java | 71 +++ .../fastaccess/data/dao/ReleasesModel.java | 149 +++++ .../com/fastaccess/data/dao/RenameModel.java | 46 ++ .../fastaccess/data/dao/RepoFilesModel.java | 117 ++++ .../com/fastaccess/data/dao/RepoModel.java | 363 ++++++++++++ .../data/dao/RepoPermissionsModel.java | 46 ++ .../fastaccess/data/dao/SearchCodeModel.java | 53 ++ .../data/dao/SearchHistoryModel.java | 79 +++ .../fastaccess/data/dao/SimpleUrlsModel.java | 46 ++ .../dao/SparseBooleanArrayParcelable.java | 64 ++ .../com/fastaccess/data/dao/UserModel.java | 238 ++++++++ .../fastaccess/data/dao/UsersListModel.java | 9 + .../transformer/CommitsFilesTransformer.java | 24 + .../transformer/CommitsListTransformer.java | 24 + .../dao/transformer/EventTypeTransformer.java | 23 + .../dao/transformer/FilesTypeTransformer.java | 23 + .../transformer/GitCommitListTransformer.java | 24 + .../transformer/IssueEventTransformer.java | 23 + .../transformer/IssueStateTransformer.java | 23 + .../transformer/LabelsListTransformer.java | 24 + .../data/dao/transformer/MapTransformer.java | 24 + .../ReleasesAssetsTransformer.java | 24 + .../dao/transformer/UsersListTransformer.java | 24 + .../fastaccess/data/dao/types/EventsType.java | 34 ++ .../fastaccess/data/dao/types/FilesType.java | 24 + .../data/dao/types/GitEntryType.java | 7 + .../data/dao/types/IssueEventType.java | 36 ++ .../fastaccess/data/dao/types/IssueState.java | 20 + .../data/dao/types/NotificationReason.java | 12 + .../fastaccess/data/service/GistService.java | 72 +++ .../fastaccess/data/service/IssueService.java | 78 +++ .../data/service/PullRequestService.java | 49 ++ .../fastaccess/data/service/RepoService.java | 104 ++++ .../data/service/SearchService.java | 30 + .../data/service/UserRestService.java | 63 ++ .../com/fastaccess/helper/ActivityHelper.java | 118 ++++ .../com/fastaccess/helper/AnimHelper.java | 158 +++++ .../java/com/fastaccess/helper/AppHelper.java | 60 ++ .../com/fastaccess/helper/BundleConstant.java | 42 ++ .../java/com/fastaccess/helper/Bundler.java | 195 ++++++ .../com/fastaccess/helper/FileHelper.java | 17 + .../com/fastaccess/helper/InputHelper.java | 68 +++ .../java/com/fastaccess/helper/Logger.java | 78 +++ .../fastaccess/helper/ParseDateFormat.java | 44 ++ .../com/fastaccess/helper/PrefGetter.java | 19 + .../com/fastaccess/helper/PrefHelper.java | 80 +++ .../java/com/fastaccess/helper/RxHelper.java | 25 + .../com/fastaccess/helper/TypeFaceHelper.java | 25 + .../com/fastaccess/helper/ViewHelper.java | 136 +++++ .../provider/markdown/MarkDownProvider.java | 260 ++++++++ .../provider/rest/RestProvider.java | 176 ++++++ .../converters/GithubResponseConverter.java | 43 ++ .../interceptors/PaginationInterceptor.java | 54 ++ .../provider/rest/loadmore/OnLoadMore.java | 38 ++ .../provider/scheme/SchemeParser.java | 196 ++++++ .../fastaccess/provider/uil/UILProvider.java | 47 ++ .../ui/adapter/CommentsAdapter.java | 30 + .../ui/adapter/CommitFilesAdapter.java | 40 ++ .../fastaccess/ui/adapter/CommitsAdapter.java | 30 + .../fastaccess/ui/adapter/FeedsAdapter.java | 30 + .../ui/adapter/FragmentsPagerAdapter.java | 35 ++ .../ui/adapter/GistFilesAdapter.java | 32 + .../fastaccess/ui/adapter/GistsAdapter.java | 37 ++ .../ui/adapter/IssueTimelineAdapter.java | 48 ++ .../fastaccess/ui/adapter/IssuesAdapter.java | 37 ++ .../ui/adapter/PullRequestAdapter.java | 38 ++ .../adapter/PullRequestTimelineAdapter.java | 48 ++ .../ui/adapter/ReleasesAdapter.java | 30 + .../ui/adapter/RepoFilePathsAdapter.java | 32 + .../ui/adapter/RepoFilesAdapter.java | 32 + .../fastaccess/ui/adapter/ReposAdapter.java | 38 ++ .../ui/adapter/SearchCodeAdapter.java | 29 + .../ui/adapter/SimpleListAdapter.java | 27 + .../fastaccess/ui/adapter/UsersAdapter.java | 37 ++ .../viewholder/CommentsViewHolder.java | 60 ++ .../viewholder/CommitFilesViewHolder.java | 124 ++++ .../adapter/viewholder/CommitsViewHolder.java | 52 ++ .../adapter/viewholder/FeedsViewHolder.java | 51 ++ .../viewholder/GistFilesViewHolder.java | 42 ++ .../adapter/viewholder/GistsViewHolder.java | 51 ++ .../viewholder/IssueDetailsViewHolder.java | 50 ++ .../viewholder/IssueTimelineViewHolder.java | 93 +++ .../adapter/viewholder/IssuesViewHolder.java | 51 ++ .../PullRequestDetailsViewHolder.java | 50 ++ .../PullRequestTimelineViewHolder.java | 93 +++ .../viewholder/PullRequestViewHolder.java | 47 ++ .../viewholder/ReleasesViewHolder.java | 57 ++ .../viewholder/RepoFilePathsViewHolder.java | 36 ++ .../viewholder/RepoFilesViewHolder.java | 51 ++ .../adapter/viewholder/ReposViewHolder.java | 62 ++ .../viewholder/SearchCodeViewHolder.java | 39 ++ .../adapter/viewholder/SimpleViewHolder.java | 29 + .../adapter/viewholder/UsersViewHolder.java | 49 ++ .../com/fastaccess/ui/base/BaseActivity.java | 195 ++++++ .../ui/base/BaseBottomSheetDialog.java | 119 ++++ .../com/fastaccess/ui/base/BaseFragment.java | 115 ++++ .../com/fastaccess/ui/base/mvp/BaseMvp.java | 57 ++ .../ui/base/mvp/presenter/BasePresenter.java | 62 ++ .../ui/modules/code/CodeViewerView.java | 97 +++ .../ui/modules/editor/EditorMvp.java | 43 ++ .../ui/modules/editor/EditorPresenter.java | 179 ++++++ .../ui/modules/editor/EditorView.java | 141 +++++ .../fastaccess/ui/modules/feeds/FeedsMvp.java | 40 ++ .../ui/modules/feeds/FeedsPresenter.java | 121 ++++ .../ui/modules/feeds/FeedsView.java | 116 ++++ .../fastaccess/ui/modules/gists/GistsMvp.java | 33 ++ .../ui/modules/gists/GistsPresenter.java | 91 +++ .../ui/modules/gists/GistsView.java | 102 ++++ .../modules/gists/create/CreateGistMvp.java | 37 ++ .../gists/create/CreateGistPresenter.java | 62 ++ .../modules/gists/create/CreateGistView.java | 105 ++++ .../ui/modules/gists/gist/GistMvp.java | 50 ++ .../ui/modules/gists/gist/GistPresenter.java | 127 ++++ .../ui/modules/gists/gist/GistView.java | 198 +++++++ .../gists/gist/comments/GistCommentsMvp.java | 61 ++ .../gist/comments/GistCommentsPresenter.java | 154 +++++ .../gists/gist/comments/GistCommentsView.java | 185 ++++++ .../gists/gist/files/GistFilesListMvp.java | 20 + .../gist/files/GistFilesListPresenter.java | 23 + .../gists/gist/files/GistFilesListView.java | 85 +++ .../fastaccess/ui/modules/login/LoginMvp.java | 34 ++ .../ui/modules/login/LoginPresenter.java | 77 +++ .../ui/modules/login/LoginView.java | 83 +++ .../fastaccess/ui/modules/main/MainMvp.java | 58 ++ .../ui/modules/main/MainPresenter.java | 96 +++ .../fastaccess/ui/modules/main/MainView.java | 169 ++++++ .../modules/parser/LinksParserActivity.java | 50 ++ .../ui/modules/profile/ProfilePagerMvp.java | 14 + .../profile/ProfilePagerPresenter.java | 9 + .../ui/modules/profile/ProfilePagerView.java | 59 ++ .../followers/ProfileFollowersMvp.java | 34 ++ .../followers/ProfileFollowersPresenter.java | 94 +++ .../followers/ProfileFollowersView.java | 101 ++++ .../following/ProfileFollowingMvp.java | 34 ++ .../following/ProfileFollowingPresenter.java | 94 +++ .../following/ProfileFollowingView.java | 101 ++++ .../profile/gists/ProfileGistsMvp.java | 33 ++ .../profile/gists/ProfileGistsPresenter.java | 98 +++ .../profile/gists/ProfileGistsView.java | 107 ++++ .../profile/overview/ProfileOverviewMvp.java | 27 + .../overview/ProfileOverviewPresenter.java | 55 ++ .../profile/overview/ProfileOverviewView.java | 100 ++++ .../profile/repos/ProfileReposMvp.java | 33 ++ .../profile/repos/ProfileReposPresenter.java | 100 ++++ .../profile/repos/ProfileReposView.java | 101 ++++ .../profile/starred/ProfileStarredMvp.java | 33 ++ .../starred/ProfileStarredPresenter.java | 102 ++++ .../profile/starred/ProfileStarredView.java | 103 ++++ .../ui/modules/repos/RepoPagerMvp.java | 97 +++ .../ui/modules/repos/RepoPagerPresenter.java | 281 +++++++++ .../ui/modules/repos/RepoPagerView.java | 227 +++++++ .../modules/repos/code/RepoCodePagerMvp.java | 14 + .../repos/code/RepoCodePagerPresenter.java | 9 + .../modules/repos/code/RepoCodePagerView.java | 60 ++ .../repos/code/commit/RepoCommitsMvp.java | 35 ++ .../code/commit/RepoCommitsPresenter.java | 108 ++++ .../repos/code/commit/RepoCommitsView.java | 104 ++++ .../code/commit/details/CommitPagerMvp.java | 31 + .../commit/details/CommitPagerPresenter.java | 65 ++ .../code/commit/details/CommitPagerView.java | 158 +++++ .../details/comments/CommitCommentsMvp.java | 70 +++ .../comments/CommitCommentsPresenter.java | 168 ++++++ .../details/comments/CommitCommentsView.java | 179 ++++++ .../commit/details/files/CommitFilesMvp.java | 34 ++ .../details/files/CommitFilesPresenter.java | 64 ++ .../commit/details/files/CommitFilesView.java | 83 +++ .../contributors/RepoContributorsMvp.java | 36 ++ .../RepoContributorsPresenter.java | 95 +++ .../contributors/RepoContributorsView.java | 104 ++++ .../repos/code/files/RepoFilesMvp.java | 44 ++ .../repos/code/files/RepoFilesPresenter.java | 82 +++ .../repos/code/files/RepoFilesView.java | 145 +++++ .../code/files/paths/RepoFilePathMvp.java | 44 ++ .../files/paths/RepoFilePathPresenter.java | 63 ++ .../code/files/paths/RepoFilePathView.java | 107 ++++ .../repos/code/prettifier/ViewerMvp.java | 49 ++ .../code/prettifier/ViewerPresenter.java | 147 +++++ .../repos/code/prettifier/ViewerView.java | 118 ++++ .../repos/code/releases/RepoReleasesMvp.java | 43 ++ .../code/releases/RepoReleasesPresenter.java | 113 ++++ .../repos/code/releases/RepoReleasesView.java | 135 +++++ .../repos/issues/RepoIssuesPagerMvp.java | 14 + .../issues/RepoIssuesPagerPresenter.java | 9 + .../repos/issues/RepoIssuesPagerView.java | 56 ++ .../repos/issues/issue/RepoIssuesMvp.java | 37 ++ .../issues/issue/RepoIssuesPresenter.java | 120 ++++ .../repos/issues/issue/RepoIssuesView.java | 107 ++++ .../issues/issue/details/IssuePagerMvp.java | 44 ++ .../issue/details/IssuePagerPresenter.java | 149 +++++ .../issues/issue/details/IssuePagerView.java | 222 +++++++ .../details/comments/IssueCommentsMvp.java | 71 +++ .../comments/IssueCommentsPresenter.java | 177 ++++++ .../details/comments/IssueCommentsView.java | 178 ++++++ .../issue/details/events/IssueDetailsMvp.java | 37 ++ .../details/events/IssueDetailsPresenter.java | 112 ++++ .../details/events/IssueDetailsView.java | 97 +++ .../RepoPullRequestPagerMvp.java | 14 + .../RepoPullRequestPagerPresenter.java | 9 + .../RepoPullRequestPagerView.java | 56 ++ .../pull_request/RepoPullRequestMvp.java | 36 ++ .../RepoPullRequestPresenter.java | 120 ++++ .../pull_request/RepoPullRequestView.java | 107 ++++ .../details/PullRequestPagerMvp.java | 53 ++ .../details/PullRequestPagerPresenter.java | 156 +++++ .../details/PullRequestPagerView.java | 213 +++++++ .../commits/PullRequestCommitsMvp.java | 35 ++ .../commits/PullRequestCommitsPresenter.java | 110 ++++ .../commits/PullRequestCommitsView.java | 106 ++++ .../details/events/PullRequestDetailsMvp.java | 34 ++ .../events/PullRequestDetailsPresenter.java | 101 ++++ .../events/PullRequestDetailsView.java | 98 +++ .../ui/modules/search/SearchMvp.java | 30 + .../ui/modules/search/SearchPresenter.java | 62 ++ .../ui/modules/search/SearchView.java | 108 ++++ .../ui/modules/search/code/SearchCodeMvp.java | 36 ++ .../search/code/SearchCodePresenter.java | 78 +++ .../modules/search/code/SearchCodeView.java | 117 ++++ .../search/issues/SearchIssuesMvp.java | 34 ++ .../search/issues/SearchIssuesPresenter.java | 90 +++ .../search/issues/SearchIssuesView.java | 111 ++++ .../modules/search/repos/SearchReposMvp.java | 35 ++ .../search/repos/SearchReposPresenter.java | 77 +++ .../modules/search/repos/SearchReposView.java | 111 ++++ .../modules/search/users/SearchUsersMvp.java | 34 ++ .../search/users/SearchUsersPresenter.java | 74 +++ .../modules/search/users/SearchUsersView.java | 110 ++++ .../ui/modules/user/UserPagerMvp.java | 27 + .../ui/modules/user/UserPagerPresenter.java | 49 ++ .../ui/modules/user/UserPagerView.java | 128 ++++ .../ui/widgets/AppbarRefreshLayout.java | 23 + .../ui/widgets/AutoLinearLayout.java | 495 ++++++++++++++++ .../fastaccess/ui/widgets/AvatarLayout.java | 106 ++++ .../fastaccess/ui/widgets/DiffLineSpan.java | 32 + .../ui/widgets/FontAutoCompleteEditText.java | 47 ++ .../com/fastaccess/ui/widgets/FontButton.java | 50 ++ .../fastaccess/ui/widgets/FontCheckbox.java | 43 ++ .../fastaccess/ui/widgets/FontEditText.java | 46 ++ .../ui/widgets/FontRadioButton.java | 43 ++ .../fastaccess/ui/widgets/FontTextView.java | 81 +++ .../ui/widgets/ForegroundImageView.java | 123 ++++ .../ui/widgets/ForegroundRelativeLayout.java | 100 ++++ .../ui/widgets/NestedCoordinatorLayout.java | 110 ++++ .../ui/widgets/SpannableBuilder.java | 94 +++ .../fastaccess/ui/widgets/StateLayout.java | 162 +++++ .../com/fastaccess/ui/widgets/SwitchView.java | 35 ++ .../fastaccess/ui/widgets/ViewPagerView.java | 58 ++ .../ui/widgets/dialog/ListDialogView.java | 86 +++ .../ui/widgets/dialog/MessageDialogView.java | 121 ++++ .../dialog/ProgressDialogFragment.java | 41 ++ .../recyclerview/BaseRecyclerAdapter.java | 144 +++++ .../widgets/recyclerview/BaseViewHolder.java | 60 ++ .../recyclerview/BottomPaddingDecoration.java | 61 ++ .../recyclerview/DynamicRecyclerView.java | 105 ++++ .../layout_manager/GridManager.java | 63 ++ .../layout_manager/LinearManager.java | 30 + .../layout_manager/StaggeredManager.java | 27 + .../recyclerview/scroll/InfiniteScroll.java | 88 +++ .../scroll/RecyclerViewPositionHelper.java | 80 +++ .../com/prettifier/pretty/NestedWebView.java | 128 ++++ .../prettifier/pretty/PrettifyWebView.java | 161 +++++ .../MarkDownInterceptorInterface.java | 33 ++ .../pretty/helper/GithubHelper.java | 99 ++++ .../pretty/helper/PrettifyHelper.java | 39 ++ .../main/res/animator/cardview_selector.xml | 22 + app/src/main/res/animator/raise_selector.xml | 39 ++ .../res/drawable-nodpi/header_background.jpeg | Bin 0 -> 83414 bytes .../res/drawable-xxhdpi/bottom_shadow.9.png | Bin 0 -> 191 bytes .../ic_timeline_arrow_left.9.png | Bin 0 -> 1606 bytes .../res/drawable-xxhdpi/shadow_drawable.9.png | Bin 0 -> 1974 bytes app/src/main/res/drawable/bottom_border.xml | 14 + app/src/main/res/drawable/ic_add.xml | 9 + app/src/main/res/drawable/ic_announcement.xml | 9 + .../main/res/drawable/ic_arrow_drop_down.xml | 9 + app/src/main/res/drawable/ic_arrow_right.xml | 9 + app/src/main/res/drawable/ic_at.xml | 10 + app/src/main/res/drawable/ic_back.xml | 9 + app/src/main/res/drawable/ic_bookmark.xml | 9 + app/src/main/res/drawable/ic_brower.xml | 9 + app/src/main/res/drawable/ic_clear.xml | 9 + app/src/main/res/drawable/ic_clear_black.xml | 9 + app/src/main/res/drawable/ic_code.xml | 9 + app/src/main/res/drawable/ic_comment.xml | 9 + app/src/main/res/drawable/ic_done.xml | 9 + app/src/main/res/drawable/ic_download.xml | 9 + app/src/main/res/drawable/ic_edit.xml | 9 + app/src/main/res/drawable/ic_email.xml | 9 + app/src/main/res/drawable/ic_eye.xml | 9 + app/src/main/res/drawable/ic_eye_off.xml | 9 + .../main/res/drawable/ic_file_document.xml | 10 + app/src/main/res/drawable/ic_file_multi.xml | 9 + app/src/main/res/drawable/ic_folder.xml | 10 + app/src/main/res/drawable/ic_fork.xml | 10 + app/src/main/res/drawable/ic_format_bold.xml | 9 + .../main/res/drawable/ic_format_italic.xml | 9 + .../res/drawable/ic_format_list_bulleted.xml | 9 + app/src/main/res/drawable/ic_format_quote.xml | 9 + .../res/drawable/ic_format_strikethrough.xml | 9 + .../res/drawable/ic_format_underlined.xml | 9 + app/src/main/res/drawable/ic_gists.xml | 9 + app/src/main/res/drawable/ic_github.xml | 9 + app/src/main/res/drawable/ic_github_black.xml | 9 + app/src/main/res/drawable/ic_group.xml | 9 + app/src/main/res/drawable/ic_header_one.xml | 8 + app/src/main/res/drawable/ic_header_three.xml | 8 + app/src/main/res/drawable/ic_header_two.xml | 8 + app/src/main/res/drawable/ic_home.xml | 9 + app/src/main/res/drawable/ic_image.xml | 9 + app/src/main/res/drawable/ic_insert_link.xml | 9 + app/src/main/res/drawable/ic_issue_closed.xml | 9 + app/src/main/res/drawable/ic_issue_opened.xml | 9 + app/src/main/res/drawable/ic_issues.xml | 9 + app/src/main/res/drawable/ic_label.xml | 9 + app/src/main/res/drawable/ic_language.xml | 9 + app/src/main/res/drawable/ic_license.xml | 9 + app/src/main/res/drawable/ic_list_numbers.xml | 8 + app/src/main/res/drawable/ic_location.xml | 9 + app/src/main/res/drawable/ic_lock.xml | 9 + app/src/main/res/drawable/ic_menu.xml | 9 + app/src/main/res/drawable/ic_merge.xml | 9 + app/src/main/res/drawable/ic_milestone.xml | 9 + app/src/main/res/drawable/ic_minus.xml | 8 + app/src/main/res/drawable/ic_overflow.xml | 9 + app/src/main/res/drawable/ic_profile.xml | 9 + .../main/res/drawable/ic_pull_requests.xml | 9 + app/src/main/res/drawable/ic_redo.xml | 9 + app/src/main/res/drawable/ic_search.xml | 9 + app/src/main/res/drawable/ic_settings.xml | 9 + app/src/main/res/drawable/ic_share.xml | 9 + app/src/main/res/drawable/ic_star.xml | 9 + app/src/main/res/drawable/ic_time.xml | 9 + app/src/main/res/drawable/ic_trash.xml | 9 + app/src/main/res/drawable/ic_undo.xml | 9 + app/src/main/res/drawable/ic_unlock.xml | 9 + app/src/main/res/drawable/left_border.xml | 17 + app/src/main/res/drawable/right_border.xml | 17 + .../res/drawable/splash_screen_drawable.xml | 12 + app/src/main/res/drawable/toolbar_shadow.xml | 8 + .../main/res/drawable/toolbar_shadow_up.xml | 8 + app/src/main/res/drawable/top_border.xml | 16 + .../layout/activity_fragment_layout.xml | 14 + .../layout/activity_main_view.xml | 55 ++ .../layout/centered_tabbed_viewpager.xml | 35 ++ .../layout/commit_pager_activity.xml | 112 ++++ .../layout/create_gist_layout.xml | 128 ++++ .../main_layouts/layout/editor_layout.xml | 256 ++++++++ .../layout/general_viewer_layout.xml | 35 ++ .../layout/gists_pager_layout.xml | 80 +++ .../layout/issue_pager_activity.xml | 82 +++ .../main_layouts/layout/login_layout.xml | 60 ++ .../main_layouts/layout/repo_file_layout.xml | 51 ++ .../layout/repo_pager_activity.xml | 122 ++++ .../main_layouts/layout/search_layout.xml | 90 +++ .../layout/small_grid_refresh_list.xml | 24 + .../layout/tabbed_pager_layout.xml | 15 + .../main_layouts/layout/tabbed_viewpager.xml | 38 ++ .../layout/title_header_layout.xml | 66 +++ .../layout/vertical_refresh_list.xml | 23 + .../layout/appbar_elevation_dark.xml | 33 ++ .../layout/appbar_elevation_light.xml | 30 + .../layout/appbar_search_elevation.xml | 74 +++ .../layout/appbar_tabbed_elevation.xml | 44 ++ .../other_layouts/layout/avatar_layout.xml | 20 + .../other_layouts/layout/drawer_header.xml | 32 + .../other_layouts/layout/empty_layout.xml | 43 ++ .../other_layouts/layout/message_dialog.xml | 72 +++ .../layout/simple_list_dialog.xml | 26 + .../other_layouts/layout/state_layout.xml | 7 + .../row_layouts/layout/comments_row_item.xml | 65 ++ .../layout/commit_comments_row_item.xml | 65 ++ .../layout/commit_file_row_item.xml | 101 ++++ .../row_layouts/layout/feeds_row_item.xml | 56 ++ .../row_layouts/layout/file_path_row_item.xml | 13 + .../layout/gist_files_row_item.xml | 62 ++ .../layout/issue_detail_header_row_item.xml | 66 +++ .../row_layouts/layout/issue_row_item.xml | 57 ++ .../layout/issue_timeline_row_item.xml | 63 ++ .../layout/profile_overview_layout.xml | 201 +++++++ .../row_layouts/layout/releases_row_item.xml | 73 +++ .../layout/repo_files_row_item.xml | 71 +++ .../row_layouts/layout/repos_row_item.xml | 98 +++ .../row_layouts/layout/simple_row_item.xml | 20 + app/src/main/res/menu/bottom_nav_menu.xml | 27 + .../main/res/menu/download_browser_menu.xml | 15 + app/src/main/res/menu/download_menu.xml | 10 + app/src/main/res/menu/download_share_menu.xml | 18 + app/src/main/res/menu/follow_menu.xml | 9 + app/src/main/res/menu/gist_menu.xml | 16 + app/src/main/res/menu/issue_menu.xml | 23 + app/src/main/res/menu/pull_request_menu.xml | 24 + .../main/res/menu/repo_bottom_nav_menu.xml | 27 + app/src/main/res/menu/repo_menu.xml | 14 + app/src/main/res/menu/search_menu.xml | 10 + app/src/main/res/menu/share_menu.xml | 10 + app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3418 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 4208 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2206 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2555 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4842 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 6114 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 7718 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 10056 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 10486 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 14696 bytes app/src/main/res/values-land/dimens.xml | 4 + .../main/res/values-sw600dp-land/dimens.xml | 7 + app/src/main/res/values-sw600dp/dimens.xml | 6 + .../main/res/values-sw720dp-land/dimens.xml | 7 + app/src/main/res/values-sw720dp/dimens.xml | 7 + app/src/main/res/values-v23/styles.xml | 10 + .../main/res/values-w820dp-land/dimens.xml | 7 + app/src/main/res/values-w820dp/dimens.xml | 8 + app/src/main/res/values/attrs.xml | 82 +++ app/src/main/res/values/colors.xml | 275 +++++++++ app/src/main/res/values/dimens.xml | 26 + app/src/main/res/values/strings.xml | 137 +++++ app/src/main/res/values/styles.xml | 64 ++ build.gradle | 28 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 53636 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 160 +++++ gradlew.bat | 90 +++ settings.gradle | 1 + 526 files changed, 29648 insertions(+) create mode 100644 .gitignore create mode 100644 app/.gitignore create mode 100644 app/build.gradle create mode 100644 app/proguard-rules.pro create mode 100644 app/src/main/AndroidManifest.xml create mode 100755 app/src/main/assets/fonts/app_font.ttf create mode 100644 app/src/main/assets/highlight/js/lang-Splus.js create mode 100644 app/src/main/assets/highlight/js/lang-aea.js create mode 100644 app/src/main/assets/highlight/js/lang-agc.js create mode 100644 app/src/main/assets/highlight/js/lang-apollo.js create mode 100644 app/src/main/assets/highlight/js/lang-basic.js create mode 100644 app/src/main/assets/highlight/js/lang-cbm.js create mode 100644 app/src/main/assets/highlight/js/lang-cl.js create mode 100644 app/src/main/assets/highlight/js/lang-clj.js create mode 100644 app/src/main/assets/highlight/js/lang-css.js create mode 100644 app/src/main/assets/highlight/js/lang-dart.js create mode 100644 app/src/main/assets/highlight/js/lang-el.js create mode 100644 app/src/main/assets/highlight/js/lang-erl.js create mode 100644 app/src/main/assets/highlight/js/lang-erlang.js create mode 100644 app/src/main/assets/highlight/js/lang-fs.js create mode 100644 app/src/main/assets/highlight/js/lang-go.js create mode 100644 app/src/main/assets/highlight/js/lang-hs.js create mode 100644 app/src/main/assets/highlight/js/lang-lasso.js create mode 100644 app/src/main/assets/highlight/js/lang-lassoscript.js create mode 100644 app/src/main/assets/highlight/js/lang-latex.js create mode 100644 app/src/main/assets/highlight/js/lang-lgt.js create mode 100644 app/src/main/assets/highlight/js/lang-lisp.js create mode 100644 app/src/main/assets/highlight/js/lang-ll.js create mode 100644 app/src/main/assets/highlight/js/lang-llvm.js create mode 100644 app/src/main/assets/highlight/js/lang-logtalk.js create mode 100644 app/src/main/assets/highlight/js/lang-ls.js create mode 100644 app/src/main/assets/highlight/js/lang-lsp.js create mode 100644 app/src/main/assets/highlight/js/lang-lua.js create mode 100644 app/src/main/assets/highlight/js/lang-matlab.js create mode 100644 app/src/main/assets/highlight/js/lang-ml.js create mode 100644 app/src/main/assets/highlight/js/lang-mumps.js create mode 100644 app/src/main/assets/highlight/js/lang-n.js create mode 100644 app/src/main/assets/highlight/js/lang-nemerle.js create mode 100644 app/src/main/assets/highlight/js/lang-pascal.js create mode 100644 app/src/main/assets/highlight/js/lang-proto.js create mode 100644 app/src/main/assets/highlight/js/lang-r.js create mode 100644 app/src/main/assets/highlight/js/lang-rd.js create mode 100644 app/src/main/assets/highlight/js/lang-rkt.js create mode 100644 app/src/main/assets/highlight/js/lang-rust.js create mode 100644 app/src/main/assets/highlight/js/lang-s.js create mode 100644 app/src/main/assets/highlight/js/lang-scala.js create mode 100644 app/src/main/assets/highlight/js/lang-scm.js create mode 100644 app/src/main/assets/highlight/js/lang-sql.js create mode 100644 app/src/main/assets/highlight/js/lang-ss.js create mode 100644 app/src/main/assets/highlight/js/lang-swift.js create mode 100644 app/src/main/assets/highlight/js/lang-tcl.js create mode 100644 app/src/main/assets/highlight/js/lang-tex.js create mode 100644 app/src/main/assets/highlight/js/lang-vb.js create mode 100644 app/src/main/assets/highlight/js/lang-vbs.js create mode 100644 app/src/main/assets/highlight/js/lang-vhd.js create mode 100644 app/src/main/assets/highlight/js/lang-vhdl.js create mode 100644 app/src/main/assets/highlight/js/lang-wiki.js create mode 100644 app/src/main/assets/highlight/js/lang-xq.js create mode 100644 app/src/main/assets/highlight/js/lang-xquery.js create mode 100644 app/src/main/assets/highlight/js/lang-yaml.js create mode 100644 app/src/main/assets/highlight/js/lang-yml.js create mode 100644 app/src/main/assets/highlight/js/prettify.js create mode 100644 app/src/main/assets/highlight/styles/prettify.css create mode 100755 app/src/main/assets/md/github.css create mode 100644 app/src/main/assets/md/intercept-touch.js create mode 100644 app/src/main/java/com/fastaccess/App.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/AccessTokenModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/CommentRequestModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/CommentsModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/CommitFileListModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/CommitFileModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/CommitListModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/CommitModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/CreateGistModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/EventsModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/FileModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/FilesListModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/FragmentPagerAdapterModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/GistHubErrorsModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/GistsModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/GitCommitListModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/GitCommitModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/GitHubErrorResponse.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/GithubFileModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/GithubState.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/IssueEventAdapterModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/IssueEventModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/IssueModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/IssueRequestModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/LabelListModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/LabelModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/LicenseModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/LoginModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/MarkdownModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/MergeRequestModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/MergeResponseModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/MilestoneModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/NameParser.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/Pageable.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/PayloadModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/PullRequestAdapterModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/PullRequestModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/PullsIssuesParser.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/ReleasesAssetsListModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/ReleasesAssetsModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/ReleasesModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/RenameModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/RepoFilesModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/RepoModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/RepoPermissionsModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/SearchCodeModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/SearchHistoryModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/SimpleUrlsModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/SparseBooleanArrayParcelable.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/UserModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/UsersListModel.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/transformer/CommitsFilesTransformer.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/transformer/CommitsListTransformer.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/transformer/EventTypeTransformer.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/transformer/FilesTypeTransformer.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/transformer/GitCommitListTransformer.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/transformer/IssueEventTransformer.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/transformer/IssueStateTransformer.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/transformer/LabelsListTransformer.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/transformer/MapTransformer.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/transformer/ReleasesAssetsTransformer.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/transformer/UsersListTransformer.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/types/EventsType.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/types/FilesType.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/types/GitEntryType.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/types/IssueEventType.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/types/IssueState.java create mode 100644 app/src/main/java/com/fastaccess/data/dao/types/NotificationReason.java create mode 100644 app/src/main/java/com/fastaccess/data/service/GistService.java create mode 100644 app/src/main/java/com/fastaccess/data/service/IssueService.java create mode 100644 app/src/main/java/com/fastaccess/data/service/PullRequestService.java create mode 100644 app/src/main/java/com/fastaccess/data/service/RepoService.java create mode 100644 app/src/main/java/com/fastaccess/data/service/SearchService.java create mode 100644 app/src/main/java/com/fastaccess/data/service/UserRestService.java create mode 100644 app/src/main/java/com/fastaccess/helper/ActivityHelper.java create mode 100644 app/src/main/java/com/fastaccess/helper/AnimHelper.java create mode 100644 app/src/main/java/com/fastaccess/helper/AppHelper.java create mode 100644 app/src/main/java/com/fastaccess/helper/BundleConstant.java create mode 100644 app/src/main/java/com/fastaccess/helper/Bundler.java create mode 100644 app/src/main/java/com/fastaccess/helper/FileHelper.java create mode 100644 app/src/main/java/com/fastaccess/helper/InputHelper.java create mode 100644 app/src/main/java/com/fastaccess/helper/Logger.java create mode 100644 app/src/main/java/com/fastaccess/helper/ParseDateFormat.java create mode 100644 app/src/main/java/com/fastaccess/helper/PrefGetter.java create mode 100644 app/src/main/java/com/fastaccess/helper/PrefHelper.java create mode 100644 app/src/main/java/com/fastaccess/helper/RxHelper.java create mode 100644 app/src/main/java/com/fastaccess/helper/TypeFaceHelper.java create mode 100644 app/src/main/java/com/fastaccess/helper/ViewHelper.java create mode 100644 app/src/main/java/com/fastaccess/provider/markdown/MarkDownProvider.java create mode 100644 app/src/main/java/com/fastaccess/provider/rest/RestProvider.java create mode 100644 app/src/main/java/com/fastaccess/provider/rest/converters/GithubResponseConverter.java create mode 100644 app/src/main/java/com/fastaccess/provider/rest/interceptors/PaginationInterceptor.java create mode 100644 app/src/main/java/com/fastaccess/provider/rest/loadmore/OnLoadMore.java create mode 100644 app/src/main/java/com/fastaccess/provider/scheme/SchemeParser.java create mode 100644 app/src/main/java/com/fastaccess/provider/uil/UILProvider.java create mode 100644 app/src/main/java/com/fastaccess/ui/adapter/CommentsAdapter.java create mode 100644 app/src/main/java/com/fastaccess/ui/adapter/CommitFilesAdapter.java create mode 100644 app/src/main/java/com/fastaccess/ui/adapter/CommitsAdapter.java create mode 100644 app/src/main/java/com/fastaccess/ui/adapter/FeedsAdapter.java create mode 100644 app/src/main/java/com/fastaccess/ui/adapter/FragmentsPagerAdapter.java create mode 100644 app/src/main/java/com/fastaccess/ui/adapter/GistFilesAdapter.java create mode 100644 app/src/main/java/com/fastaccess/ui/adapter/GistsAdapter.java create mode 100644 app/src/main/java/com/fastaccess/ui/adapter/IssueTimelineAdapter.java create mode 100644 app/src/main/java/com/fastaccess/ui/adapter/IssuesAdapter.java create mode 100644 app/src/main/java/com/fastaccess/ui/adapter/PullRequestAdapter.java create mode 100644 app/src/main/java/com/fastaccess/ui/adapter/PullRequestTimelineAdapter.java create mode 100644 app/src/main/java/com/fastaccess/ui/adapter/ReleasesAdapter.java create mode 100644 app/src/main/java/com/fastaccess/ui/adapter/RepoFilePathsAdapter.java create mode 100644 app/src/main/java/com/fastaccess/ui/adapter/RepoFilesAdapter.java create mode 100644 app/src/main/java/com/fastaccess/ui/adapter/ReposAdapter.java create mode 100644 app/src/main/java/com/fastaccess/ui/adapter/SearchCodeAdapter.java create mode 100644 app/src/main/java/com/fastaccess/ui/adapter/SimpleListAdapter.java create mode 100644 app/src/main/java/com/fastaccess/ui/adapter/UsersAdapter.java create mode 100644 app/src/main/java/com/fastaccess/ui/adapter/viewholder/CommentsViewHolder.java create mode 100644 app/src/main/java/com/fastaccess/ui/adapter/viewholder/CommitFilesViewHolder.java create mode 100644 app/src/main/java/com/fastaccess/ui/adapter/viewholder/CommitsViewHolder.java create mode 100644 app/src/main/java/com/fastaccess/ui/adapter/viewholder/FeedsViewHolder.java create mode 100644 app/src/main/java/com/fastaccess/ui/adapter/viewholder/GistFilesViewHolder.java create mode 100644 app/src/main/java/com/fastaccess/ui/adapter/viewholder/GistsViewHolder.java create mode 100644 app/src/main/java/com/fastaccess/ui/adapter/viewholder/IssueDetailsViewHolder.java create mode 100644 app/src/main/java/com/fastaccess/ui/adapter/viewholder/IssueTimelineViewHolder.java create mode 100644 app/src/main/java/com/fastaccess/ui/adapter/viewholder/IssuesViewHolder.java create mode 100644 app/src/main/java/com/fastaccess/ui/adapter/viewholder/PullRequestDetailsViewHolder.java create mode 100644 app/src/main/java/com/fastaccess/ui/adapter/viewholder/PullRequestTimelineViewHolder.java create mode 100644 app/src/main/java/com/fastaccess/ui/adapter/viewholder/PullRequestViewHolder.java create mode 100644 app/src/main/java/com/fastaccess/ui/adapter/viewholder/ReleasesViewHolder.java create mode 100644 app/src/main/java/com/fastaccess/ui/adapter/viewholder/RepoFilePathsViewHolder.java create mode 100644 app/src/main/java/com/fastaccess/ui/adapter/viewholder/RepoFilesViewHolder.java create mode 100644 app/src/main/java/com/fastaccess/ui/adapter/viewholder/ReposViewHolder.java create mode 100644 app/src/main/java/com/fastaccess/ui/adapter/viewholder/SearchCodeViewHolder.java create mode 100644 app/src/main/java/com/fastaccess/ui/adapter/viewholder/SimpleViewHolder.java create mode 100644 app/src/main/java/com/fastaccess/ui/adapter/viewholder/UsersViewHolder.java create mode 100644 app/src/main/java/com/fastaccess/ui/base/BaseActivity.java create mode 100644 app/src/main/java/com/fastaccess/ui/base/BaseBottomSheetDialog.java create mode 100644 app/src/main/java/com/fastaccess/ui/base/BaseFragment.java create mode 100644 app/src/main/java/com/fastaccess/ui/base/mvp/BaseMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/base/mvp/presenter/BasePresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/code/CodeViewerView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/editor/EditorMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/editor/EditorPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/editor/EditorView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/feeds/FeedsMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/feeds/FeedsPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/feeds/FeedsView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/gists/GistsMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/gists/GistsPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/gists/GistsView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/gists/create/CreateGistMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/gists/create/CreateGistPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/gists/create/CreateGistView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/gists/gist/GistMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/gists/gist/GistPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/gists/gist/GistView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/gists/gist/comments/GistCommentsMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/gists/gist/comments/GistCommentsPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/gists/gist/comments/GistCommentsView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/gists/gist/files/GistFilesListMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/gists/gist/files/GistFilesListPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/gists/gist/files/GistFilesListView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/login/LoginMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/login/LoginPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/login/LoginView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/main/MainMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/main/MainPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/main/MainView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/parser/LinksParserActivity.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/profile/ProfilePagerMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/profile/ProfilePagerPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/profile/ProfilePagerView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/profile/followers/ProfileFollowersMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/profile/followers/ProfileFollowersPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/profile/followers/ProfileFollowersView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/profile/following/ProfileFollowingMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/profile/following/ProfileFollowingPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/profile/following/ProfileFollowingView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/profile/gists/ProfileGistsMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/profile/gists/ProfileGistsPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/profile/gists/ProfileGistsView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/profile/overview/ProfileOverviewMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/profile/overview/ProfileOverviewPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/profile/overview/ProfileOverviewView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/profile/repos/ProfileReposMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/profile/repos/ProfileReposPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/profile/repos/ProfileReposView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/profile/starred/ProfileStarredMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/profile/starred/ProfileStarredPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/profile/starred/ProfileStarredView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/RepoPagerMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/RepoPagerPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/RepoPagerView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/code/RepoCodePagerMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/code/RepoCodePagerPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/code/RepoCodePagerView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/RepoCommitsMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/RepoCommitsPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/RepoCommitsView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/CommitPagerMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/CommitPagerPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/CommitPagerView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/comments/CommitCommentsMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/comments/CommitCommentsPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/comments/CommitCommentsView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/files/CommitFilesMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/files/CommitFilesPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/files/CommitFilesView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/code/contributors/RepoContributorsMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/code/contributors/RepoContributorsPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/code/contributors/RepoContributorsView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/code/files/RepoFilesMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/code/files/RepoFilesPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/code/files/RepoFilesView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/code/files/paths/RepoFilePathMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/code/files/paths/RepoFilePathPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/code/files/paths/RepoFilePathView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/code/prettifier/ViewerMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/code/prettifier/ViewerPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/code/prettifier/ViewerView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/code/releases/RepoReleasesMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/code/releases/RepoReleasesPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/code/releases/RepoReleasesView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/issues/RepoIssuesPagerMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/issues/RepoIssuesPagerPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/issues/RepoIssuesPagerView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/RepoIssuesMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/RepoIssuesPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/RepoIssuesView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/details/IssuePagerMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/details/IssuePagerPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/details/IssuePagerView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/details/comments/IssueCommentsMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/details/comments/IssueCommentsPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/details/comments/IssueCommentsView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/details/events/IssueDetailsMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/details/events/IssueDetailsPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/details/events/IssueDetailsView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/RepoPullRequestPagerMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/RepoPullRequestPagerPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/RepoPullRequestPagerView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/RepoPullRequestMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/RepoPullRequestPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/RepoPullRequestView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/PullRequestPagerMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/PullRequestPagerPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/PullRequestPagerView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/commits/PullRequestCommitsMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/commits/PullRequestCommitsPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/commits/PullRequestCommitsView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/events/PullRequestDetailsMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/events/PullRequestDetailsPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/events/PullRequestDetailsView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/search/SearchMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/search/SearchPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/search/SearchView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/search/code/SearchCodeMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/search/code/SearchCodePresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/search/code/SearchCodeView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/search/issues/SearchIssuesMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/search/issues/SearchIssuesPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/search/issues/SearchIssuesView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/search/repos/SearchReposMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/search/repos/SearchReposPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/search/repos/SearchReposView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/search/users/SearchUsersMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/search/users/SearchUsersPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/search/users/SearchUsersView.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/user/UserPagerMvp.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/user/UserPagerPresenter.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/user/UserPagerView.java create mode 100644 app/src/main/java/com/fastaccess/ui/widgets/AppbarRefreshLayout.java create mode 100644 app/src/main/java/com/fastaccess/ui/widgets/AutoLinearLayout.java create mode 100644 app/src/main/java/com/fastaccess/ui/widgets/AvatarLayout.java create mode 100644 app/src/main/java/com/fastaccess/ui/widgets/DiffLineSpan.java create mode 100644 app/src/main/java/com/fastaccess/ui/widgets/FontAutoCompleteEditText.java create mode 100644 app/src/main/java/com/fastaccess/ui/widgets/FontButton.java create mode 100644 app/src/main/java/com/fastaccess/ui/widgets/FontCheckbox.java create mode 100644 app/src/main/java/com/fastaccess/ui/widgets/FontEditText.java create mode 100644 app/src/main/java/com/fastaccess/ui/widgets/FontRadioButton.java create mode 100644 app/src/main/java/com/fastaccess/ui/widgets/FontTextView.java create mode 100644 app/src/main/java/com/fastaccess/ui/widgets/ForegroundImageView.java create mode 100644 app/src/main/java/com/fastaccess/ui/widgets/ForegroundRelativeLayout.java create mode 100644 app/src/main/java/com/fastaccess/ui/widgets/NestedCoordinatorLayout.java create mode 100644 app/src/main/java/com/fastaccess/ui/widgets/SpannableBuilder.java create mode 100644 app/src/main/java/com/fastaccess/ui/widgets/StateLayout.java create mode 100644 app/src/main/java/com/fastaccess/ui/widgets/SwitchView.java create mode 100644 app/src/main/java/com/fastaccess/ui/widgets/ViewPagerView.java create mode 100644 app/src/main/java/com/fastaccess/ui/widgets/dialog/ListDialogView.java create mode 100644 app/src/main/java/com/fastaccess/ui/widgets/dialog/MessageDialogView.java create mode 100644 app/src/main/java/com/fastaccess/ui/widgets/dialog/ProgressDialogFragment.java create mode 100644 app/src/main/java/com/fastaccess/ui/widgets/recyclerview/BaseRecyclerAdapter.java create mode 100644 app/src/main/java/com/fastaccess/ui/widgets/recyclerview/BaseViewHolder.java create mode 100644 app/src/main/java/com/fastaccess/ui/widgets/recyclerview/BottomPaddingDecoration.java create mode 100644 app/src/main/java/com/fastaccess/ui/widgets/recyclerview/DynamicRecyclerView.java create mode 100644 app/src/main/java/com/fastaccess/ui/widgets/recyclerview/layout_manager/GridManager.java create mode 100644 app/src/main/java/com/fastaccess/ui/widgets/recyclerview/layout_manager/LinearManager.java create mode 100644 app/src/main/java/com/fastaccess/ui/widgets/recyclerview/layout_manager/StaggeredManager.java create mode 100644 app/src/main/java/com/fastaccess/ui/widgets/recyclerview/scroll/InfiniteScroll.java create mode 100644 app/src/main/java/com/fastaccess/ui/widgets/recyclerview/scroll/RecyclerViewPositionHelper.java create mode 100644 app/src/main/java/com/prettifier/pretty/NestedWebView.java create mode 100755 app/src/main/java/com/prettifier/pretty/PrettifyWebView.java create mode 100644 app/src/main/java/com/prettifier/pretty/callback/MarkDownInterceptorInterface.java create mode 100755 app/src/main/java/com/prettifier/pretty/helper/GithubHelper.java create mode 100755 app/src/main/java/com/prettifier/pretty/helper/PrettifyHelper.java create mode 100644 app/src/main/res/animator/cardview_selector.xml create mode 100644 app/src/main/res/animator/raise_selector.xml create mode 100644 app/src/main/res/drawable-nodpi/header_background.jpeg create mode 100755 app/src/main/res/drawable-xxhdpi/bottom_shadow.9.png create mode 100755 app/src/main/res/drawable-xxhdpi/ic_timeline_arrow_left.9.png create mode 100644 app/src/main/res/drawable-xxhdpi/shadow_drawable.9.png create mode 100644 app/src/main/res/drawable/bottom_border.xml create mode 100644 app/src/main/res/drawable/ic_add.xml create mode 100644 app/src/main/res/drawable/ic_announcement.xml create mode 100644 app/src/main/res/drawable/ic_arrow_drop_down.xml create mode 100644 app/src/main/res/drawable/ic_arrow_right.xml create mode 100644 app/src/main/res/drawable/ic_at.xml create mode 100644 app/src/main/res/drawable/ic_back.xml create mode 100644 app/src/main/res/drawable/ic_bookmark.xml create mode 100644 app/src/main/res/drawable/ic_brower.xml create mode 100644 app/src/main/res/drawable/ic_clear.xml create mode 100644 app/src/main/res/drawable/ic_clear_black.xml create mode 100644 app/src/main/res/drawable/ic_code.xml create mode 100644 app/src/main/res/drawable/ic_comment.xml create mode 100644 app/src/main/res/drawable/ic_done.xml create mode 100644 app/src/main/res/drawable/ic_download.xml create mode 100644 app/src/main/res/drawable/ic_edit.xml create mode 100644 app/src/main/res/drawable/ic_email.xml create mode 100644 app/src/main/res/drawable/ic_eye.xml create mode 100644 app/src/main/res/drawable/ic_eye_off.xml create mode 100644 app/src/main/res/drawable/ic_file_document.xml create mode 100644 app/src/main/res/drawable/ic_file_multi.xml create mode 100644 app/src/main/res/drawable/ic_folder.xml create mode 100644 app/src/main/res/drawable/ic_fork.xml create mode 100644 app/src/main/res/drawable/ic_format_bold.xml create mode 100644 app/src/main/res/drawable/ic_format_italic.xml create mode 100644 app/src/main/res/drawable/ic_format_list_bulleted.xml create mode 100644 app/src/main/res/drawable/ic_format_quote.xml create mode 100644 app/src/main/res/drawable/ic_format_strikethrough.xml create mode 100644 app/src/main/res/drawable/ic_format_underlined.xml create mode 100644 app/src/main/res/drawable/ic_gists.xml create mode 100644 app/src/main/res/drawable/ic_github.xml create mode 100644 app/src/main/res/drawable/ic_github_black.xml create mode 100644 app/src/main/res/drawable/ic_group.xml create mode 100644 app/src/main/res/drawable/ic_header_one.xml create mode 100644 app/src/main/res/drawable/ic_header_three.xml create mode 100644 app/src/main/res/drawable/ic_header_two.xml create mode 100644 app/src/main/res/drawable/ic_home.xml create mode 100644 app/src/main/res/drawable/ic_image.xml create mode 100644 app/src/main/res/drawable/ic_insert_link.xml create mode 100644 app/src/main/res/drawable/ic_issue_closed.xml create mode 100644 app/src/main/res/drawable/ic_issue_opened.xml create mode 100644 app/src/main/res/drawable/ic_issues.xml create mode 100644 app/src/main/res/drawable/ic_label.xml create mode 100644 app/src/main/res/drawable/ic_language.xml create mode 100644 app/src/main/res/drawable/ic_license.xml create mode 100644 app/src/main/res/drawable/ic_list_numbers.xml create mode 100644 app/src/main/res/drawable/ic_location.xml create mode 100644 app/src/main/res/drawable/ic_lock.xml create mode 100644 app/src/main/res/drawable/ic_menu.xml create mode 100644 app/src/main/res/drawable/ic_merge.xml create mode 100644 app/src/main/res/drawable/ic_milestone.xml create mode 100644 app/src/main/res/drawable/ic_minus.xml create mode 100644 app/src/main/res/drawable/ic_overflow.xml create mode 100644 app/src/main/res/drawable/ic_profile.xml create mode 100644 app/src/main/res/drawable/ic_pull_requests.xml create mode 100644 app/src/main/res/drawable/ic_redo.xml create mode 100644 app/src/main/res/drawable/ic_search.xml create mode 100644 app/src/main/res/drawable/ic_settings.xml create mode 100644 app/src/main/res/drawable/ic_share.xml create mode 100644 app/src/main/res/drawable/ic_star.xml create mode 100644 app/src/main/res/drawable/ic_time.xml create mode 100644 app/src/main/res/drawable/ic_trash.xml create mode 100644 app/src/main/res/drawable/ic_undo.xml create mode 100644 app/src/main/res/drawable/ic_unlock.xml create mode 100644 app/src/main/res/drawable/left_border.xml create mode 100644 app/src/main/res/drawable/right_border.xml create mode 100644 app/src/main/res/drawable/splash_screen_drawable.xml create mode 100644 app/src/main/res/drawable/toolbar_shadow.xml create mode 100644 app/src/main/res/drawable/toolbar_shadow_up.xml create mode 100644 app/src/main/res/drawable/top_border.xml create mode 100644 app/src/main/res/layouts/main_layouts/layout/activity_fragment_layout.xml create mode 100644 app/src/main/res/layouts/main_layouts/layout/activity_main_view.xml create mode 100644 app/src/main/res/layouts/main_layouts/layout/centered_tabbed_viewpager.xml create mode 100644 app/src/main/res/layouts/main_layouts/layout/commit_pager_activity.xml create mode 100644 app/src/main/res/layouts/main_layouts/layout/create_gist_layout.xml create mode 100644 app/src/main/res/layouts/main_layouts/layout/editor_layout.xml create mode 100644 app/src/main/res/layouts/main_layouts/layout/general_viewer_layout.xml create mode 100644 app/src/main/res/layouts/main_layouts/layout/gists_pager_layout.xml create mode 100644 app/src/main/res/layouts/main_layouts/layout/issue_pager_activity.xml create mode 100644 app/src/main/res/layouts/main_layouts/layout/login_layout.xml create mode 100644 app/src/main/res/layouts/main_layouts/layout/repo_file_layout.xml create mode 100644 app/src/main/res/layouts/main_layouts/layout/repo_pager_activity.xml create mode 100644 app/src/main/res/layouts/main_layouts/layout/search_layout.xml create mode 100644 app/src/main/res/layouts/main_layouts/layout/small_grid_refresh_list.xml create mode 100644 app/src/main/res/layouts/main_layouts/layout/tabbed_pager_layout.xml create mode 100644 app/src/main/res/layouts/main_layouts/layout/tabbed_viewpager.xml create mode 100644 app/src/main/res/layouts/main_layouts/layout/title_header_layout.xml create mode 100644 app/src/main/res/layouts/main_layouts/layout/vertical_refresh_list.xml create mode 100644 app/src/main/res/layouts/other_layouts/layout/appbar_elevation_dark.xml create mode 100644 app/src/main/res/layouts/other_layouts/layout/appbar_elevation_light.xml create mode 100644 app/src/main/res/layouts/other_layouts/layout/appbar_search_elevation.xml create mode 100644 app/src/main/res/layouts/other_layouts/layout/appbar_tabbed_elevation.xml create mode 100644 app/src/main/res/layouts/other_layouts/layout/avatar_layout.xml create mode 100644 app/src/main/res/layouts/other_layouts/layout/drawer_header.xml create mode 100644 app/src/main/res/layouts/other_layouts/layout/empty_layout.xml create mode 100644 app/src/main/res/layouts/other_layouts/layout/message_dialog.xml create mode 100644 app/src/main/res/layouts/other_layouts/layout/simple_list_dialog.xml create mode 100644 app/src/main/res/layouts/other_layouts/layout/state_layout.xml create mode 100644 app/src/main/res/layouts/row_layouts/layout/comments_row_item.xml create mode 100644 app/src/main/res/layouts/row_layouts/layout/commit_comments_row_item.xml create mode 100644 app/src/main/res/layouts/row_layouts/layout/commit_file_row_item.xml create mode 100644 app/src/main/res/layouts/row_layouts/layout/feeds_row_item.xml create mode 100644 app/src/main/res/layouts/row_layouts/layout/file_path_row_item.xml create mode 100644 app/src/main/res/layouts/row_layouts/layout/gist_files_row_item.xml create mode 100644 app/src/main/res/layouts/row_layouts/layout/issue_detail_header_row_item.xml create mode 100644 app/src/main/res/layouts/row_layouts/layout/issue_row_item.xml create mode 100644 app/src/main/res/layouts/row_layouts/layout/issue_timeline_row_item.xml create mode 100644 app/src/main/res/layouts/row_layouts/layout/profile_overview_layout.xml create mode 100644 app/src/main/res/layouts/row_layouts/layout/releases_row_item.xml create mode 100644 app/src/main/res/layouts/row_layouts/layout/repo_files_row_item.xml create mode 100644 app/src/main/res/layouts/row_layouts/layout/repos_row_item.xml create mode 100644 app/src/main/res/layouts/row_layouts/layout/simple_row_item.xml create mode 100644 app/src/main/res/menu/bottom_nav_menu.xml create mode 100644 app/src/main/res/menu/download_browser_menu.xml create mode 100644 app/src/main/res/menu/download_menu.xml create mode 100644 app/src/main/res/menu/download_share_menu.xml create mode 100644 app/src/main/res/menu/follow_menu.xml create mode 100644 app/src/main/res/menu/gist_menu.xml create mode 100644 app/src/main/res/menu/issue_menu.xml create mode 100644 app/src/main/res/menu/pull_request_menu.xml create mode 100644 app/src/main/res/menu/repo_bottom_nav_menu.xml create mode 100644 app/src/main/res/menu/repo_menu.xml create mode 100644 app/src/main/res/menu/search_menu.xml create mode 100644 app/src/main/res/menu/share_menu.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100755 app/src/main/res/values-land/dimens.xml create mode 100644 app/src/main/res/values-sw600dp-land/dimens.xml create mode 100644 app/src/main/res/values-sw600dp/dimens.xml create mode 100644 app/src/main/res/values-sw720dp-land/dimens.xml create mode 100644 app/src/main/res/values-sw720dp/dimens.xml create mode 100644 app/src/main/res/values-v23/styles.xml create mode 100755 app/src/main/res/values-w820dp-land/dimens.xml create mode 100755 app/src/main/res/values-w820dp/dimens.xml create mode 100644 app/src/main/res/values/attrs.xml create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/dimens.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/styles.xml create mode 100644 build.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..42897946 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +*.iml +.gradle +/local.properties +.DS_Store +/build +/captures +.externalNativeBuild +/gradle.properties +/.idea/ +/app/google-services.json +/app/db/ +/app/build/ +/app/src/main/res/values/secrets.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 00000000..2abde4aa --- /dev/null +++ b/app/.gitignore @@ -0,0 +1,2 @@ +/build +/google-services.json diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 00000000..6f037347 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,118 @@ +apply plugin: 'com.android.application' +apply plugin: 'me.tatarka.retrolambda' +apply plugin: "com.neenbedankt.android-apt" +apply plugin: 'com.jakewharton.hugo' +apply plugin: 'com.siimkinks.sqlitemagic' + +android { + signingConfigs { + signing { + keyAlias ANDROID_KEY_ALIAS + keyPassword ANDROID_STORE_PASSWORD + storeFile file('StyleMe') + storePassword ANDROID_STORE_PASSWORD + } + } + compileSdkVersion 25 + buildToolsVersion "25.0.2" + defaultConfig { + applicationId "com.fastaccess.github" + minSdkVersion 21 + targetSdkVersion 25 + versionCode 100 + versionName "1.0.0" + signingConfig signingConfigs.signing + buildConfigField "String", "GITHUB_CLIENT_ID", "\"${GITHUB_CLIENT_ID}\"" + buildConfigField "String", "GITHUB_SECRET", "\"${GITHUB_SECRET}\"" + buildConfigField "String", "REDIRECT_URL", "\"${REDIRECT_URL}\"" + buildConfigField "String", "REST_URL", '"https://api.github.com/"' + } + buildTypes { + release { + minifyEnabled true + shrinkResources true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + sourceSets { + main { + res.srcDirs = [ + "src/main/res/", + "src/main/res/layouts/main_layouts", + "src/main/res/layouts/row_layouts", + "src/main/res/layouts/other_layouts", + "src/main/res" + ] + } + } + + lintOptions { + abortOnError false + htmlReport true + xmlReport true + disable 'InvalidPackage' + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + configurations { + all*.exclude module: 'annotations' + } +} + +retrolambda { + oldJdk System.getenv("JAVA7_HOME") +} + +repositories { + maven { url "https://clojars.org/repo/" } + maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } + maven { url "http://dl.bintray.com/amulyakhare/maven" } + maven { url "https://jitpack.io" } + maven { url "https://s3.amazonaws.com/repo.commonsware.com" } + maven { url 'http://oss.jfrog.org/artifactory/oss-snapshot-local' } +} + +dependencies { + ext { + supportVerion = "25.1.1" + firebase = "10.0.1" + thirtyinchVersion = '0.8.0-rc3' + retrofit = '2.1.0' + } + compile fileTree(include: ['*.jar'], dir: 'libs') + compile "com.android.support:appcompat-v7:${supportVerion}" + compile "com.android.support:design:${supportVerion}" + compile "com.android.support:cardview-v7:${supportVerion}" + compile "com.android.support:recyclerview-v7:${supportVerion}" + compile "com.android.support:preference-v14:${supportVerion}" + compile "com.android.support:customtabs:${supportVerion}" + compile "com.google.firebase:firebase-messaging:${firebase}" + compile "com.google.firebase:firebase-analytics:${firebase}" + compile "net.grandcentrix.thirtyinch:thirtyinch:${thirtyinchVersion}" + compile "net.grandcentrix.thirtyinch:thirtyinch-rx:${thirtyinchVersion}" + compile 'com.android.support.constraint:constraint-layout:1.0.0-beta4' + compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5' + compile 'de.hdodenhof:circleimageview:2.1.0' + compile 'com.jakewharton:butterknife:8.4.0' + compile 'org.greenrobot:eventbus:3.0.0' + compile 'frankiesardo:icepick:3.1.0' + compile 'it.sephiroth.android.library.bottomnavigation:bottom-navigation:1.0.7' + compile 'pub.devrel:easypermissions:0.2.1' + compile 'io.reactivex:rxandroid:1.2.1' + compile 'io.reactivex:rxjava:1.2.2' + compile "com.squareup.retrofit2:retrofit:${retrofit}" + compile "com.squareup.retrofit2:converter-gson:${retrofit}" + compile "com.squareup.retrofit2:adapter-rxjava:${retrofit}" + compile 'com.squareup.okhttp3:logging-interceptor:3.3.1' + compile 'com.annimon:stream:1.1.4' + compile 'com.commonsware.cwac:anddown:0.3.0' + provided "org.projectlombok:lombok:1.12.6" + apt "org.projectlombok:lombok:1.12.6" + apt 'frankiesardo:icepick-processor:3.1.0' + apt 'com.jakewharton:butterknife-compiler:8.4.0' +} + +apply plugin: "com.google.gms.google-services" \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 00000000..34e26cf9 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/Kosh/Library/Android/sdk/tools/proguard/proguard-android.txt +# You can edit the include getPath and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..8cf89a1f --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/assets/fonts/app_font.ttf b/app/src/main/assets/fonts/app_font.ttf new file mode 100755 index 0000000000000000000000000000000000000000..b158a334eb372a9ab2ecd4f2566e60d561e71a9f GIT binary patch literal 114624 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

WH`00`s@p7bm8Ow12>ck9v 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\|\|?|\*|\+|\^|\/|!|%.*?%|=|~|\$|@|:{1,3}|[\[\](){};,?])/],["pln",/^(?:[A-Za-z]+[A-Za-z0-9_.]*|\.[a-zA-Z_][0-9a-zA-Z\._]*)(?![A-Za-z0-9_.])/],["str",/^`.+`/]]),["r","s","R","S","Splus"]); diff --git a/app/src/main/assets/highlight/js/lang-aea.js b/app/src/main/assets/highlight/js/lang-aea.js new file mode 100644 index 00000000..784ebb29 --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-aea.js @@ -0,0 +1,18 @@ +/* + + Copyright (C) 2009 Onno Hommes. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["com",/^#[^\r\n]*/,null,"#"],["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["str",/^\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)/,null,'"']],[["kwd",/^(?:ADS|AD|AUG|BZF|BZMF|CAE|CAF|CA|CCS|COM|CS|DAS|DCA|DCOM|DCS|DDOUBL|DIM|DOUBLE|DTCB|DTCF|DV|DXCH|EDRUPT|EXTEND|INCR|INDEX|NDX|INHINT|LXCH|MASK|MSK|MP|MSU|NOOP|OVSK|QXCH|RAND|READ|RELINT|RESUME|RETURN|ROR|RXOR|SQUARE|SU|TCR|TCAA|OVSK|TCF|TC|TS|WAND|WOR|WRITE|XCH|XLQ|XXALQ|ZL|ZQ|ADD|ADZ|SUB|SUZ|MPY|MPR|MPZ|DVP|COM|ABS|CLA|CLZ|LDQ|STO|STQ|ALS|LLS|LRS|TRA|TSQ|TMI|TOV|AXT|TIX|DLY|INP|OUT)\s/, +null],["typ",/^(?:-?GENADR|=MINUS|2BCADR|VN|BOF|MM|-?2CADR|-?[1-6]DNADR|ADRES|BBCON|[SE]?BANK\=?|BLOCK|BNKSUM|E?CADR|COUNT\*?|2?DEC\*?|-?DNCHAN|-?DNPTR|EQUALS|ERASE|MEMORY|2?OCT|REMADR|SETLOC|SUBRO|ORG|BSS|BES|SYN|EQU|DEFINE|END)\s/,null],["lit",/^\'(?:-*(?:\w|\\[\x21-\x7e])(?:[\w-]*|\\[\x21-\x7e])[=!?]?)?/],["pln",/^-*(?:[!-z_]|\\[\x21-\x7e])(?:[\w-]*|\\[\x21-\x7e])[=!?]?/i],["pun",/^[^\w\t\n\r \xA0()\"\\\';]+/]]),["apollo","agc","aea"]); diff --git a/app/src/main/assets/highlight/js/lang-agc.js b/app/src/main/assets/highlight/js/lang-agc.js new file mode 100644 index 00000000..784ebb29 --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-agc.js @@ -0,0 +1,18 @@ +/* + + Copyright (C) 2009 Onno Hommes. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["com",/^#[^\r\n]*/,null,"#"],["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["str",/^\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)/,null,'"']],[["kwd",/^(?:ADS|AD|AUG|BZF|BZMF|CAE|CAF|CA|CCS|COM|CS|DAS|DCA|DCOM|DCS|DDOUBL|DIM|DOUBLE|DTCB|DTCF|DV|DXCH|EDRUPT|EXTEND|INCR|INDEX|NDX|INHINT|LXCH|MASK|MSK|MP|MSU|NOOP|OVSK|QXCH|RAND|READ|RELINT|RESUME|RETURN|ROR|RXOR|SQUARE|SU|TCR|TCAA|OVSK|TCF|TC|TS|WAND|WOR|WRITE|XCH|XLQ|XXALQ|ZL|ZQ|ADD|ADZ|SUB|SUZ|MPY|MPR|MPZ|DVP|COM|ABS|CLA|CLZ|LDQ|STO|STQ|ALS|LLS|LRS|TRA|TSQ|TMI|TOV|AXT|TIX|DLY|INP|OUT)\s/, +null],["typ",/^(?:-?GENADR|=MINUS|2BCADR|VN|BOF|MM|-?2CADR|-?[1-6]DNADR|ADRES|BBCON|[SE]?BANK\=?|BLOCK|BNKSUM|E?CADR|COUNT\*?|2?DEC\*?|-?DNCHAN|-?DNPTR|EQUALS|ERASE|MEMORY|2?OCT|REMADR|SETLOC|SUBRO|ORG|BSS|BES|SYN|EQU|DEFINE|END)\s/,null],["lit",/^\'(?:-*(?:\w|\\[\x21-\x7e])(?:[\w-]*|\\[\x21-\x7e])[=!?]?)?/],["pln",/^-*(?:[!-z_]|\\[\x21-\x7e])(?:[\w-]*|\\[\x21-\x7e])[=!?]?/i],["pun",/^[^\w\t\n\r \xA0()\"\\\';]+/]]),["apollo","agc","aea"]); diff --git a/app/src/main/assets/highlight/js/lang-apollo.js b/app/src/main/assets/highlight/js/lang-apollo.js new file mode 100644 index 00000000..784ebb29 --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-apollo.js @@ -0,0 +1,18 @@ +/* + + Copyright (C) 2009 Onno Hommes. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["com",/^#[^\r\n]*/,null,"#"],["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["str",/^\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)/,null,'"']],[["kwd",/^(?:ADS|AD|AUG|BZF|BZMF|CAE|CAF|CA|CCS|COM|CS|DAS|DCA|DCOM|DCS|DDOUBL|DIM|DOUBLE|DTCB|DTCF|DV|DXCH|EDRUPT|EXTEND|INCR|INDEX|NDX|INHINT|LXCH|MASK|MSK|MP|MSU|NOOP|OVSK|QXCH|RAND|READ|RELINT|RESUME|RETURN|ROR|RXOR|SQUARE|SU|TCR|TCAA|OVSK|TCF|TC|TS|WAND|WOR|WRITE|XCH|XLQ|XXALQ|ZL|ZQ|ADD|ADZ|SUB|SUZ|MPY|MPR|MPZ|DVP|COM|ABS|CLA|CLZ|LDQ|STO|STQ|ALS|LLS|LRS|TRA|TSQ|TMI|TOV|AXT|TIX|DLY|INP|OUT)\s/, +null],["typ",/^(?:-?GENADR|=MINUS|2BCADR|VN|BOF|MM|-?2CADR|-?[1-6]DNADR|ADRES|BBCON|[SE]?BANK\=?|BLOCK|BNKSUM|E?CADR|COUNT\*?|2?DEC\*?|-?DNCHAN|-?DNPTR|EQUALS|ERASE|MEMORY|2?OCT|REMADR|SETLOC|SUBRO|ORG|BSS|BES|SYN|EQU|DEFINE|END)\s/,null],["lit",/^\'(?:-*(?:\w|\\[\x21-\x7e])(?:[\w-]*|\\[\x21-\x7e])[=!?]?)?/],["pln",/^-*(?:[!-z_]|\\[\x21-\x7e])(?:[\w-]*|\\[\x21-\x7e])[=!?]?/i],["pun",/^[^\w\t\n\r \xA0()\"\\\';]+/]]),["apollo","agc","aea"]); diff --git a/app/src/main/assets/highlight/js/lang-basic.js b/app/src/main/assets/highlight/js/lang-basic.js new file mode 100644 index 00000000..2d6151dc --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-basic.js @@ -0,0 +1,18 @@ +/* + + Copyright (C) 2013 Peter Kofler + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["str",/^(?:"(?:[^\\"\r\n]|\\.)*(?:"|$))/,null,'"'],["pln",/^\s+/,null," \r\n\t\u00a0"]],[["com",/^REM[^\r\n]*/,null],["kwd",/^\b(?:AND|CLOSE|CLR|CMD|CONT|DATA|DEF ?FN|DIM|END|FOR|GET|GOSUB|GOTO|IF|INPUT|LET|LIST|LOAD|NEW|NEXT|NOT|ON|OPEN|OR|POKE|PRINT|READ|RESTORE|RETURN|RUN|SAVE|STEP|STOP|SYS|THEN|TO|VERIFY|WAIT)\b/,null],["pln",/^[A-Z][A-Z0-9]?(?:\$|%)?/i,null],["lit",/^(?:\d+(?:\.\d*)?|\.\d+)(?:e[+\-]?\d+)?/i, +null,"0123456789"],["pun",/^.[^\s\w\.$%"]*/,null]]),["basic","cbm"]); diff --git a/app/src/main/assets/highlight/js/lang-cbm.js b/app/src/main/assets/highlight/js/lang-cbm.js new file mode 100644 index 00000000..2d6151dc --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-cbm.js @@ -0,0 +1,18 @@ +/* + + Copyright (C) 2013 Peter Kofler + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["str",/^(?:"(?:[^\\"\r\n]|\\.)*(?:"|$))/,null,'"'],["pln",/^\s+/,null," \r\n\t\u00a0"]],[["com",/^REM[^\r\n]*/,null],["kwd",/^\b(?:AND|CLOSE|CLR|CMD|CONT|DATA|DEF ?FN|DIM|END|FOR|GET|GOSUB|GOTO|IF|INPUT|LET|LIST|LOAD|NEW|NEXT|NOT|ON|OPEN|OR|POKE|PRINT|READ|RESTORE|RETURN|RUN|SAVE|STEP|STOP|SYS|THEN|TO|VERIFY|WAIT)\b/,null],["pln",/^[A-Z][A-Z0-9]?(?:\$|%)?/i,null],["lit",/^(?:\d+(?:\.\d*)?|\.\d+)(?:e[+\-]?\d+)?/i, +null,"0123456789"],["pun",/^.[^\s\w\.$%"]*/,null]]),["basic","cbm"]); diff --git a/app/src/main/assets/highlight/js/lang-cl.js b/app/src/main/assets/highlight/js/lang-cl.js new file mode 100644 index 00000000..2f18c967 --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-cl.js @@ -0,0 +1,18 @@ +/* + + Copyright (C) 2008 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["opn",/^\(+/,null,"("],["clo",/^\)+/,null,")"],["com",/^;[^\r\n]*/,null,";"],["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["str",/^\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)/,null,'"']],[["kwd",/^(?:block|c[ad]+r|catch|con[ds]|def(?:ine|un)|do|eq|eql|equal|equalp|eval-when|flet|format|go|if|labels|lambda|let|load-time-value|locally|macrolet|multiple-value-call|nil|progn|progv|quote|require|return-from|setq|symbol-macrolet|t|tagbody|the|throw|unwind)\b/, +null],["lit",/^[+\-]?(?:[0#]x[0-9a-f]+|\d+\/\d+|(?:\.\d+|\d+(?:\.\d*)?)(?:[ed][+\-]?\d+)?)/i],["lit",/^\'(?:-*(?:\w|\\[\x21-\x7e])(?:[\w-]*|\\[\x21-\x7e])[=!?]?)?/],["pln",/^-*(?:[a-z_]|\\[\x21-\x7e])(?:[\w-]*|\\[\x21-\x7e])[=!?]?/i],["pun",/^[^\w\t\n\r \xA0()\"\\\';]+/]]),"cl el lisp lsp scm ss rkt".split(" ")); diff --git a/app/src/main/assets/highlight/js/lang-clj.js b/app/src/main/assets/highlight/js/lang-clj.js new file mode 100644 index 00000000..d1173b1e --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-clj.js @@ -0,0 +1,17 @@ +/* + Copyright (C) 2011 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["opn",/^[\(\{\[]+/,null,"([{"],["clo",/^[\)\}\]]+/,null,")]}"],["com",/^;[^\r\n]*/,null,";"],["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["str",/^\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)/,null,'"']],[["kwd",/^(?:def|if|do|let|quote|var|fn|loop|recur|throw|try|monitor-enter|monitor-exit|defmacro|defn|defn-|macroexpand|macroexpand-1|for|doseq|dosync|dotimes|and|or|when|not|assert|doto|proxy|defstruct|first|rest|cons|defprotocol|deftype|defrecord|reify|defmulti|defmethod|meta|with-meta|ns|in-ns|create-ns|import|intern|refer|alias|namespace|resolve|ref|deref|refset|new|set!|memfn|to-array|into-array|aset|gen-class|reduce|map|filter|find|nil?|empty?|hash-map|hash-set|vec|vector|seq|flatten|reverse|assoc|dissoc|list|list?|disj|get|union|difference|intersection|extend|extend-type|extend-protocol|prn)\b/, +null],["typ",/^:[0-9a-zA-Z\-]+/]]),["clj"]); diff --git a/app/src/main/assets/highlight/js/lang-css.js b/app/src/main/assets/highlight/js/lang-css.js new file mode 100644 index 00000000..90d175da --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-css.js @@ -0,0 +1,18 @@ +/* + + Copyright (C) 2009 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[["str",/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],["str",/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']+)\)/i],["kwd",/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//], +["com",/^(?:\x3c!--|--\x3e)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#(?:[0-9a-f]{3}){1,2}\b/i],["pln",/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],["pun",/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^\)\"\']+/]]),["css-str"]); diff --git a/app/src/main/assets/highlight/js/lang-dart.js b/app/src/main/assets/highlight/js/lang-dart.js new file mode 100644 index 00000000..da142a4e --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-dart.js @@ -0,0 +1,19 @@ +/* + + Copyright (C) 2013 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"]],[["com",/^#!(?:.*)/],["kwd",/^\b(?:import|library|part of|part|as|show|hide)\b/i],["com",/^\/\/(?:.*)/],["com",/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],["kwd",/^\b(?:class|interface)\b/i],["kwd",/^\b(?:assert|async|await|break|case|catch|continue|default|do|else|finally|for|if|in|is|new|return|super|switch|sync|this|throw|try|while)\b/i],["kwd",/^\b(?:abstract|const|extends|factory|final|get|implements|native|operator|set|static|typedef|var)\b/i], +["typ",/^\b(?:bool|double|Dynamic|int|num|Object|String|void)\b/i],["kwd",/^\b(?:false|null|true)\b/i],["str",/^r?[\']{3}[\s|\S]*?[^\\][\']{3}/],["str",/^r?[\"]{3}[\s|\S]*?[^\\][\"]{3}/],["str",/^r?\'(\'|(?:[^\n\r\f])*?[^\\]\')/],["str",/^r?\"(\"|(?:[^\n\r\f])*?[^\\]\")/],["typ",/^[A-Z]\w*/],["pln",/^[a-z_$][a-z0-9_]*/i],["pun",/^[~!%^&*+=|?:<>/-]/],["lit",/^\b0x[0-9a-f]+/i],["lit",/^\b\d+(?:\.\d*)?(?:e[+-]?\d+)?/i],["lit", +/^\b\.\d+(?:e[+-]?\d+)?/i],["pun",/^[(){}\[\],.;]/]]),["dart"]); diff --git a/app/src/main/assets/highlight/js/lang-el.js b/app/src/main/assets/highlight/js/lang-el.js new file mode 100644 index 00000000..2f18c967 --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-el.js @@ -0,0 +1,18 @@ +/* + + Copyright (C) 2008 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["opn",/^\(+/,null,"("],["clo",/^\)+/,null,")"],["com",/^;[^\r\n]*/,null,";"],["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["str",/^\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)/,null,'"']],[["kwd",/^(?:block|c[ad]+r|catch|con[ds]|def(?:ine|un)|do|eq|eql|equal|equalp|eval-when|flet|format|go|if|labels|lambda|let|load-time-value|locally|macrolet|multiple-value-call|nil|progn|progv|quote|require|return-from|setq|symbol-macrolet|t|tagbody|the|throw|unwind)\b/, +null],["lit",/^[+\-]?(?:[0#]x[0-9a-f]+|\d+\/\d+|(?:\.\d+|\d+(?:\.\d*)?)(?:[ed][+\-]?\d+)?)/i],["lit",/^\'(?:-*(?:\w|\\[\x21-\x7e])(?:[\w-]*|\\[\x21-\x7e])[=!?]?)?/],["pln",/^-*(?:[a-z_]|\\[\x21-\x7e])(?:[\w-]*|\\[\x21-\x7e])[=!?]?/i],["pun",/^[^\w\t\n\r \xA0()\"\\\';]+/]]),"cl el lisp lsp scm ss rkt".split(" ")); diff --git a/app/src/main/assets/highlight/js/lang-erl.js b/app/src/main/assets/highlight/js/lang-erl.js new file mode 100644 index 00000000..e7da9b04 --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-erl.js @@ -0,0 +1,18 @@ +/* + + Copyright (C) 2013 Andrew Allen + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\x0B\x0C\r ]+/,null,"\t\n\x0B\f\r "],["str",/^\"(?:[^\"\\\n\x0C\r]|\\[\s\S])*(?:\"|$)/,null,'"'],["lit",/^[a-z][a-zA-Z0-9_]*/],["lit",/^\'(?:[^\'\\\n\x0C\r]|\\[^&])+\'?/,null,"'"],["lit",/^\?[^ \t\n({]+/,null,"?"],["lit",/^(?:0o[0-7]+|0x[\da-f]+|\d+(?:\.\d+)?(?:e[+\-]?\d+)?)/i,null,"0123456789"]],[["com",/^%[^\n]*/],["kwd",/^(?:module|attributes|do|let|in|letrec|apply|call|primop|case|of|end|when|fun|try|catch|receive|after|char|integer|float,atom,string,var)\b/], +["kwd",/^-[a-z_]+/],["typ",/^[A-Z_][a-zA-Z0-9_]*/],["pun",/^[.,;]/]]),["erlang","erl"]); diff --git a/app/src/main/assets/highlight/js/lang-erlang.js b/app/src/main/assets/highlight/js/lang-erlang.js new file mode 100644 index 00000000..e7da9b04 --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-erlang.js @@ -0,0 +1,18 @@ +/* + + Copyright (C) 2013 Andrew Allen + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\x0B\x0C\r ]+/,null,"\t\n\x0B\f\r "],["str",/^\"(?:[^\"\\\n\x0C\r]|\\[\s\S])*(?:\"|$)/,null,'"'],["lit",/^[a-z][a-zA-Z0-9_]*/],["lit",/^\'(?:[^\'\\\n\x0C\r]|\\[^&])+\'?/,null,"'"],["lit",/^\?[^ \t\n({]+/,null,"?"],["lit",/^(?:0o[0-7]+|0x[\da-f]+|\d+(?:\.\d+)?(?:e[+\-]?\d+)?)/i,null,"0123456789"]],[["com",/^%[^\n]*/],["kwd",/^(?:module|attributes|do|let|in|letrec|apply|call|primop|case|of|end|when|fun|try|catch|receive|after|char|integer|float,atom,string,var)\b/], +["kwd",/^-[a-z_]+/],["typ",/^[A-Z_][a-zA-Z0-9_]*/],["pun",/^[.,;]/]]),["erlang","erl"]); diff --git a/app/src/main/assets/highlight/js/lang-fs.js b/app/src/main/assets/highlight/js/lang-fs.js new file mode 100644 index 00000000..c012a3ff --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-fs.js @@ -0,0 +1,18 @@ +/* + + Copyright (C) 2008 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["com",/^#(?:if[\t\n\r \xA0]+(?:[a-z_$][\w\']*|``[^\r\n\t`]*(?:``|$))|else|endif|light)/i,null,"#"],["str",/^(?:\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)|\'(?:[^\'\\]|\\[\s\S])(?:\'|$))/,null,"\"'"]],[["com",/^(?:\/\/[^\r\n]*|\(\*[\s\S]*?\*\))/],["kwd",/^(?:abstract|and|as|assert|begin|class|default|delegate|do|done|downcast|downto|elif|else|end|exception|extern|false|finally|for|fun|function|if|in|inherit|inline|interface|internal|lazy|let|match|member|module|mutable|namespace|new|null|of|open|or|override|private|public|rec|return|static|struct|then|to|true|try|type|upcast|use|val|void|when|while|with|yield|asr|land|lor|lsl|lsr|lxor|mod|sig|atomic|break|checked|component|const|constraint|constructor|continue|eager|event|external|fixed|functor|global|include|method|mixin|object|parallel|process|protected|pure|sealed|trait|virtual|volatile)\b/], +["lit",/^[+\-]?(?:0x[\da-f]+|(?:(?:\.\d+|\d+(?:\.\d*)?)(?:e[+\-]?\d+)?))/i],["pln",/^(?:[a-z_][\w']*[!?#]?|``[^\r\n\t`]*(?:``|$))/i],["pun",/^[^\t\n\r \xA0\"\'\w]+/]]),["fs","ml"]); diff --git a/app/src/main/assets/highlight/js/lang-go.js b/app/src/main/assets/highlight/js/lang-go.js new file mode 100644 index 00000000..1f6934a4 --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-go.js @@ -0,0 +1,17 @@ +/* + + Copyright (C) 2010 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["pln",/^(?:\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)|\'(?:[^\'\\]|\\[\s\S])+(?:\'|$)|`[^`]*(?:`|$))/,null,"\"'"]],[["com",/^(?:\/\/[^\r\n]*|\/\*[\s\S]*?\*\/)/],["pln",/^(?:[^\/\"\'`]|\/(?![\/\*]))+/i]]),["go"]); diff --git a/app/src/main/assets/highlight/js/lang-hs.js b/app/src/main/assets/highlight/js/lang-hs.js new file mode 100644 index 00000000..2002221e --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-hs.js @@ -0,0 +1,18 @@ +/* + + Copyright (C) 2009 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\x0B\x0C\r ]+/,null,"\t\n\x0B\f\r "],["str",/^\"(?:[^\"\\\n\x0C\r]|\\[\s\S])*(?:\"|$)/,null,'"'],["str",/^\'(?:[^\'\\\n\x0C\r]|\\[^&])\'?/,null,"'"],["lit",/^(?:0o[0-7]+|0x[\da-f]+|\d+(?:\.\d+)?(?:e[+\-]?\d+)?)/i,null,"0123456789"]],[["com",/^(?:(?:--+(?:[^\r\n\x0C]*)?)|(?:\{-(?:[^-]|-+[^-\}])*-\}))/],["kwd",/^(?:case|class|data|default|deriving|do|else|if|import|in|infix|infixl|infixr|instance|let|module|newtype|of|then|type|where|_)(?=[^a-zA-Z0-9\']|$)/, +null],["pln",/^(?:[A-Z][\w\']*\.)*[a-zA-Z][\w\']*/],["pun",/^[^\t\n\x0B\x0C\r a-zA-Z0-9\'\"]+/]]),["hs"]); diff --git a/app/src/main/assets/highlight/js/lang-lasso.js b/app/src/main/assets/highlight/js/lang-lasso.js new file mode 100644 index 00000000..415ca677 --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-lasso.js @@ -0,0 +1,19 @@ +/* + + Copyright (C) 2013 Eric Knibbe + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["str",/^\'(?:[^\'\\]|\\[\s\S])*(?:\'|$)/,null,"'"],["str",/^\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)/,null,'"'],["str",/^\`[^\`]*(?:\`|$)/,null,"`"],["lit",/^0x[\da-f]+|\d+/i,null,"0123456789"],["atn",/^#\d+|[#$][a-z_][\w.]*|#![ \S]+lasso9\b/i,null,"#$"]],[["tag",/^[[\]]|<\?(?:lasso(?:script)?|=)|\?>|noprocess\b|no_square_brackets\b/i],["com",/^\/\/[^\r\n]*|\/\*[\s\S]*?\*\//], +["atn",/^-(?!infinity)[a-z_][\w.]*|\.\s*'[a-z_][\w.]*'/i],["lit",/^\d*\.\d+(?:e[-+]?\d+)?|infinity\b|NaN\b/i],["atv",/^::\s*[a-z_][\w.]*/i],["lit",/^(?:true|false|none|minimal|full|all|void|and|or|not|bw|nbw|ew|new|cn|ncn|lt|lte|gt|gte|eq|neq|rx|nrx|ft)\b/i],["kwd",/^(?:error_code|error_msg|error_pop|error_push|error_reset|cache|database_names|database_schemanames|database_tablenames|define_tag|define_type|email_batch|encode_set|html_comment|handle|handle_error|header|if|inline|iterate|ljax_target|link|link_currentaction|link_currentgroup|link_currentrecord|link_detail|link_firstgroup|link_firstrecord|link_lastgroup|link_lastrecord|link_nextgroup|link_nextrecord|link_prevgroup|link_prevrecord|log|loop|namespace_using|output_none|portal|private|protect|records|referer|referrer|repeating|resultset|rows|search_args|search_arguments|select|sort_args|sort_arguments|thread_atomic|value_list|while|abort|case|else|if_empty|if_false|if_null|if_true|loop_abort|loop_continue|loop_count|params|params_up|return|return_value|run_children|soap_definetag|soap_lastrequest|soap_lastresponse|tag_name|ascending|average|by|define|descending|do|equals|frozen|group|handle_failure|import|in|into|join|let|match|max|min|on|order|parent|protected|provide|public|require|returnhome|skip|split_thread|sum|take|thread|to|trait|type|where|with|yield|yieldhome)\b/i], +["typ",/^(?:array|date|decimal|duration|integer|map|pair|string|tag|xml|null|boolean|bytes|keyword|list|locale|queue|set|stack|staticarray|local|var|variable|global|data|self|inherited|currentcapture|givenblock)\b|^\.\.?/i],["pln",/^[a-z_][\w.]*(?:=\s*(?=\())?/i],["pun",/^:=|[-+*\/%=<>&|!?\\]/]]),["lasso","ls","lassoscript"]); diff --git a/app/src/main/assets/highlight/js/lang-lassoscript.js b/app/src/main/assets/highlight/js/lang-lassoscript.js new file mode 100644 index 00000000..415ca677 --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-lassoscript.js @@ -0,0 +1,19 @@ +/* + + Copyright (C) 2013 Eric Knibbe + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["str",/^\'(?:[^\'\\]|\\[\s\S])*(?:\'|$)/,null,"'"],["str",/^\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)/,null,'"'],["str",/^\`[^\`]*(?:\`|$)/,null,"`"],["lit",/^0x[\da-f]+|\d+/i,null,"0123456789"],["atn",/^#\d+|[#$][a-z_][\w.]*|#![ \S]+lasso9\b/i,null,"#$"]],[["tag",/^[[\]]|<\?(?:lasso(?:script)?|=)|\?>|noprocess\b|no_square_brackets\b/i],["com",/^\/\/[^\r\n]*|\/\*[\s\S]*?\*\//], +["atn",/^-(?!infinity)[a-z_][\w.]*|\.\s*'[a-z_][\w.]*'/i],["lit",/^\d*\.\d+(?:e[-+]?\d+)?|infinity\b|NaN\b/i],["atv",/^::\s*[a-z_][\w.]*/i],["lit",/^(?:true|false|none|minimal|full|all|void|and|or|not|bw|nbw|ew|new|cn|ncn|lt|lte|gt|gte|eq|neq|rx|nrx|ft)\b/i],["kwd",/^(?:error_code|error_msg|error_pop|error_push|error_reset|cache|database_names|database_schemanames|database_tablenames|define_tag|define_type|email_batch|encode_set|html_comment|handle|handle_error|header|if|inline|iterate|ljax_target|link|link_currentaction|link_currentgroup|link_currentrecord|link_detail|link_firstgroup|link_firstrecord|link_lastgroup|link_lastrecord|link_nextgroup|link_nextrecord|link_prevgroup|link_prevrecord|log|loop|namespace_using|output_none|portal|private|protect|records|referer|referrer|repeating|resultset|rows|search_args|search_arguments|select|sort_args|sort_arguments|thread_atomic|value_list|while|abort|case|else|if_empty|if_false|if_null|if_true|loop_abort|loop_continue|loop_count|params|params_up|return|return_value|run_children|soap_definetag|soap_lastrequest|soap_lastresponse|tag_name|ascending|average|by|define|descending|do|equals|frozen|group|handle_failure|import|in|into|join|let|match|max|min|on|order|parent|protected|provide|public|require|returnhome|skip|split_thread|sum|take|thread|to|trait|type|where|with|yield|yieldhome)\b/i], +["typ",/^(?:array|date|decimal|duration|integer|map|pair|string|tag|xml|null|boolean|bytes|keyword|list|locale|queue|set|stack|staticarray|local|var|variable|global|data|self|inherited|currentcapture|givenblock)\b|^\.\.?/i],["pln",/^[a-z_][\w.]*(?:=\s*(?=\())?/i],["pun",/^:=|[-+*\/%=<>&|!?\\]/]]),["lasso","ls","lassoscript"]); diff --git a/app/src/main/assets/highlight/js/lang-latex.js b/app/src/main/assets/highlight/js/lang-latex.js new file mode 100644 index 00000000..efc758cd --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-latex.js @@ -0,0 +1,17 @@ +/* + + Copyright (C) 2011 Martin S. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["com",/^%[^\r\n]*/,null,"%"]],[["kwd",/^\\[a-zA-Z@]+/],["kwd",/^\\./],["typ",/^[$&]/],["lit",/[+-]?(?:\.\d+|\d+(?:\.\d*)?)(cm|em|ex|in|pc|pt|bp|mm)/i],["pun",/^[{}()\[\]=]+/]]),["latex","tex"]); diff --git a/app/src/main/assets/highlight/js/lang-lgt.js b/app/src/main/assets/highlight/js/lang-lgt.js new file mode 100644 index 00000000..2959d759 --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-lgt.js @@ -0,0 +1,18 @@ +/* + + Copyright (C) 2014 Paulo Moura + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["str",/^\"(?:[^\"\\\n\x0C\r]|\\[\s\S])*(?:\"|$)/,null,'"'],["lit",/^[a-z][a-zA-Z0-9_]*/],["lit",/^\'(?:[^\'\\\n\x0C\r]|\\[^&])+\'?/,null,"'"],["lit",/^(?:0'.|0b[0-1]+|0o[0-7]+|0x[\da-f]+|\d+(?:\.\d+)?(?:e[+\-]?\d+)?)/i,null,"0123456789"]],[["com",/^%[^\r\n]*/,null,"%"],["com",/^\/\*[\s\S]*?\*\//],["kwd",/^\s*:-\s(c(a(lls|tegory)|oinductive)|p(ublic|r(ot(ocol|ected)|ivate))|e(l(if|se)|n(coding|sure_loaded)|xport)|i(f|n(clude|itialization|fo))|alias|d(ynamic|iscontiguous)|m(eta_(non_terminal|predicate)|od(e|ule)|ultifile)|reexport|s(et_(logtalk|prolog)_flag|ynchronized)|o(bject|p)|use(s|_module))/], +["kwd",/^\s*:-\s(e(lse|nd(if|_(category|object|protocol)))|built_in|dynamic|synchronized|threaded)/],["typ",/^[A-Z_][a-zA-Z0-9_]*/],["pun",/^[.,;{}:^<>=\\/+*?#!-]/]]),["logtalk","lgt"]); diff --git a/app/src/main/assets/highlight/js/lang-lisp.js b/app/src/main/assets/highlight/js/lang-lisp.js new file mode 100644 index 00000000..2f18c967 --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-lisp.js @@ -0,0 +1,18 @@ +/* + + Copyright (C) 2008 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["opn",/^\(+/,null,"("],["clo",/^\)+/,null,")"],["com",/^;[^\r\n]*/,null,";"],["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["str",/^\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)/,null,'"']],[["kwd",/^(?:block|c[ad]+r|catch|con[ds]|def(?:ine|un)|do|eq|eql|equal|equalp|eval-when|flet|format|go|if|labels|lambda|let|load-time-value|locally|macrolet|multiple-value-call|nil|progn|progv|quote|require|return-from|setq|symbol-macrolet|t|tagbody|the|throw|unwind)\b/, +null],["lit",/^[+\-]?(?:[0#]x[0-9a-f]+|\d+\/\d+|(?:\.\d+|\d+(?:\.\d*)?)(?:[ed][+\-]?\d+)?)/i],["lit",/^\'(?:-*(?:\w|\\[\x21-\x7e])(?:[\w-]*|\\[\x21-\x7e])[=!?]?)?/],["pln",/^-*(?:[a-z_]|\\[\x21-\x7e])(?:[\w-]*|\\[\x21-\x7e])[=!?]?/i],["pun",/^[^\w\t\n\r \xA0()\"\\\';]+/]]),"cl el lisp lsp scm ss rkt".split(" ")); diff --git a/app/src/main/assets/highlight/js/lang-ll.js b/app/src/main/assets/highlight/js/lang-ll.js new file mode 100644 index 00000000..7604d962 --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-ll.js @@ -0,0 +1,17 @@ +/* + + Copyright (C) 2013 Nikhil Dabas + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["str",/^!?\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)/,null,'"'],["com",/^;[^\r\n]*/,null,";"]],[["pln",/^[%@!](?:[-a-zA-Z$._][-a-zA-Z$._0-9]*|\d+)/],["kwd",/^[A-Za-z_][0-9A-Za-z_]*/,null],["lit",/^\d+\.\d+/],["lit",/^(?:\d+|0[xX][a-fA-F0-9]+)/],["pun",/^[()\[\]{},=*<>:]|\.\.\.$/]]),["llvm","ll"]); diff --git a/app/src/main/assets/highlight/js/lang-llvm.js b/app/src/main/assets/highlight/js/lang-llvm.js new file mode 100644 index 00000000..7604d962 --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-llvm.js @@ -0,0 +1,17 @@ +/* + + Copyright (C) 2013 Nikhil Dabas + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["str",/^!?\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)/,null,'"'],["com",/^;[^\r\n]*/,null,";"]],[["pln",/^[%@!](?:[-a-zA-Z$._][-a-zA-Z$._0-9]*|\d+)/],["kwd",/^[A-Za-z_][0-9A-Za-z_]*/,null],["lit",/^\d+\.\d+/],["lit",/^(?:\d+|0[xX][a-fA-F0-9]+)/],["pun",/^[()\[\]{},=*<>:]|\.\.\.$/]]),["llvm","ll"]); diff --git a/app/src/main/assets/highlight/js/lang-logtalk.js b/app/src/main/assets/highlight/js/lang-logtalk.js new file mode 100644 index 00000000..2959d759 --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-logtalk.js @@ -0,0 +1,18 @@ +/* + + Copyright (C) 2014 Paulo Moura + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["str",/^\"(?:[^\"\\\n\x0C\r]|\\[\s\S])*(?:\"|$)/,null,'"'],["lit",/^[a-z][a-zA-Z0-9_]*/],["lit",/^\'(?:[^\'\\\n\x0C\r]|\\[^&])+\'?/,null,"'"],["lit",/^(?:0'.|0b[0-1]+|0o[0-7]+|0x[\da-f]+|\d+(?:\.\d+)?(?:e[+\-]?\d+)?)/i,null,"0123456789"]],[["com",/^%[^\r\n]*/,null,"%"],["com",/^\/\*[\s\S]*?\*\//],["kwd",/^\s*:-\s(c(a(lls|tegory)|oinductive)|p(ublic|r(ot(ocol|ected)|ivate))|e(l(if|se)|n(coding|sure_loaded)|xport)|i(f|n(clude|itialization|fo))|alias|d(ynamic|iscontiguous)|m(eta_(non_terminal|predicate)|od(e|ule)|ultifile)|reexport|s(et_(logtalk|prolog)_flag|ynchronized)|o(bject|p)|use(s|_module))/], +["kwd",/^\s*:-\s(e(lse|nd(if|_(category|object|protocol)))|built_in|dynamic|synchronized|threaded)/],["typ",/^[A-Z_][a-zA-Z0-9_]*/],["pun",/^[.,;{}:^<>=\\/+*?#!-]/]]),["logtalk","lgt"]); diff --git a/app/src/main/assets/highlight/js/lang-ls.js b/app/src/main/assets/highlight/js/lang-ls.js new file mode 100644 index 00000000..415ca677 --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-ls.js @@ -0,0 +1,19 @@ +/* + + Copyright (C) 2013 Eric Knibbe + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["str",/^\'(?:[^\'\\]|\\[\s\S])*(?:\'|$)/,null,"'"],["str",/^\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)/,null,'"'],["str",/^\`[^\`]*(?:\`|$)/,null,"`"],["lit",/^0x[\da-f]+|\d+/i,null,"0123456789"],["atn",/^#\d+|[#$][a-z_][\w.]*|#![ \S]+lasso9\b/i,null,"#$"]],[["tag",/^[[\]]|<\?(?:lasso(?:script)?|=)|\?>|noprocess\b|no_square_brackets\b/i],["com",/^\/\/[^\r\n]*|\/\*[\s\S]*?\*\//], +["atn",/^-(?!infinity)[a-z_][\w.]*|\.\s*'[a-z_][\w.]*'/i],["lit",/^\d*\.\d+(?:e[-+]?\d+)?|infinity\b|NaN\b/i],["atv",/^::\s*[a-z_][\w.]*/i],["lit",/^(?:true|false|none|minimal|full|all|void|and|or|not|bw|nbw|ew|new|cn|ncn|lt|lte|gt|gte|eq|neq|rx|nrx|ft)\b/i],["kwd",/^(?:error_code|error_msg|error_pop|error_push|error_reset|cache|database_names|database_schemanames|database_tablenames|define_tag|define_type|email_batch|encode_set|html_comment|handle|handle_error|header|if|inline|iterate|ljax_target|link|link_currentaction|link_currentgroup|link_currentrecord|link_detail|link_firstgroup|link_firstrecord|link_lastgroup|link_lastrecord|link_nextgroup|link_nextrecord|link_prevgroup|link_prevrecord|log|loop|namespace_using|output_none|portal|private|protect|records|referer|referrer|repeating|resultset|rows|search_args|search_arguments|select|sort_args|sort_arguments|thread_atomic|value_list|while|abort|case|else|if_empty|if_false|if_null|if_true|loop_abort|loop_continue|loop_count|params|params_up|return|return_value|run_children|soap_definetag|soap_lastrequest|soap_lastresponse|tag_name|ascending|average|by|define|descending|do|equals|frozen|group|handle_failure|import|in|into|join|let|match|max|min|on|order|parent|protected|provide|public|require|returnhome|skip|split_thread|sum|take|thread|to|trait|type|where|with|yield|yieldhome)\b/i], +["typ",/^(?:array|date|decimal|duration|integer|map|pair|string|tag|xml|null|boolean|bytes|keyword|list|locale|queue|set|stack|staticarray|local|var|variable|global|data|self|inherited|currentcapture|givenblock)\b|^\.\.?/i],["pln",/^[a-z_][\w.]*(?:=\s*(?=\())?/i],["pun",/^:=|[-+*\/%=<>&|!?\\]/]]),["lasso","ls","lassoscript"]); diff --git a/app/src/main/assets/highlight/js/lang-lsp.js b/app/src/main/assets/highlight/js/lang-lsp.js new file mode 100644 index 00000000..2f18c967 --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-lsp.js @@ -0,0 +1,18 @@ +/* + + Copyright (C) 2008 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["opn",/^\(+/,null,"("],["clo",/^\)+/,null,")"],["com",/^;[^\r\n]*/,null,";"],["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["str",/^\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)/,null,'"']],[["kwd",/^(?:block|c[ad]+r|catch|con[ds]|def(?:ine|un)|do|eq|eql|equal|equalp|eval-when|flet|format|go|if|labels|lambda|let|load-time-value|locally|macrolet|multiple-value-call|nil|progn|progv|quote|require|return-from|setq|symbol-macrolet|t|tagbody|the|throw|unwind)\b/, +null],["lit",/^[+\-]?(?:[0#]x[0-9a-f]+|\d+\/\d+|(?:\.\d+|\d+(?:\.\d*)?)(?:[ed][+\-]?\d+)?)/i],["lit",/^\'(?:-*(?:\w|\\[\x21-\x7e])(?:[\w-]*|\\[\x21-\x7e])[=!?]?)?/],["pln",/^-*(?:[a-z_]|\\[\x21-\x7e])(?:[\w-]*|\\[\x21-\x7e])[=!?]?/i],["pun",/^[^\w\t\n\r \xA0()\"\\\';]+/]]),"cl el lisp lsp scm ss rkt".split(" ")); diff --git a/app/src/main/assets/highlight/js/lang-lua.js b/app/src/main/assets/highlight/js/lang-lua.js new file mode 100644 index 00000000..afb2901e --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-lua.js @@ -0,0 +1,18 @@ +/* + + Copyright (C) 2008 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["str",/^(?:\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)|\'(?:[^\'\\]|\\[\s\S])*(?:\'|$))/,null,"\"'"]],[["com",/^--(?:\[(=*)\[[\s\S]*?(?:\]\1\]|$)|[^\r\n]*)/],["str",/^\[(=*)\[[\s\S]*?(?:\]\1\]|$)/],["kwd",/^(?:and|break|do|else|elseif|end|false|for|function|if|in|local|nil|not|or|repeat|return|then|true|until|while)\b/,null],["lit",/^[+-]?(?:0x[\da-f]+|(?:(?:\.\d+|\d+(?:\.\d*)?)(?:e[+\-]?\d+)?))/i], +["pln",/^[a-z_]\w*/i],["pun",/^[^\w\t\n\r \xA0][^\w\t\n\r \xA0\"\'\-\+=]*/]]),["lua"]); diff --git a/app/src/main/assets/highlight/js/lang-matlab.js b/app/src/main/assets/highlight/js/lang-matlab.js new file mode 100644 index 00000000..a0522a5a --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-matlab.js @@ -0,0 +1,29 @@ +/* + + Copyright (c) 2013 by Amro + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ +var a=window.PR,b=[[a.PR_PLAIN,/^[ \t\r\n\v\f\xA0]+/,null," \t\r\n\x0B\f\u00a0"],[a.PR_COMMENT,/^%\{[^%]*%+(?:[^\}%][^%]*%+)*\}/,null],[a.PR_COMMENT,/^%[^\r\n]*/,null,"%"],["syscmd",/^![^\r\n]*/,null,"!"]],c=[["linecont",/^\.\.\.\s*[\r\n]/,null],["err",/^\?\?\? [^\r\n]*/,null],["wrn",/^Warning: [^\r\n]*/,null],["codeoutput",/^>>\s+/,null],["codeoutput",/^octave:\d+>\s+/,null],["lang-matlab-operators",/^((?:[a-zA-Z][a-zA-Z0-9_]*(?:\.[a-zA-Z][a-zA-Z0-9_]*)*|\)|\]|\}|\.)')/,null],["lang-matlab-identifiers", +/^([a-zA-Z][a-zA-Z0-9_]*(?:\.[a-zA-Z][a-zA-Z0-9_]*)*)(?!')/,null],[a.PR_STRING,/^'(?:[^']|'')*'/,null],[a.PR_LITERAL,/^[+\-]?\.?\d+(?:\.\d*)?(?:[Ee][+\-]?\d+)?[ij]?/,null],[a.PR_TAG,/^(?:\{|\}|\(|\)|\[|\])/,null],[a.PR_PUNCTUATION,/^(?:<|>|=|~|@|&|;|,|:|!|\-|\+|\*|\^|\.|\||\\|\/)/,null]],d=[["lang-matlab-identifiers",/^([a-zA-Z][a-zA-Z0-9_]*(?:\.[a-zA-Z][a-zA-Z0-9_]*)*)/,null],[a.PR_TAG,/^(?:\{|\}|\(|\)|\[|\])/,null],[a.PR_PUNCTUATION,/^(?:<|>|=|~|@|&|;|,|:|!|\-|\+|\*|\^|\.|\||\\|\/)/,null],["transpose", +/^'/,null]]; +a.registerLangHandler(a.createSimpleLexer([],[[a.PR_KEYWORD,/^\b(?:break|case|catch|classdef|continue|else|elseif|end|for|function|global|if|otherwise|parfor|persistent|return|spmd|switch|try|while)\b/,null],["const",/^\b(?:true|false|inf|Inf|nan|NaN|eps|pi|ans|nargin|nargout|varargin|varargout)\b/,null],[a.PR_TYPE,/^\b(?:cell|struct|char|double|single|logical|u?int(?:8|16|32|64)|sparse)\b/,null],["fun",/^\b(?:abs|accumarray|acos(?:d|h)?|acot(?:d|h)?|acsc(?:d|h)?|actxcontrol(?:list|select)?|actxGetRunningServer|actxserver|addlistener|addpath|addpref|addtodate|airy|align|alim|all|allchild|alpha|alphamap|amd|ancestor|and|angle|annotation|any|area|arrayfun|asec(?:d|h)?|asin(?:d|h)?|assert|assignin|atan(?:2|d|h)?|audiodevinfo|audioplayer|audiorecorder|aufinfo|auread|autumn|auwrite|avifile|aviinfo|aviread|axes|axis|balance|bar(?:3|3h|h)?|base2dec|beep|BeginInvoke|bench|bessel(?:h|i|j|k|y)|beta|betainc|betaincinv|betaln|bicg|bicgstab|bicgstabl|bin2dec|bitand|bitcmp|bitget|bitmax|bitnot|bitor|bitset|bitshift|bitxor|blanks|blkdiag|bone|box|brighten|brush|bsxfun|builddocsearchdb|builtin|bvp4c|bvp5c|bvpget|bvpinit|bvpset|bvpxtend|calendar|calllib|callSoapService|camdolly|cameratoolbar|camlight|camlookat|camorbit|campan|campos|camproj|camroll|camtarget|camup|camva|camzoom|cart2pol|cart2sph|cast|cat|caxis|cd|cdf2rdf|cdfepoch|cdfinfo|cdflib(?:.(?:close|closeVar|computeEpoch|computeEpoch16|create|createAttr|createVar|delete|deleteAttr|deleteAttrEntry|deleteAttrgEntry|deleteVar|deleteVarRecords|epoch16Breakdown|epochBreakdown|getAttrEntry|getAttrgEntry|getAttrMaxEntry|getAttrMaxgEntry|getAttrName|getAttrNum|getAttrScope|getCacheSize|getChecksum|getCompression|getCompressionCacheSize|getConstantNames|getConstantValue|getCopyright|getFileBackward|getFormat|getLibraryCopyright|getLibraryVersion|getMajority|getName|getNumAttrEntries|getNumAttrgEntries|getNumAttributes|getNumgAttributes|getReadOnlyMode|getStageCacheSize|getValidate|getVarAllocRecords|getVarBlockingFactor|getVarCacheSize|getVarCompression|getVarData|getVarMaxAllocRecNum|getVarMaxWrittenRecNum|getVarName|getVarNum|getVarNumRecsWritten|getVarPadValue|getVarRecordData|getVarReservePercent|getVarsMaxWrittenRecNum|getVarSparseRecords|getVersion|hyperGetVarData|hyperPutVarData|inquire|inquireAttr|inquireAttrEntry|inquireAttrgEntry|inquireVar|open|putAttrEntry|putAttrgEntry|putVarData|putVarRecordData|renameAttr|renameVar|setCacheSize|setChecksum|setCompression|setCompressionCacheSize|setFileBackward|setFormat|setMajority|setReadOnlyMode|setStageCacheSize|setValidate|setVarAllocBlockRecords|setVarBlockingFactor|setVarCacheSize|setVarCompression|setVarInitialRecs|setVarPadValue|SetVarReservePercent|setVarsCacheSize|setVarSparseRecords))?|cdfread|cdfwrite|ceil|cell2mat|cell2struct|celldisp|cellfun|cellplot|cellstr|cgs|checkcode|checkin|checkout|chol|cholinc|cholupdate|circshift|cla|clabel|class|clc|clear|clearvars|clf|clipboard|clock|close|closereq|cmopts|cmpermute|cmunique|colamd|colon|colorbar|colordef|colormap|colormapeditor|colperm|Combine|comet|comet3|commandhistory|commandwindow|compan|compass|complex|computer|cond|condeig|condest|coneplot|conj|containers.Map|contour(?:3|c|f|slice)?|contrast|conv|conv2|convhull|convhulln|convn|cool|copper|copyfile|copyobj|corrcoef|cos(?:d|h)?|cot(?:d|h)?|cov|cplxpair|cputime|createClassFromWsdl|createSoapMessage|cross|csc(?:d|h)?|csvread|csvwrite|ctranspose|cumprod|cumsum|cumtrapz|curl|customverctrl|cylinder|daqread|daspect|datacursormode|datatipinfo|date|datenum|datestr|datetick|datevec|dbclear|dbcont|dbdown|dblquad|dbmex|dbquit|dbstack|dbstatus|dbstep|dbstop|dbtype|dbup|dde23|ddeget|ddesd|ddeset|deal|deblank|dec2base|dec2bin|dec2hex|decic|deconv|del2|delaunay|delaunay3|delaunayn|DelaunayTri|delete|demo|depdir|depfun|det|detrend|deval|diag|dialog|diary|diff|diffuse|dir|disp|display|dither|divergence|dlmread|dlmwrite|dmperm|doc|docsearch|dos|dot|dragrect|drawnow|dsearch|dsearchn|dynamicprops|echo|echodemo|edit|eig|eigs|ellipj|ellipke|ellipsoid|empty|enableNETfromNetworkDrive|enableservice|EndInvoke|enumeration|eomday|eq|erf|erfc|erfcinv|erfcx|erfinv|error|errorbar|errordlg|etime|etree|etreeplot|eval|evalc|evalin|event.(?:EventData|listener|PropertyEvent|proplistener)|exifread|exist|exit|exp|expint|expm|expm1|export2wsdlg|eye|ezcontour|ezcontourf|ezmesh|ezmeshc|ezplot|ezplot3|ezpolar|ezsurf|ezsurfc|factor|factorial|fclose|feather|feature|feof|ferror|feval|fft|fft2|fftn|fftshift|fftw|fgetl|fgets|fieldnames|figure|figurepalette|fileattrib|filebrowser|filemarker|fileparts|fileread|filesep|fill|fill3|filter|filter2|find|findall|findfigs|findobj|findstr|finish|fitsdisp|fitsinfo|fitsread|fitswrite|fix|flag|flipdim|fliplr|flipud|floor|flow|fminbnd|fminsearch|fopen|format|fplot|fprintf|frame2im|fread|freqspace|frewind|fscanf|fseek|ftell|FTP|full|fullfile|func2str|functions|funm|fwrite|fzero|gallery|gamma|gammainc|gammaincinv|gammaln|gca|gcbf|gcbo|gcd|gcf|gco|ge|genpath|genvarname|get|getappdata|getenv|getfield|getframe|getpixelposition|getpref|ginput|gmres|gplot|grabcode|gradient|gray|graymon|grid|griddata(?:3|n)?|griddedInterpolant|gsvd|gt|gtext|guidata|guide|guihandles|gunzip|gzip|h5create|h5disp|h5info|h5read|h5readatt|h5write|h5writeatt|hadamard|handle|hankel|hdf|hdf5|hdf5info|hdf5read|hdf5write|hdfinfo|hdfread|hdftool|help|helpbrowser|helpdesk|helpdlg|helpwin|hess|hex2dec|hex2num|hgexport|hggroup|hgload|hgsave|hgsetget|hgtransform|hidden|hilb|hist|histc|hold|home|horzcat|hostid|hot|hsv|hsv2rgb|hypot|ichol|idivide|ifft|ifft2|ifftn|ifftshift|ilu|im2frame|im2java|imag|image|imagesc|imapprox|imfinfo|imformats|import|importdata|imread|imwrite|ind2rgb|ind2sub|inferiorto|info|inline|inmem|inpolygon|input|inputdlg|inputname|inputParser|inspect|instrcallback|instrfind|instrfindall|int2str|integral(?:2|3)?|interp(?:1|1q|2|3|ft|n)|interpstreamspeed|intersect|intmax|intmin|inv|invhilb|ipermute|isa|isappdata|iscell|iscellstr|ischar|iscolumn|isdir|isempty|isequal|isequaln|isequalwithequalnans|isfield|isfinite|isfloat|isglobal|ishandle|ishghandle|ishold|isinf|isinteger|isjava|iskeyword|isletter|islogical|ismac|ismatrix|ismember|ismethod|isnan|isnumeric|isobject|isocaps|isocolors|isonormals|isosurface|ispc|ispref|isprime|isprop|isreal|isrow|isscalar|issorted|isspace|issparse|isstr|isstrprop|isstruct|isstudent|isunix|isvarname|isvector|javaaddpath|javaArray|javachk|javaclasspath|javacomponent|javaMethod|javaMethodEDT|javaObject|javaObjectEDT|javarmpath|jet|keyboard|kron|lasterr|lasterror|lastwarn|lcm|ldivide|ldl|le|legend|legendre|length|libfunctions|libfunctionsview|libisloaded|libpointer|libstruct|license|light|lightangle|lighting|lin2mu|line|lines|linkaxes|linkdata|linkprop|linsolve|linspace|listdlg|listfonts|load|loadlibrary|loadobj|log|log10|log1p|log2|loglog|logm|logspace|lookfor|lower|ls|lscov|lsqnonneg|lsqr|lt|lu|luinc|magic|makehgtform|mat2cell|mat2str|material|matfile|matlab.io.MatFile|matlab.mixin.(?:Copyable|Heterogeneous(?:.getDefaultScalarElement)?)|matlabrc|matlabroot|max|maxNumCompThreads|mean|median|membrane|memmapfile|memory|menu|mesh|meshc|meshgrid|meshz|meta.(?:class(?:.fromName)?|DynamicProperty|EnumeratedValue|event|MetaData|method|package(?:.(?:fromName|getAllPackages))?|property)|metaclass|methods|methodsview|mex(?:.getCompilerConfigurations)?|MException|mexext|mfilename|min|minres|minus|mislocked|mkdir|mkpp|mldivide|mlint|mlintrpt|mlock|mmfileinfo|mmreader|mod|mode|more|move|movefile|movegui|movie|movie2avi|mpower|mrdivide|msgbox|mtimes|mu2lin|multibandread|multibandwrite|munlock|namelengthmax|nargchk|narginchk|nargoutchk|native2unicode|nccreate|ncdisp|nchoosek|ncinfo|ncread|ncreadatt|ncwrite|ncwriteatt|ncwriteschema|ndgrid|ndims|ne|NET(?:.(?:addAssembly|Assembly|convertArray|createArray|createGeneric|disableAutoRelease|enableAutoRelease|GenericClass|invokeGenericMethod|NetException|setStaticProperty))?|netcdf.(?:abort|close|copyAtt|create|defDim|defGrp|defVar|defVarChunking|defVarDeflate|defVarFill|defVarFletcher32|delAtt|endDef|getAtt|getChunkCache|getConstant|getConstantNames|getVar|inq|inqAtt|inqAttID|inqAttName|inqDim|inqDimID|inqDimIDs|inqFormat|inqGrpName|inqGrpNameFull|inqGrpParent|inqGrps|inqLibVers|inqNcid|inqUnlimDims|inqVar|inqVarChunking|inqVarDeflate|inqVarFill|inqVarFletcher32|inqVarID|inqVarIDs|open|putAtt|putVar|reDef|renameAtt|renameDim|renameVar|setChunkCache|setDefaultFormat|setFill|sync)|newplot|nextpow2|nnz|noanimate|nonzeros|norm|normest|not|notebook|now|nthroot|null|num2cell|num2hex|num2str|numel|nzmax|ode(?:113|15i|15s|23|23s|23t|23tb|45)|odeget|odeset|odextend|onCleanup|ones|open|openfig|opengl|openvar|optimget|optimset|or|ordeig|orderfields|ordqz|ordschur|orient|orth|pack|padecoef|pagesetupdlg|pan|pareto|parseSoapResponse|pascal|patch|path|path2rc|pathsep|pathtool|pause|pbaspect|pcg|pchip|pcode|pcolor|pdepe|pdeval|peaks|perl|perms|permute|pie|pink|pinv|planerot|playshow|plot|plot3|plotbrowser|plotedit|plotmatrix|plottools|plotyy|plus|pol2cart|polar|poly|polyarea|polyder|polyeig|polyfit|polyint|polyval|polyvalm|pow2|power|ppval|prefdir|preferences|primes|print|printdlg|printopt|printpreview|prod|profile|profsave|propedit|propertyeditor|psi|publish|PutCharArray|PutFullMatrix|PutWorkspaceData|pwd|qhull|qmr|qr|qrdelete|qrinsert|qrupdate|quad|quad2d|quadgk|quadl|quadv|questdlg|quit|quiver|quiver3|qz|rand|randi|randn|randperm|RandStream(?:.(?:create|getDefaultStream|getGlobalStream|list|setDefaultStream|setGlobalStream))?|rank|rat|rats|rbbox|rcond|rdivide|readasync|real|reallog|realmax|realmin|realpow|realsqrt|record|rectangle|rectint|recycle|reducepatch|reducevolume|refresh|refreshdata|regexp|regexpi|regexprep|regexptranslate|rehash|rem|Remove|RemoveAll|repmat|reset|reshape|residue|restoredefaultpath|rethrow|rgb2hsv|rgb2ind|rgbplot|ribbon|rmappdata|rmdir|rmfield|rmpath|rmpref|rng|roots|rose|rosser|rot90|rotate|rotate3d|round|rref|rsf2csf|run|save|saveas|saveobj|savepath|scatter|scatter3|schur|sec|secd|sech|selectmoveresize|semilogx|semilogy|sendmail|serial|set|setappdata|setdiff|setenv|setfield|setpixelposition|setpref|setstr|setxor|shading|shg|shiftdim|showplottool|shrinkfaces|sign|sin(?:d|h)?|size|slice|smooth3|snapnow|sort|sortrows|sound|soundsc|spalloc|spaugment|spconvert|spdiags|specular|speye|spfun|sph2cart|sphere|spinmap|spline|spones|spparms|sprand|sprandn|sprandsym|sprank|spring|sprintf|spy|sqrt|sqrtm|squeeze|ss2tf|sscanf|stairs|startup|std|stem|stem3|stopasync|str2double|str2func|str2mat|str2num|strcat|strcmp|strcmpi|stream2|stream3|streamline|streamparticles|streamribbon|streamslice|streamtube|strfind|strjust|strmatch|strncmp|strncmpi|strread|strrep|strtok|strtrim|struct2cell|structfun|strvcat|sub2ind|subplot|subsasgn|subsindex|subspace|subsref|substruct|subvolume|sum|summer|superclasses|superiorto|support|surf|surf2patch|surface|surfc|surfl|surfnorm|svd|svds|swapbytes|symamd|symbfact|symmlq|symrcm|symvar|system|tan(?:d|h)?|tar|tempdir|tempname|tetramesh|texlabel|text|textread|textscan|textwrap|tfqmr|throw|tic|Tiff(?:.(?:getTagNames|getVersion))?|timer|timerfind|timerfindall|times|timeseries|title|toc|todatenum|toeplitz|toolboxdir|trace|transpose|trapz|treelayout|treeplot|tril|trimesh|triplequad|triplot|TriRep|TriScatteredInterp|trisurf|triu|tscollection|tsearch|tsearchn|tstool|type|typecast|uibuttongroup|uicontextmenu|uicontrol|uigetdir|uigetfile|uigetpref|uiimport|uimenu|uiopen|uipanel|uipushtool|uiputfile|uiresume|uisave|uisetcolor|uisetfont|uisetpref|uistack|uitable|uitoggletool|uitoolbar|uiwait|uminus|undocheckout|unicode2native|union|unique|unix|unloadlibrary|unmesh|unmkpp|untar|unwrap|unzip|uplus|upper|urlread|urlwrite|usejava|userpath|validateattributes|validatestring|vander|var|vectorize|ver|verctrl|verLessThan|version|vertcat|VideoReader(?:.isPlatformSupported)?|VideoWriter(?:.getProfiles)?|view|viewmtx|visdiff|volumebounds|voronoi|voronoin|wait|waitbar|waitfor|waitforbuttonpress|warndlg|warning|waterfall|wavfinfo|wavplay|wavread|wavrecord|wavwrite|web|weekday|what|whatsnew|which|whitebg|who|whos|wilkinson|winopen|winqueryreg|winter|wk1finfo|wk1read|wk1write|workspace|xlabel|xlim|xlsfinfo|xlsread|xlswrite|xmlread|xmlwrite|xor|xslt|ylabel|ylim|zeros|zip|zlabel|zlim|zoom)\b/,null], +["fun_tbx",/^\b(?:addedvarplot|andrewsplot|anova(?:1|2|n)|ansaribradley|aoctool|barttest|bbdesign|beta(?:cdf|fit|inv|like|pdf|rnd|stat)|bino(?:cdf|fit|inv|pdf|rnd|stat)|biplot|bootci|bootstrp|boxplot|candexch|candgen|canoncorr|capability|capaplot|caseread|casewrite|categorical|ccdesign|cdfplot|chi2(?:cdf|gof|inv|pdf|rnd|stat)|cholcov|Classification(?:BaggedEnsemble|Discriminant(?:.(?:fit|make|template))?|Ensemble|KNN(?:.(?:fit|template))?|PartitionedEnsemble|PartitionedModel|Tree(?:.(?:fit|template))?)|classify|classregtree|cluster|clusterdata|cmdscale|combnk|Compact(?:Classification(?:Discriminant|Ensemble|Tree)|Regression(?:Ensemble|Tree)|TreeBagger)|confusionmat|controlchart|controlrules|cophenet|copula(?:cdf|fit|param|pdf|rnd|stat)|cordexch|corr|corrcov|coxphfit|createns|crosstab|crossval|cvpartition|datasample|dataset|daugment|dcovary|dendrogram|dfittool|disttool|dummyvar|dwtest|ecdf|ecdfhist|ev(?:cdf|fit|inv|like|pdf|rnd|stat)|ExhaustiveSearcher|exp(?:cdf|fit|inv|like|pdf|rnd|stat)|factoran|fcdf|ff2n|finv|fitdist|fitensemble|fpdf|fracfact|fracfactgen|friedman|frnd|fstat|fsurfht|fullfact|gagerr|gam(?:cdf|fit|inv|like|pdf|rnd|stat)|GeneralizedLinearModel(?:.fit)?|geo(?:cdf|inv|mean|pdf|rnd|stat)|gev(?:cdf|fit|inv|like|pdf|rnd|stat)|gline|glmfit|glmval|glyphplot|gmdistribution(?:.fit)?|gname|gp(?:cdf|fit|inv|like|pdf|rnd|stat)|gplotmatrix|grp2idx|grpstats|gscatter|haltonset|harmmean|hist3|histfit|hmm(?:decode|estimate|generate|train|viterbi)|hougen|hyge(?:cdf|inv|pdf|rnd|stat)|icdf|inconsistent|interactionplot|invpred|iqr|iwishrnd|jackknife|jbtest|johnsrnd|KDTreeSearcher|kmeans|knnsearch|kruskalwallis|ksdensity|kstest|kstest2|kurtosis|lasso|lassoglm|lassoPlot|leverage|lhsdesign|lhsnorm|lillietest|LinearModel(?:.fit)?|linhyptest|linkage|logn(?:cdf|fit|inv|like|pdf|rnd|stat)|lsline|mad|mahal|maineffectsplot|manova1|manovacluster|mdscale|mhsample|mle|mlecov|mnpdf|mnrfit|mnrnd|mnrval|moment|multcompare|multivarichart|mvn(?:cdf|pdf|rnd)|mvregress|mvregresslike|mvt(?:cdf|pdf|rnd)|NaiveBayes(?:.fit)?|nan(?:cov|max|mean|median|min|std|sum|var)|nbin(?:cdf|fit|inv|pdf|rnd|stat)|ncf(?:cdf|inv|pdf|rnd|stat)|nct(?:cdf|inv|pdf|rnd|stat)|ncx2(?:cdf|inv|pdf|rnd|stat)|NeighborSearcher|nlinfit|nlintool|nlmefit|nlmefitsa|nlparci|nlpredci|nnmf|nominal|NonLinearModel(?:.fit)?|norm(?:cdf|fit|inv|like|pdf|rnd|stat)|normplot|normspec|ordinal|outlierMeasure|parallelcoords|paretotails|partialcorr|pcacov|pcares|pdf|pdist|pdist2|pearsrnd|perfcurve|perms|piecewisedistribution|plsregress|poiss(?:cdf|fit|inv|pdf|rnd|tat)|polyconf|polytool|prctile|princomp|ProbDist(?:Kernel|Parametric|UnivKernel|UnivParam)?|probplot|procrustes|qqplot|qrandset|qrandstream|quantile|randg|random|randsample|randtool|range|rangesearch|ranksum|rayl(?:cdf|fit|inv|pdf|rnd|stat)|rcoplot|refcurve|refline|regress|Regression(?:BaggedEnsemble|Ensemble|PartitionedEnsemble|PartitionedModel|Tree(?:.(?:fit|template))?)|regstats|relieff|ridge|robustdemo|robustfit|rotatefactors|rowexch|rsmdemo|rstool|runstest|sampsizepwr|scatterhist|sequentialfs|signrank|signtest|silhouette|skewness|slicesample|sobolset|squareform|statget|statset|stepwise|stepwisefit|surfht|tabulate|tblread|tblwrite|tcdf|tdfread|tiedrank|tinv|tpdf|TreeBagger|treedisp|treefit|treeprune|treetest|treeval|trimmean|trnd|tstat|ttest|ttest2|unid(?:cdf|inv|pdf|rnd|stat)|unif(?:cdf|inv|it|pdf|rnd|stat)|vartest(?:2|n)?|wbl(?:cdf|fit|inv|like|pdf|rnd|stat)|wblplot|wishrnd|x2fx|xptread|zscore|ztest)\b/, +null],["fun_tbx",/^\b(?:adapthisteq|analyze75info|analyze75read|applycform|applylut|axes2pix|bestblk|blockproc|bwarea|bwareaopen|bwboundaries|bwconncomp|bwconvhull|bwdist|bwdistgeodesic|bweuler|bwhitmiss|bwlabel|bwlabeln|bwmorph|bwpack|bwperim|bwselect|bwtraceboundary|bwulterode|bwunpack|checkerboard|col2im|colfilt|conndef|convmtx2|corner|cornermetric|corr2|cp2tform|cpcorr|cpselect|cpstruct2pairs|dct2|dctmtx|deconvblind|deconvlucy|deconvreg|deconvwnr|decorrstretch|demosaic|dicom(?:anon|dict|info|lookup|read|uid|write)|edge|edgetaper|entropy|entropyfilt|fan2para|fanbeam|findbounds|fliptform|freqz2|fsamp2|fspecial|ftrans2|fwind1|fwind2|getheight|getimage|getimagemodel|getline|getneighbors|getnhood|getpts|getrangefromclass|getrect|getsequence|gray2ind|graycomatrix|graycoprops|graydist|grayslice|graythresh|hdrread|hdrwrite|histeq|hough|houghlines|houghpeaks|iccfind|iccread|iccroot|iccwrite|idct2|ifanbeam|im2bw|im2col|im2double|im2int16|im2java2d|im2single|im2uint16|im2uint8|imabsdiff|imadd|imadjust|ImageAdapter|imageinfo|imagemodel|imapplymatrix|imattributes|imbothat|imclearborder|imclose|imcolormaptool|imcomplement|imcontour|imcontrast|imcrop|imdilate|imdisplayrange|imdistline|imdivide|imellipse|imerode|imextendedmax|imextendedmin|imfill|imfilter|imfindcircles|imfreehand|imfuse|imgca|imgcf|imgetfile|imhandles|imhist|imhmax|imhmin|imimposemin|imlincomb|imline|immagbox|immovie|immultiply|imnoise|imopen|imoverview|imoverviewpanel|impixel|impixelinfo|impixelinfoval|impixelregion|impixelregionpanel|implay|impoint|impoly|impositionrect|improfile|imputfile|impyramid|imreconstruct|imrect|imregconfig|imregionalmax|imregionalmin|imregister|imresize|imroi|imrotate|imsave|imscrollpanel|imshow|imshowpair|imsubtract|imtool|imtophat|imtransform|imview|ind2gray|ind2rgb|interfileinfo|interfileread|intlut|ippl|iptaddcallback|iptcheckconn|iptcheckhandle|iptcheckinput|iptcheckmap|iptchecknargin|iptcheckstrs|iptdemos|iptgetapi|iptGetPointerBehavior|iptgetpref|ipticondir|iptnum2ordinal|iptPointerManager|iptprefs|iptremovecallback|iptSetPointerBehavior|iptsetpref|iptwindowalign|iradon|isbw|isflat|isgray|isicc|isind|isnitf|isrgb|isrset|lab2double|lab2uint16|lab2uint8|label2rgb|labelmatrix|makecform|makeConstrainToRectFcn|makehdr|makelut|makeresampler|maketform|mat2gray|mean2|medfilt2|montage|nitfinfo|nitfread|nlfilter|normxcorr2|ntsc2rgb|openrset|ordfilt2|otf2psf|padarray|para2fan|phantom|poly2mask|psf2otf|qtdecomp|qtgetblk|qtsetblk|radon|rangefilt|reflect|regionprops|registration.metric.(?:MattesMutualInformation|MeanSquares)|registration.optimizer.(?:OnePlusOneEvolutionary|RegularStepGradientDescent)|rgb2gray|rgb2ntsc|rgb2ycbcr|roicolor|roifill|roifilt2|roipoly|rsetwrite|std2|stdfilt|strel|stretchlim|subimage|tformarray|tformfwd|tforminv|tonemap|translate|truesize|uintlut|viscircles|warp|watershed|whitepoint|wiener2|xyz2double|xyz2uint16|ycbcr2rgb)\b/, +null],["fun_tbx",/^\b(?:bintprog|color|fgoalattain|fminbnd|fmincon|fminimax|fminsearch|fminunc|fseminf|fsolve|fzero|fzmult|gangstr|ktrlink|linprog|lsqcurvefit|lsqlin|lsqnonlin|lsqnonneg|optimget|optimset|optimtool|quadprog)\b/,null],["ident",/^[a-zA-Z][a-zA-Z0-9_]*(?:\.[a-zA-Z][a-zA-Z0-9_]*)*/,null]]),["matlab-identifiers"]);a.registerLangHandler(a.createSimpleLexer([],d),["matlab-operators"]);a.registerLangHandler(a.createSimpleLexer(b,c),["matlab"]); diff --git a/app/src/main/assets/highlight/js/lang-ml.js b/app/src/main/assets/highlight/js/lang-ml.js new file mode 100644 index 00000000..c012a3ff --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-ml.js @@ -0,0 +1,18 @@ +/* + + Copyright (C) 2008 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["com",/^#(?:if[\t\n\r \xA0]+(?:[a-z_$][\w\']*|``[^\r\n\t`]*(?:``|$))|else|endif|light)/i,null,"#"],["str",/^(?:\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)|\'(?:[^\'\\]|\\[\s\S])(?:\'|$))/,null,"\"'"]],[["com",/^(?:\/\/[^\r\n]*|\(\*[\s\S]*?\*\))/],["kwd",/^(?:abstract|and|as|assert|begin|class|default|delegate|do|done|downcast|downto|elif|else|end|exception|extern|false|finally|for|fun|function|if|in|inherit|inline|interface|internal|lazy|let|match|member|module|mutable|namespace|new|null|of|open|or|override|private|public|rec|return|static|struct|then|to|true|try|type|upcast|use|val|void|when|while|with|yield|asr|land|lor|lsl|lsr|lxor|mod|sig|atomic|break|checked|component|const|constraint|constructor|continue|eager|event|external|fixed|functor|global|include|method|mixin|object|parallel|process|protected|pure|sealed|trait|virtual|volatile)\b/], +["lit",/^[+\-]?(?:0x[\da-f]+|(?:(?:\.\d+|\d+(?:\.\d*)?)(?:e[+\-]?\d+)?))/i],["pln",/^(?:[a-z_][\w']*[!?#]?|``[^\r\n\t`]*(?:``|$))/i],["pun",/^[^\t\n\r \xA0\"\'\w]+/]]),["fs","ml"]); diff --git a/app/src/main/assets/highlight/js/lang-mumps.js b/app/src/main/assets/highlight/js/lang-mumps.js new file mode 100644 index 00000000..6d512589 --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-mumps.js @@ -0,0 +1,18 @@ +/* + + Copyright (C) 2011 Kitware Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["str",/^(?:"(?:[^"]|\\.)*")/,null,'"']],[["com",/^;[^\r\n]*/,null,";"],["dec",/^(?:\$(?:D|DEVICE|EC|ECODE|ES|ESTACK|ET|ETRAP|H|HOROLOG|I|IO|J|JOB|K|KEY|P|PRINCIPAL|Q|QUIT|ST|STACK|S|STORAGE|SY|SYSTEM|T|TEST|TL|TLEVEL|TR|TRESTART|X|Y|Z[A-Z]*|A|ASCII|C|CHAR|D|DATA|E|EXTRACT|F|FIND|FN|FNUMBER|G|GET|J|JUSTIFY|L|LENGTH|NA|NAME|O|ORDER|P|PIECE|QL|QLENGTH|QS|QSUBSCRIPT|Q|QUERY|R|RANDOM|RE|REVERSE|S|SELECT|ST|STACK|T|TEXT|TR|TRANSLATE|NaN))\b/i, +null],["kwd",/^(?:[^\$]B|BREAK|C|CLOSE|D|DO|E|ELSE|F|FOR|G|GOTO|H|HALT|H|HANG|I|IF|J|JOB|K|KILL|L|LOCK|M|MERGE|N|NEW|O|OPEN|Q|QUIT|R|READ|S|SET|TC|TCOMMIT|TRE|TRESTART|TRO|TROLLBACK|TS|TSTART|U|USE|V|VIEW|W|WRITE|X|XECUTE)\b/i,null],["lit",/^[+-]?(?:(?:\.\d+|\d+(?:\.\d*)?)(?:E[+\-]?\d+)?)/i],["pln",/^[a-z][a-zA-Z0-9]*/i],["pun",/^[^\w\t\n\r\xA0\"\$;%\^]|_/]]),["mumps"]); diff --git a/app/src/main/assets/highlight/js/lang-n.js b/app/src/main/assets/highlight/js/lang-n.js new file mode 100644 index 00000000..9b3910c9 --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-n.js @@ -0,0 +1,19 @@ +/* + + Copyright (C) 2011 Zimin A.V. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["str",/^(?:\'(?:[^\\\'\r\n]|\\.)*\'|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,'"'],["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"],["pln",/^\s+/,null," \r\n\t\u00a0"]],[["str",/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null],["str",/^<#(?:[^#>])*(?:#>|$)/,null],["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null],["com",/^\/\/[^\r\n]*/, +null],["com",/^\/\*[\s\S]*?(?:\*\/|$)/,null],["kwd",/^(?:abstract|and|as|base|catch|class|def|delegate|enum|event|extern|false|finally|fun|implements|interface|internal|is|macro|match|matches|module|mutable|namespace|new|null|out|override|params|partial|private|protected|public|ref|sealed|static|struct|syntax|this|throw|true|try|type|typeof|using|variant|virtual|volatile|when|where|with|assert|assert2|async|break|checked|continue|do|else|ensures|for|foreach|if|late|lock|new|nolate|otherwise|regexp|repeat|requires|return|surroundwith|unchecked|unless|using|while|yield)\b/, +null],["typ",/^(?:array|bool|byte|char|decimal|double|float|int|list|long|object|sbyte|short|string|ulong|uint|ufloat|ulong|ushort|void)\b/,null],["lit",/^@[a-z_$][a-z_$@0-9]*/i,null],["typ",/^@[A-Z]+[a-z][A-Za-z_$@0-9]*/,null],["pln",/^'?[A-Za-z_$][a-z_$@0-9]*/i,null],["lit",/^(?:0x[a-f0-9]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+\-]?\d+)?)[a-z]*/i,null,"0123456789"],["pun",/^.[^\s\w\.$@\'\"\`\/\#]*/,null]]),["n","nemerle"]); diff --git a/app/src/main/assets/highlight/js/lang-nemerle.js b/app/src/main/assets/highlight/js/lang-nemerle.js new file mode 100644 index 00000000..9b3910c9 --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-nemerle.js @@ -0,0 +1,19 @@ +/* + + Copyright (C) 2011 Zimin A.V. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["str",/^(?:\'(?:[^\\\'\r\n]|\\.)*\'|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,'"'],["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"],["pln",/^\s+/,null," \r\n\t\u00a0"]],[["str",/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null],["str",/^<#(?:[^#>])*(?:#>|$)/,null],["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null],["com",/^\/\/[^\r\n]*/, +null],["com",/^\/\*[\s\S]*?(?:\*\/|$)/,null],["kwd",/^(?:abstract|and|as|base|catch|class|def|delegate|enum|event|extern|false|finally|fun|implements|interface|internal|is|macro|match|matches|module|mutable|namespace|new|null|out|override|params|partial|private|protected|public|ref|sealed|static|struct|syntax|this|throw|true|try|type|typeof|using|variant|virtual|volatile|when|where|with|assert|assert2|async|break|checked|continue|do|else|ensures|for|foreach|if|late|lock|new|nolate|otherwise|regexp|repeat|requires|return|surroundwith|unchecked|unless|using|while|yield)\b/, +null],["typ",/^(?:array|bool|byte|char|decimal|double|float|int|list|long|object|sbyte|short|string|ulong|uint|ufloat|ulong|ushort|void)\b/,null],["lit",/^@[a-z_$][a-z_$@0-9]*/i,null],["typ",/^@[A-Z]+[a-z][A-Za-z_$@0-9]*/,null],["pln",/^'?[A-Za-z_$][a-z_$@0-9]*/i,null],["lit",/^(?:0x[a-f0-9]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+\-]?\d+)?)[a-z]*/i,null,"0123456789"],["pun",/^.[^\s\w\.$@\'\"\`\/\#]*/,null]]),["n","nemerle"]); diff --git a/app/src/main/assets/highlight/js/lang-pascal.js b/app/src/main/assets/highlight/js/lang-pascal.js new file mode 100644 index 00000000..c76a11cf --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-pascal.js @@ -0,0 +1,18 @@ +/* + + Copyright (C) 2013 Peter Kofler + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["str",/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$))/,null,"'"],["pln",/^\s+/,null," \r\n\t\u00a0"]],[["com",/^\(\*[\s\S]*?(?:\*\)|$)|^\{[\s\S]*?(?:\}|$)/,null],["kwd",/^(?:ABSOLUTE|AND|ARRAY|ASM|ASSEMBLER|BEGIN|CASE|CONST|CONSTRUCTOR|DESTRUCTOR|DIV|DO|DOWNTO|ELSE|END|EXTERNAL|FOR|FORWARD|FUNCTION|GOTO|IF|IMPLEMENTATION|IN|INLINE|INTERFACE|INTERRUPT|LABEL|MOD|NOT|OBJECT|OF|OR|PACKED|PROCEDURE|PROGRAM|RECORD|REPEAT|SET|SHL|SHR|THEN|TO|TYPE|UNIT|UNTIL|USES|VAR|VIRTUAL|WHILE|WITH|XOR)\b/i, +null],["lit",/^(?:true|false|self|nil)/i,null],["pln",/^[a-z][a-z0-9]*/i,null],["lit",/^(?:\$[a-f0-9]+|(?:\d+(?:\.\d*)?|\.\d+)(?:e[+\-]?\d+)?)/i,null,"0123456789"],["pun",/^.[^\s\w\.$@\'\/]*/,null]]),["pascal"]); diff --git a/app/src/main/assets/highlight/js/lang-proto.js b/app/src/main/assets/highlight/js/lang-proto.js new file mode 100644 index 00000000..3215ff6d --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-proto.js @@ -0,0 +1,17 @@ +/* + + Copyright (C) 2006 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.sourceDecorator({keywords:"bytes,default,double,enum,extend,extensions,false,group,import,max,message,option,optional,package,repeated,required,returns,rpc,service,syntax,to,true",types:/^(bool|(double|s?fixed|[su]?int)(32|64)|float|string)\b/,cStyleComments:!0}),["proto"]); diff --git a/app/src/main/assets/highlight/js/lang-r.js b/app/src/main/assets/highlight/js/lang-r.js new file mode 100644 index 00000000..6ce16e8a --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-r.js @@ -0,0 +1,18 @@ +/* + + Copyright (C) 2012 Jeffrey B. Arnold + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["str",/^\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)/,null,'"'],["str",/^\'(?:[^\'\\]|\\[\s\S])*(?:\'|$)/,null,"'"]],[["com",/^#.*/],["kwd",/^(?:if|else|for|while|repeat|in|next|break|return|switch|function)(?![A-Za-z0-9_.])/],["lit",/^0[xX][a-fA-F0-9]+([pP][0-9]+)?[Li]?/],["lit",/^[+-]?([0-9]+(\.[0-9]+)?|\.[0-9]+)([eE][+-]?[0-9]+)?[Li]?/],["lit",/^(?:NULL|NA(?:_(?:integer|real|complex|character)_)?|Inf|TRUE|FALSE|NaN|\.\.(?:\.|[0-9]+))(?![A-Za-z0-9_.])/], +["pun",/^(?:<>?|-|==|<=|>=|<|>|&&?|!=|\|\|?|\*|\+|\^|\/|!|%.*?%|=|~|\$|@|:{1,3}|[\[\](){};,?])/],["pln",/^(?:[A-Za-z]+[A-Za-z0-9_.]*|\.[a-zA-Z_][0-9a-zA-Z\._]*)(?![A-Za-z0-9_.])/],["str",/^`.+`/]]),["r","s","R","S","Splus"]); diff --git a/app/src/main/assets/highlight/js/lang-rd.js b/app/src/main/assets/highlight/js/lang-rd.js new file mode 100644 index 00000000..113141c9 --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-rd.js @@ -0,0 +1,17 @@ +/* + + Copyright (C) 2012 Jeffrey Arnold + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["com",/^%[^\r\n]*/,null,"%"]],[["lit",/^\\(?:cr|l?dots|R|tab)\b/],["kwd",/^\\[a-zA-Z@]+/],["kwd",/^#(?:ifn?def|endif)/],["pln",/^\\[{}]/],["pun",/^[{}()\[\]]+/]]),["Rd","rd"]); diff --git a/app/src/main/assets/highlight/js/lang-rkt.js b/app/src/main/assets/highlight/js/lang-rkt.js new file mode 100644 index 00000000..2f18c967 --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-rkt.js @@ -0,0 +1,18 @@ +/* + + Copyright (C) 2008 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["opn",/^\(+/,null,"("],["clo",/^\)+/,null,")"],["com",/^;[^\r\n]*/,null,";"],["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["str",/^\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)/,null,'"']],[["kwd",/^(?:block|c[ad]+r|catch|con[ds]|def(?:ine|un)|do|eq|eql|equal|equalp|eval-when|flet|format|go|if|labels|lambda|let|load-time-value|locally|macrolet|multiple-value-call|nil|progn|progv|quote|require|return-from|setq|symbol-macrolet|t|tagbody|the|throw|unwind)\b/, +null],["lit",/^[+\-]?(?:[0#]x[0-9a-f]+|\d+\/\d+|(?:\.\d+|\d+(?:\.\d*)?)(?:[ed][+\-]?\d+)?)/i],["lit",/^\'(?:-*(?:\w|\\[\x21-\x7e])(?:[\w-]*|\\[\x21-\x7e])[=!?]?)?/],["pln",/^-*(?:[a-z_]|\\[\x21-\x7e])(?:[\w-]*|\\[\x21-\x7e])[=!?]?/i],["pun",/^[^\w\t\n\r \xA0()\"\\\';]+/]]),"cl el lisp lsp scm ss rkt".split(" ")); diff --git a/app/src/main/assets/highlight/js/lang-rust.js b/app/src/main/assets/highlight/js/lang-rust.js new file mode 100644 index 00000000..43856778 --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-rust.js @@ -0,0 +1,20 @@ +/* + + Copyright (C) 2015 Chris Morgan + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([],[["pln",/^[\t\n\r \xA0]+/],["com",/^\/\/.*/],["com",/^\/\*[\s\S]*?(?:\*\/|$)/],["str",/^b"(?:[^\\]|\\(?:.|x[\da-fA-F]{2}))*?"/],["str",/^"(?:[^\\]|\\(?:.|x[\da-fA-F]{2}|u\{\[\da-fA-F]{1,6}\}))*?"/],["str",/^b?r(#*)\"[\s\S]*?\"\1/],["str",/^b'([^\\]|\\(.|x[\da-fA-F]{2}))'/],["str",/^'([^\\]|\\(.|x[\da-fA-F]{2}|u\{[\da-fA-F]{1,6}\}))'/],["tag",/^'\w+?\b/],["kwd",/^(?:match|if|else|as|break|box|continue|extern|fn|for|in|if|impl|let|loop|pub|return|super|unsafe|where|while|use|mod|trait|struct|enum|type|move|mut|ref|static|const|crate)\b/], +["kwd",/^(?:alignof|become|do|offsetof|priv|pure|sizeof|typeof|unsized|yield|abstract|virtual|final|override|macro)\b/],["typ",/^(?:[iu](8|16|32|64|size)|char|bool|f32|f64|str|Self)\b/],["typ",/^(?:Copy|Send|Sized|Sync|Drop|Fn|FnMut|FnOnce|Box|ToOwned|Clone|PartialEq|PartialOrd|Eq|Ord|AsRef|AsMut|Into|From|Default|Iterator|Extend|IntoIterator|DoubleEndedIterator|ExactSizeIterator|Option|Some|None|Result|Ok|Err|SliceConcatExt|String|ToString|Vec)\b/],["lit",/^(self|true|false|null)\b/], +["lit",/^\d[0-9_]*(?:[iu](?:size|8|16|32|64))?/],["lit",/^0x[a-fA-F0-9_]+(?:[iu](?:size|8|16|32|64))?/],["lit",/^0o[0-7_]+(?:[iu](?:size|8|16|32|64))?/],["lit",/^0b[01_]+(?:[iu](?:size|8|16|32|64))?/],["lit",/^\d[0-9_]*\.(?![^\s\d.])/],["lit",/^\d[0-9_]*(?:\.\d[0-9_]*)(?:[eE][+-]?[0-9_]+)?(?:f32|f64)?/],["lit",/^\d[0-9_]*(?:\.\d[0-9_]*)?(?:[eE][+-]?[0-9_]+)(?:f32|f64)?/],["lit",/^\d[0-9_]*(?:\.\d[0-9_]*)?(?:[eE][+-]?[0-9_]+)?(?:f32|f64)/], +["atn",/^[a-z_]\w*!/i],["pln",/^[a-z_]\w*/i],["atv",/^#!?\[[\s\S]*?\]/],["pun",/^[+\-/*=^&|!<>%[\](){}?:.,;]/],["pln",/./]]),["rust"]); diff --git a/app/src/main/assets/highlight/js/lang-s.js b/app/src/main/assets/highlight/js/lang-s.js new file mode 100644 index 00000000..6ce16e8a --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-s.js @@ -0,0 +1,18 @@ +/* + + Copyright (C) 2012 Jeffrey B. Arnold + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["str",/^\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)/,null,'"'],["str",/^\'(?:[^\'\\]|\\[\s\S])*(?:\'|$)/,null,"'"]],[["com",/^#.*/],["kwd",/^(?:if|else|for|while|repeat|in|next|break|return|switch|function)(?![A-Za-z0-9_.])/],["lit",/^0[xX][a-fA-F0-9]+([pP][0-9]+)?[Li]?/],["lit",/^[+-]?([0-9]+(\.[0-9]+)?|\.[0-9]+)([eE][+-]?[0-9]+)?[Li]?/],["lit",/^(?:NULL|NA(?:_(?:integer|real|complex|character)_)?|Inf|TRUE|FALSE|NaN|\.\.(?:\.|[0-9]+))(?![A-Za-z0-9_.])/], +["pun",/^(?:<>?|-|==|<=|>=|<|>|&&?|!=|\|\|?|\*|\+|\^|\/|!|%.*?%|=|~|\$|@|:{1,3}|[\[\](){};,?])/],["pln",/^(?:[A-Za-z]+[A-Za-z0-9_.]*|\.[a-zA-Z_][0-9a-zA-Z\._]*)(?![A-Za-z0-9_.])/],["str",/^`.+`/]]),["r","s","R","S","Splus"]); diff --git a/app/src/main/assets/highlight/js/lang-scala.js b/app/src/main/assets/highlight/js/lang-scala.js new file mode 100644 index 00000000..3347dd60 --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-scala.js @@ -0,0 +1,18 @@ +/* + + Copyright (C) 2010 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["str",/^(?:"(?:(?:""(?:""?(?!")|[^\\"]|\\.)*"{0,3})|(?:[^"\r\n\\]|\\.)*"?))/,null,'"'],["lit",/^`(?:[^\r\n\\`]|\\.)*`?/,null,"`"],["pun",/^[!#%&()*+,\-:;<=>?@\[\\\]^{|}~]+/,null,"!#%&()*+,-:;<=>?@[\\]^{|}~"]],[["str",/^'(?:[^\r\n\\']|\\(?:'|[^\r\n']+))'/],["lit",/^'[a-zA-Z_$][\w$]*(?!['$\w])/],["kwd",/^(?:abstract|case|catch|class|def|do|else|extends|final|finally|for|forSome|if|implicit|import|lazy|match|new|object|override|package|private|protected|requires|return|sealed|super|throw|trait|try|type|val|var|while|with|yield)\b/], +["lit",/^(?:true|false|null|this)\b/],["lit",/^(?:(?:0(?:[0-7]+|X[0-9A-F]+))L?|(?:(?:0|[1-9][0-9]*)(?:(?:\.[0-9]+)?(?:E[+\-]?[0-9]+)?F?|L?))|\\.[0-9]+(?:E[+\-]?[0-9]+)?F?)/i],["typ",/^[$_]*[A-Z][_$A-Z0-9]*[a-z][\w$]*/],["pln",/^[$a-zA-Z_][\w$]*/],["com",/^\/(?:\/.*|\*(?:\/|\**[^*/])*(?:\*+\/?)?)/],["pun",/^(?:\.+|\/)/]]),["scala"]); diff --git a/app/src/main/assets/highlight/js/lang-scm.js b/app/src/main/assets/highlight/js/lang-scm.js new file mode 100644 index 00000000..2f18c967 --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-scm.js @@ -0,0 +1,18 @@ +/* + + Copyright (C) 2008 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["opn",/^\(+/,null,"("],["clo",/^\)+/,null,")"],["com",/^;[^\r\n]*/,null,";"],["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["str",/^\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)/,null,'"']],[["kwd",/^(?:block|c[ad]+r|catch|con[ds]|def(?:ine|un)|do|eq|eql|equal|equalp|eval-when|flet|format|go|if|labels|lambda|let|load-time-value|locally|macrolet|multiple-value-call|nil|progn|progv|quote|require|return-from|setq|symbol-macrolet|t|tagbody|the|throw|unwind)\b/, +null],["lit",/^[+\-]?(?:[0#]x[0-9a-f]+|\d+\/\d+|(?:\.\d+|\d+(?:\.\d*)?)(?:[ed][+\-]?\d+)?)/i],["lit",/^\'(?:-*(?:\w|\\[\x21-\x7e])(?:[\w-]*|\\[\x21-\x7e])[=!?]?)?/],["pln",/^-*(?:[a-z_]|\\[\x21-\x7e])(?:[\w-]*|\\[\x21-\x7e])[=!?]?/i],["pun",/^[^\w\t\n\r \xA0()\"\\\';]+/]]),"cl el lisp lsp scm ss rkt".split(" ")); diff --git a/app/src/main/assets/highlight/js/lang-sql.js b/app/src/main/assets/highlight/js/lang-sql.js new file mode 100644 index 00000000..a7d292cb --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-sql.js @@ -0,0 +1,18 @@ +/* + + Copyright (C) 2008 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["str",/^(?:"(?:[^\"\\]|\\.)*"|'(?:[^\'\\]|\\.)*')/,null,"\"'"]],[["com",/^(?:--[^\r\n]*|\/\*[\s\S]*?(?:\*\/|$))/],["kwd",/^(?:ADD|ALL|ALTER|AND|ANY|APPLY|AS|ASC|AUTHORIZATION|BACKUP|BEGIN|BETWEEN|BREAK|BROWSE|BULK|BY|CASCADE|CASE|CHECK|CHECKPOINT|CLOSE|CLUSTERED|COALESCE|COLLATE|COLUMN|COMMIT|COMPUTE|CONNECT|CONSTRAINT|CONTAINS|CONTAINSTABLE|CONTINUE|CONVERT|CREATE|CROSS|CURRENT|CURRENT_DATE|CURRENT_TIME|CURRENT_TIMESTAMP|CURRENT_USER|CURSOR|DATABASE|DBCC|DEALLOCATE|DECLARE|DEFAULT|DELETE|DENY|DESC|DISK|DISTINCT|DISTRIBUTED|DOUBLE|DROP|DUMMY|DUMP|ELSE|END|ERRLVL|ESCAPE|EXCEPT|EXEC|EXECUTE|EXISTS|EXIT|FETCH|FILE|FILLFACTOR|FOLLOWING|FOR|FOREIGN|FREETEXT|FREETEXTTABLE|FROM|FULL|FUNCTION|GOTO|GRANT|GROUP|HAVING|HOLDLOCK|IDENTITY|IDENTITYCOL|IDENTITY_INSERT|IF|IN|INDEX|INNER|INSERT|INTERSECT|INTO|IS|JOIN|KEY|KILL|LEFT|LIKE|LINENO|LOAD|MATCH|MATCHED|MERGE|NATURAL|NATIONAL|NOCHECK|NONCLUSTERED|NOCYCLE|NOT|NULL|NULLIF|OF|OFF|OFFSETS|ON|OPEN|OPENDATASOURCE|OPENQUERY|OPENROWSET|OPENXML|OPTION|OR|ORDER|OUTER|OVER|PARTITION|PERCENT|PIVOT|PLAN|PRECEDING|PRECISION|PRIMARY|PRINT|PROC|PROCEDURE|PUBLIC|RAISERROR|READ|READTEXT|RECONFIGURE|REFERENCES|REPLICATION|RESTORE|RESTRICT|RETURN|REVOKE|RIGHT|ROLLBACK|ROWCOUNT|ROWGUIDCOL|ROWS?|RULE|SAVE|SCHEMA|SELECT|SESSION_USER|SET|SETUSER|SHUTDOWN|SOME|START|STATISTICS|SYSTEM_USER|TABLE|TEXTSIZE|THEN|TO|TOP|TRAN|TRANSACTION|TRIGGER|TRUNCATE|TSEQUAL|UNBOUNDED|UNION|UNIQUE|UNPIVOT|UPDATE|UPDATETEXT|USE|USER|USING|VALUES|VARYING|VIEW|WAITFOR|WHEN|WHERE|WHILE|WITH|WITHIN|WRITETEXT|XML)(?=[^\w-]|$)/i, +null],["lit",/^[+-]?(?:0x[\da-f]+|(?:(?:\.\d+|\d+(?:\.\d*)?)(?:e[+\-]?\d+)?))/i],["pln",/^[a-z_][\w-]*/i],["pun",/^[^\w\t\n\r \xA0\"\'][^\w\t\n\r \xA0+\-\"\']*/]]),["sql"]); diff --git a/app/src/main/assets/highlight/js/lang-ss.js b/app/src/main/assets/highlight/js/lang-ss.js new file mode 100644 index 00000000..2f18c967 --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-ss.js @@ -0,0 +1,18 @@ +/* + + Copyright (C) 2008 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["opn",/^\(+/,null,"("],["clo",/^\)+/,null,")"],["com",/^;[^\r\n]*/,null,";"],["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["str",/^\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)/,null,'"']],[["kwd",/^(?:block|c[ad]+r|catch|con[ds]|def(?:ine|un)|do|eq|eql|equal|equalp|eval-when|flet|format|go|if|labels|lambda|let|load-time-value|locally|macrolet|multiple-value-call|nil|progn|progv|quote|require|return-from|setq|symbol-macrolet|t|tagbody|the|throw|unwind)\b/, +null],["lit",/^[+\-]?(?:[0#]x[0-9a-f]+|\d+\/\d+|(?:\.\d+|\d+(?:\.\d*)?)(?:[ed][+\-]?\d+)?)/i],["lit",/^\'(?:-*(?:\w|\\[\x21-\x7e])(?:[\w-]*|\\[\x21-\x7e])[=!?]?)?/],["pln",/^-*(?:[a-z_]|\\[\x21-\x7e])(?:[\w-]*|\\[\x21-\x7e])[=!?]?/i],["pun",/^[^\w\t\n\r \xA0()\"\\\';]+/]]),"cl el lisp lsp scm ss rkt".split(" ")); diff --git a/app/src/main/assets/highlight/js/lang-swift.js b/app/src/main/assets/highlight/js/lang-swift.js new file mode 100644 index 00000000..5442fa77 --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-swift.js @@ -0,0 +1,16 @@ +/* + + Copyright (C) 2015 Google Inc. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[ \n\r\t\v\f\0]+/,null," \n\r\t\v\f\x00"],["str",/^"(?:[^"\\]|(?:\\.)|(?:\\\((?:[^"\\)]|\\.)*\)))*"/,null,'"']],[["lit",/^(?:(?:0x[\da-fA-F][\da-fA-F_]*\.[\da-fA-F][\da-fA-F_]*[pP]?)|(?:\d[\d_]*\.\d[\d_]*[eE]?))[+-]?\d[\d_]*/,null],["lit",/^-?(?:(?:0(?:(?:b[01][01_]*)|(?:o[0-7][0-7_]*)|(?:x[\da-fA-F][\da-fA-F_]*)))|(?:\d[\d_]*))/,null],["lit",/^(?:true|false|nil)\b/,null],["kwd",/^\b(?:__COLUMN__|__FILE__|__FUNCTION__|__LINE__|#available|#else|#elseif|#endif|#if|#line|arch|arm|arm64|associativity|as|break|case|catch|class|continue|convenience|default|defer|deinit|didSet|do|dynamic|dynamicType|else|enum|fallthrough|final|for|func|get|import|indirect|infix|init|inout|internal|i386|if|in|iOS|iOSApplicationExtension|is|lazy|left|let|mutating|none|nonmutating|operator|optional|OSX|OSXApplicationExtension|override|postfix|precedence|prefix|private|protocol|Protocol|public|required|rethrows|return|right|safe|self|set|static|struct|subscript|super|switch|throw|try|Type|typealias|unowned|unsafe|var|weak|watchOS|while|willSet|x86_64)\b/, +null],["com",/^\/\/.*?[\n\r]/,null],["com",/^\/\*[\s\S]*?(?:\*\/|$)/,null],["pun",/^<<=|<=|<<|>>=|>=|>>|===|==|\.\.\.|&&=|\.\.<|!==|!=|&=|~=|~|\(|\)|\[|\]|{|}|@|#|;|\.|,|:|\|\|=|\?\?|\|\||&&|&\*|&\+|&-|&=|\+=|-=|\/=|\*=|\^=|%=|\|=|->|`|==|\+\+|--|\/|\+|!|\*|%|<|>|&|\||\^|\?|=|-|_/,null],["typ",/^\b(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null]]),["swift"]); diff --git a/app/src/main/assets/highlight/js/lang-tcl.js b/app/src/main/assets/highlight/js/lang-tcl.js new file mode 100644 index 00000000..1d75c3f0 --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-tcl.js @@ -0,0 +1,18 @@ +/* + + Copyright (C) 2012 Pyrios + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["opn",/^\{+/,null,"{"],["clo",/^\}+/,null,"}"],["com",/^#[^\r\n]*/,null,"#"],["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["str",/^\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)/,null,'"']],[["kwd",/^(?:after|append|apply|array|break|case|catch|continue|error|eval|exec|exit|expr|for|foreach|if|incr|info|proc|return|set|switch|trace|uplevel|upvar|while)\b/,null],["lit",/^[+\-]?(?:[0#]x[0-9a-f]+|\d+\/\d+|(?:\.\d+|\d+(?:\.\d*)?)(?:[ed][+\-]?\d+)?)/i], +["lit",/^\'(?:-*(?:\w|\\[\x21-\x7e])(?:[\w-]*|\\[\x21-\x7e])[=!?]?)?/],["pln",/^-*(?:[a-z_]|\\[\x21-\x7e])(?:[\w-]*|\\[\x21-\x7e])[=!?]?/i],["pun",/^[^\w\t\n\r \xA0()\"\\\';]+/]]),["tcl"]); diff --git a/app/src/main/assets/highlight/js/lang-tex.js b/app/src/main/assets/highlight/js/lang-tex.js new file mode 100644 index 00000000..efc758cd --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-tex.js @@ -0,0 +1,17 @@ +/* + + Copyright (C) 2011 Martin S. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["com",/^%[^\r\n]*/,null,"%"]],[["kwd",/^\\[a-zA-Z@]+/],["kwd",/^\\./],["typ",/^[$&]/],["lit",/[+-]?(?:\.\d+|\d+(?:\.\d*)?)(cm|em|ex|in|pc|pt|bp|mm)/i],["pun",/^[{}()\[\]=]+/]]),["latex","tex"]); diff --git a/app/src/main/assets/highlight/js/lang-vb.js b/app/src/main/assets/highlight/js/lang-vb.js new file mode 100644 index 00000000..e34086fe --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-vb.js @@ -0,0 +1,19 @@ +/* + + Copyright (C) 2009 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xA0\u2028\u2029]+/,null,"\t\n\r \u00a0\u2028\u2029"],["str",/^(?:[\"\u201C\u201D](?:[^\"\u201C\u201D]|[\"\u201C\u201D]{2})(?:[\"\u201C\u201D]c|$)|[\"\u201C\u201D](?:[^\"\u201C\u201D]|[\"\u201C\u201D]{2})*(?:[\"\u201C\u201D]|$))/i,null,'"\u201c\u201d'],["com",/^[\'\u2018\u2019](?:_(?:\r\n?|[^\r]?)|[^\r\n_\u2028\u2029])*/,null,"'\u2018\u2019"]],[["kwd",/^(?:AddHandler|AddressOf|Alias|And|AndAlso|Ansi|As|Assembly|Auto|Boolean|ByRef|Byte|ByVal|Call|Case|Catch|CBool|CByte|CChar|CDate|CDbl|CDec|Char|CInt|Class|CLng|CObj|Const|CShort|CSng|CStr|CType|Date|Decimal|Declare|Default|Delegate|Dim|DirectCast|Do|Double|Each|Else|ElseIf|End|EndIf|Enum|Erase|Error|Event|Exit|Finally|For|Friend|Function|Get|GetType|GoSub|GoTo|Handles|If|Implements|Imports|In|Inherits|Integer|Interface|Is|Let|Lib|Like|Long|Loop|Me|Mod|Module|MustInherit|MustOverride|MyBase|MyClass|Namespace|New|Next|Not|NotInheritable|NotOverridable|Object|On|Option|Optional|Or|OrElse|Overloads|Overridable|Overrides|ParamArray|Preserve|Private|Property|Protected|Public|RaiseEvent|ReadOnly|ReDim|RemoveHandler|Resume|Return|Select|Set|Shadows|Shared|Short|Single|Static|Step|Stop|String|Structure|Sub|SyncLock|Then|Throw|To|Try|TypeOf|Unicode|Until|Variant|Wend|When|While|With|WithEvents|WriteOnly|Xor|EndIf|GoSub|Let|Variant|Wend)\b/i, +null],["com",/^REM\b[^\r\n\u2028\u2029]*/i],["lit",/^(?:True\b|False\b|Nothing\b|\d+(?:E[+\-]?\d+[FRD]?|[FRDSIL])?|(?:&H[0-9A-F]+|&O[0-7]+)[SIL]?|\d*\.\d+(?:E[+\-]?\d+)?[FRD]?|#\s+(?:\d+[\-\/]\d+[\-\/]\d+(?:\s+\d+:\d+(?::\d+)?(\s*(?:AM|PM))?)?|\d+:\d+(?::\d+)?(\s*(?:AM|PM))?)\s+#)/i],["pln",/^(?:(?:[a-z]|_\w)\w*(?:\[[%&@!#]+\])?|\[(?:[a-z]|_\w)\w*\])/i],["pun",/^[^\w\t\n\r \"\'\[\]\xA0\u2018\u2019\u201C\u201D\u2028\u2029]+/],["pun",/^(?:\[|\])/]]),["vb", +"vbs"]); diff --git a/app/src/main/assets/highlight/js/lang-vbs.js b/app/src/main/assets/highlight/js/lang-vbs.js new file mode 100644 index 00000000..e34086fe --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-vbs.js @@ -0,0 +1,19 @@ +/* + + Copyright (C) 2009 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xA0\u2028\u2029]+/,null,"\t\n\r \u00a0\u2028\u2029"],["str",/^(?:[\"\u201C\u201D](?:[^\"\u201C\u201D]|[\"\u201C\u201D]{2})(?:[\"\u201C\u201D]c|$)|[\"\u201C\u201D](?:[^\"\u201C\u201D]|[\"\u201C\u201D]{2})*(?:[\"\u201C\u201D]|$))/i,null,'"\u201c\u201d'],["com",/^[\'\u2018\u2019](?:_(?:\r\n?|[^\r]?)|[^\r\n_\u2028\u2029])*/,null,"'\u2018\u2019"]],[["kwd",/^(?:AddHandler|AddressOf|Alias|And|AndAlso|Ansi|As|Assembly|Auto|Boolean|ByRef|Byte|ByVal|Call|Case|Catch|CBool|CByte|CChar|CDate|CDbl|CDec|Char|CInt|Class|CLng|CObj|Const|CShort|CSng|CStr|CType|Date|Decimal|Declare|Default|Delegate|Dim|DirectCast|Do|Double|Each|Else|ElseIf|End|EndIf|Enum|Erase|Error|Event|Exit|Finally|For|Friend|Function|Get|GetType|GoSub|GoTo|Handles|If|Implements|Imports|In|Inherits|Integer|Interface|Is|Let|Lib|Like|Long|Loop|Me|Mod|Module|MustInherit|MustOverride|MyBase|MyClass|Namespace|New|Next|Not|NotInheritable|NotOverridable|Object|On|Option|Optional|Or|OrElse|Overloads|Overridable|Overrides|ParamArray|Preserve|Private|Property|Protected|Public|RaiseEvent|ReadOnly|ReDim|RemoveHandler|Resume|Return|Select|Set|Shadows|Shared|Short|Single|Static|Step|Stop|String|Structure|Sub|SyncLock|Then|Throw|To|Try|TypeOf|Unicode|Until|Variant|Wend|When|While|With|WithEvents|WriteOnly|Xor|EndIf|GoSub|Let|Variant|Wend)\b/i, +null],["com",/^REM\b[^\r\n\u2028\u2029]*/i],["lit",/^(?:True\b|False\b|Nothing\b|\d+(?:E[+\-]?\d+[FRD]?|[FRDSIL])?|(?:&H[0-9A-F]+|&O[0-7]+)[SIL]?|\d*\.\d+(?:E[+\-]?\d+)?[FRD]?|#\s+(?:\d+[\-\/]\d+[\-\/]\d+(?:\s+\d+:\d+(?::\d+)?(\s*(?:AM|PM))?)?|\d+:\d+(?::\d+)?(\s*(?:AM|PM))?)\s+#)/i],["pln",/^(?:(?:[a-z]|_\w)\w*(?:\[[%&@!#]+\])?|\[(?:[a-z]|_\w)\w*\])/i],["pun",/^[^\w\t\n\r \"\'\[\]\xA0\u2018\u2019\u201C\u201D\u2028\u2029]+/],["pun",/^(?:\[|\])/]]),["vb", +"vbs"]); diff --git a/app/src/main/assets/highlight/js/lang-vhd.js b/app/src/main/assets/highlight/js/lang-vhd.js new file mode 100644 index 00000000..f67a4a39 --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-vhd.js @@ -0,0 +1,19 @@ +/* + + Copyright (C) 2010 benoit@ryder.fr + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"]],[["str",/^(?:[BOX]?"(?:[^\"]|"")*"|'.')/i],["com",/^--[^\r\n]*/],["kwd",/^(?:abs|access|after|alias|all|and|architecture|array|assert|attribute|begin|block|body|buffer|bus|case|component|configuration|constant|disconnect|downto|else|elsif|end|entity|exit|file|for|function|generate|generic|group|guarded|if|impure|in|inertial|inout|is|label|library|linkage|literal|loop|map|mod|nand|new|next|nor|not|null|of|on|open|or|others|out|package|port|postponed|procedure|process|pure|range|record|register|reject|rem|report|return|rol|ror|select|severity|shared|signal|sla|sll|sra|srl|subtype|then|to|transport|type|unaffected|units|until|use|variable|wait|when|while|with|xnor|xor)(?=[^\w-]|$)/i, +null],["typ",/^(?:bit|bit_vector|character|boolean|integer|real|time|string|severity_level|positive|natural|signed|unsigned|line|text|std_u?logic(?:_vector)?)(?=[^\w-]|$)/i,null],["typ",/^\'(?:ACTIVE|ASCENDING|BASE|DELAYED|DRIVING|DRIVING_VALUE|EVENT|HIGH|IMAGE|INSTANCE_NAME|LAST_ACTIVE|LAST_EVENT|LAST_VALUE|LEFT|LEFTOF|LENGTH|LOW|PATH_NAME|POS|PRED|QUIET|RANGE|REVERSE_RANGE|RIGHT|RIGHTOF|SIMPLE_NAME|STABLE|SUCC|TRANSACTION|VAL|VALUE)(?=[^\w-]|$)/i,null],["lit",/^\d+(?:_\d+)*(?:#[\w\\.]+#(?:[+\-]?\d+(?:_\d+)*)?|(?:\.\d+(?:_\d+)*)?(?:E[+\-]?\d+(?:_\d+)*)?)/i], +["pln",/^(?:[a-z]\w*|\\[^\\]*\\)/i],["pun",/^[^\w\t\n\r \xA0\"\'][^\w\t\n\r \xA0\-\"\']*/]]),["vhdl","vhd"]); diff --git a/app/src/main/assets/highlight/js/lang-vhdl.js b/app/src/main/assets/highlight/js/lang-vhdl.js new file mode 100644 index 00000000..f67a4a39 --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-vhdl.js @@ -0,0 +1,19 @@ +/* + + Copyright (C) 2010 benoit@ryder.fr + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"]],[["str",/^(?:[BOX]?"(?:[^\"]|"")*"|'.')/i],["com",/^--[^\r\n]*/],["kwd",/^(?:abs|access|after|alias|all|and|architecture|array|assert|attribute|begin|block|body|buffer|bus|case|component|configuration|constant|disconnect|downto|else|elsif|end|entity|exit|file|for|function|generate|generic|group|guarded|if|impure|in|inertial|inout|is|label|library|linkage|literal|loop|map|mod|nand|new|next|nor|not|null|of|on|open|or|others|out|package|port|postponed|procedure|process|pure|range|record|register|reject|rem|report|return|rol|ror|select|severity|shared|signal|sla|sll|sra|srl|subtype|then|to|transport|type|unaffected|units|until|use|variable|wait|when|while|with|xnor|xor)(?=[^\w-]|$)/i, +null],["typ",/^(?:bit|bit_vector|character|boolean|integer|real|time|string|severity_level|positive|natural|signed|unsigned|line|text|std_u?logic(?:_vector)?)(?=[^\w-]|$)/i,null],["typ",/^\'(?:ACTIVE|ASCENDING|BASE|DELAYED|DRIVING|DRIVING_VALUE|EVENT|HIGH|IMAGE|INSTANCE_NAME|LAST_ACTIVE|LAST_EVENT|LAST_VALUE|LEFT|LEFTOF|LENGTH|LOW|PATH_NAME|POS|PRED|QUIET|RANGE|REVERSE_RANGE|RIGHT|RIGHTOF|SIMPLE_NAME|STABLE|SUCC|TRANSACTION|VAL|VALUE)(?=[^\w-]|$)/i,null],["lit",/^\d+(?:_\d+)*(?:#[\w\\.]+#(?:[+\-]?\d+(?:_\d+)*)?|(?:\.\d+(?:_\d+)*)?(?:E[+\-]?\d+(?:_\d+)*)?)/i], +["pln",/^(?:[a-z]\w*|\\[^\\]*\\)/i],["pun",/^[^\w\t\n\r \xA0\"\'][^\w\t\n\r \xA0\-\"\']*/]]),["vhdl","vhd"]); diff --git a/app/src/main/assets/highlight/js/lang-wiki.js b/app/src/main/assets/highlight/js/lang-wiki.js new file mode 100644 index 00000000..d03fccd2 --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-wiki.js @@ -0,0 +1,18 @@ +/* + + Copyright (C) 2009 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t \xA0a-gi-z0-9]+/,null,"\t \u00a0abcdefgijklmnopqrstuvwxyz0123456789"],["pun",/^[=*~\^\[\]]+/,null,"=*~^[]"]],[["lang-wiki.meta",/(?:^^|\r\n?|\n)(#[a-z]+)\b/],["lit",/^(?:[A-Z][a-z][a-z0-9]+[A-Z][a-z][a-zA-Z0-9]+)\b/],["lang-",/^\{\{\{([\s\S]+?)\}\}\}/],["lang-",/^`([^\r\n`]+)`/],["str",/^https?:\/\/[^\/?#\s]*(?:\/[^?#\s]*)?(?:\?[^#\s]*)?(?:#\S*)?/i],["pln",/^(?:\r\n|[\s\S])[^#=*~^A-Zh\{`\[\r\n]*/]]),["wiki"]); +PR.registerLangHandler(PR.createSimpleLexer([["kwd",/^#[a-z]+/i,null,"#"]],[]),["wiki.meta"]); diff --git a/app/src/main/assets/highlight/js/lang-xq.js b/app/src/main/assets/highlight/js/lang-xq.js new file mode 100644 index 00000000..a6d85372 --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-xq.js @@ -0,0 +1,19 @@ +/* + + Copyright (C) 2011 Patrick Wied + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["var pln",/^\$[A-Za-z0-9_\-]+/,null,"$"]],[["pln",/^[\s=][<>][\s=]/],["lit",/^\@[\w-]+/],["tag",/^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],["com",/^\(:[\s\S]*?:\)/],["pln",/^[\/\{\};,\[\]\(\)]$/],["str",/^(?:\"(?:[^\"\\\{]|\\[\s\S])*(?:\"|$)|\'(?:[^\'\\\{]|\\[\s\S])*(?:\'|$))/,null,"\"'"],["kwd",/^(?:xquery|where|version|variable|union|typeswitch|treat|to|then|text|stable|sortby|some|self|schema|satisfies|returns|return|ref|processing-instruction|preceding-sibling|preceding|precedes|parent|only|of|node|namespace|module|let|item|intersect|instance|in|import|if|function|for|follows|following-sibling|following|external|except|every|else|element|descending|descendant-or-self|descendant|define|default|declare|comment|child|cast|case|before|attribute|assert|ascending|as|ancestor-or-self|ancestor|after|eq|order|by|or|and|schema-element|document-node|node|at)\b/], +["typ",/^(?:xs:yearMonthDuration|xs:unsignedLong|xs:time|xs:string|xs:short|xs:QName|xs:Name|xs:long|xs:integer|xs:int|xs:gYearMonth|xs:gYear|xs:gMonthDay|xs:gDay|xs:float|xs:duration|xs:double|xs:decimal|xs:dayTimeDuration|xs:dateTime|xs:date|xs:byte|xs:boolean|xs:anyURI|xf:yearMonthDuration)\b/,null],["fun pln",/^(?:xp:dereference|xinc:node-expand|xinc:link-references|xinc:link-expand|xhtml:restructure|xhtml:clean|xhtml:add-lists|xdmp:zip-manifest|xdmp:zip-get|xdmp:zip-create|xdmp:xquery-version|xdmp:word-convert|xdmp:with-namespaces|xdmp:version|xdmp:value|xdmp:user-roles|xdmp:user-last-login|xdmp:user|xdmp:url-encode|xdmp:url-decode|xdmp:uri-is-file|xdmp:uri-format|xdmp:uri-content-type|xdmp:unquote|xdmp:unpath|xdmp:triggers-database|xdmp:trace|xdmp:to-json|xdmp:tidy|xdmp:subbinary|xdmp:strftime|xdmp:spawn-in|xdmp:spawn|xdmp:sleep|xdmp:shutdown|xdmp:set-session-field|xdmp:set-response-encoding|xdmp:set-response-content-type|xdmp:set-response-code|xdmp:set-request-time-limit|xdmp:set|xdmp:servers|xdmp:server-status|xdmp:server-name|xdmp:server|xdmp:security-database|xdmp:security-assert|xdmp:schema-database|xdmp:save|xdmp:role-roles|xdmp:role|xdmp:rethrow|xdmp:restart|xdmp:request-timestamp|xdmp:request-status|xdmp:request-cancel|xdmp:request|xdmp:redirect-response|xdmp:random|xdmp:quote|xdmp:query-trace|xdmp:query-meters|xdmp:product-edition|xdmp:privilege-roles|xdmp:privilege|xdmp:pretty-print|xdmp:powerpoint-convert|xdmp:platform|xdmp:permission|xdmp:pdf-convert|xdmp:path|xdmp:octal-to-integer|xdmp:node-uri|xdmp:node-replace|xdmp:node-kind|xdmp:node-insert-child|xdmp:node-insert-before|xdmp:node-insert-after|xdmp:node-delete|xdmp:node-database|xdmp:mul64|xdmp:modules-root|xdmp:modules-database|xdmp:merging|xdmp:merge-cancel|xdmp:merge|xdmp:md5|xdmp:logout|xdmp:login|xdmp:log-level|xdmp:log|xdmp:lock-release|xdmp:lock-acquire|xdmp:load|xdmp:invoke-in|xdmp:invoke|xdmp:integer-to-octal|xdmp:integer-to-hex|xdmp:http-put|xdmp:http-post|xdmp:http-options|xdmp:http-head|xdmp:http-get|xdmp:http-delete|xdmp:hosts|xdmp:host-status|xdmp:host-name|xdmp:host|xdmp:hex-to-integer|xdmp:hash64|xdmp:hash32|xdmp:has-privilege|xdmp:groups|xdmp:group-serves|xdmp:group-servers|xdmp:group-name|xdmp:group-hosts|xdmp:group|xdmp:get-session-field-names|xdmp:get-session-field|xdmp:get-response-encoding|xdmp:get-response-code|xdmp:get-request-username|xdmp:get-request-user|xdmp:get-request-url|xdmp:get-request-protocol|xdmp:get-request-path|xdmp:get-request-method|xdmp:get-request-header-names|xdmp:get-request-header|xdmp:get-request-field-names|xdmp:get-request-field-filename|xdmp:get-request-field-content-type|xdmp:get-request-field|xdmp:get-request-client-certificate|xdmp:get-request-client-address|xdmp:get-request-body|xdmp:get-current-user|xdmp:get-current-roles|xdmp:get|xdmp:function-name|xdmp:function-module|xdmp:function|xdmp:from-json|xdmp:forests|xdmp:forest-status|xdmp:forest-restore|xdmp:forest-restart|xdmp:forest-name|xdmp:forest-delete|xdmp:forest-databases|xdmp:forest-counts|xdmp:forest-clear|xdmp:forest-backup|xdmp:forest|xdmp:filesystem-file|xdmp:filesystem-directory|xdmp:exists|xdmp:excel-convert|xdmp:eval-in|xdmp:eval|xdmp:estimate|xdmp:email|xdmp:element-content-type|xdmp:elapsed-time|xdmp:document-set-quality|xdmp:document-set-property|xdmp:document-set-properties|xdmp:document-set-permissions|xdmp:document-set-collections|xdmp:document-remove-properties|xdmp:document-remove-permissions|xdmp:document-remove-collections|xdmp:document-properties|xdmp:document-locks|xdmp:document-load|xdmp:document-insert|xdmp:document-get-quality|xdmp:document-get-properties|xdmp:document-get-permissions|xdmp:document-get-collections|xdmp:document-get|xdmp:document-forest|xdmp:document-delete|xdmp:document-add-properties|xdmp:document-add-permissions|xdmp:document-add-collections|xdmp:directory-properties|xdmp:directory-locks|xdmp:directory-delete|xdmp:directory-create|xdmp:directory|xdmp:diacritic-less|xdmp:describe|xdmp:default-permissions|xdmp:default-collections|xdmp:databases|xdmp:database-restore-validate|xdmp:database-restore-status|xdmp:database-restore-cancel|xdmp:database-restore|xdmp:database-name|xdmp:database-forests|xdmp:database-backup-validate|xdmp:database-backup-status|xdmp:database-backup-purge|xdmp:database-backup-cancel|xdmp:database-backup|xdmp:database|xdmp:collection-properties|xdmp:collection-locks|xdmp:collection-delete|xdmp:collation-canonical-uri|xdmp:castable-as|xdmp:can-grant-roles|xdmp:base64-encode|xdmp:base64-decode|xdmp:architecture|xdmp:apply|xdmp:amp-roles|xdmp:amp|xdmp:add64|xdmp:add-response-header|xdmp:access|trgr:trigger-set-recursive|trgr:trigger-set-permissions|trgr:trigger-set-name|trgr:trigger-set-module|trgr:trigger-set-event|trgr:trigger-set-description|trgr:trigger-remove-permissions|trgr:trigger-module|trgr:trigger-get-permissions|trgr:trigger-enable|trgr:trigger-disable|trgr:trigger-database-online-event|trgr:trigger-data-event|trgr:trigger-add-permissions|trgr:remove-trigger|trgr:property-content|trgr:pre-commit|trgr:post-commit|trgr:get-trigger-by-id|trgr:get-trigger|trgr:document-scope|trgr:document-content|trgr:directory-scope|trgr:create-trigger|trgr:collection-scope|trgr:any-property-content|thsr:set-entry|thsr:remove-term|thsr:remove-synonym|thsr:remove-entry|thsr:query-lookup|thsr:lookup|thsr:load|thsr:insert|thsr:expand|thsr:add-synonym|spell:suggest-detailed|spell:suggest|spell:remove-word|spell:make-dictionary|spell:load|spell:levenshtein-distance|spell:is-correct|spell:insert|spell:double-metaphone|spell:add-word|sec:users-collection|sec:user-set-roles|sec:user-set-password|sec:user-set-name|sec:user-set-description|sec:user-set-default-permissions|sec:user-set-default-collections|sec:user-remove-roles|sec:user-privileges|sec:user-get-roles|sec:user-get-description|sec:user-get-default-permissions|sec:user-get-default-collections|sec:user-doc-permissions|sec:user-doc-collections|sec:user-add-roles|sec:unprotect-collection|sec:uid-for-name|sec:set-realm|sec:security-version|sec:security-namespace|sec:security-installed|sec:security-collection|sec:roles-collection|sec:role-set-roles|sec:role-set-name|sec:role-set-description|sec:role-set-default-permissions|sec:role-set-default-collections|sec:role-remove-roles|sec:role-privileges|sec:role-get-roles|sec:role-get-description|sec:role-get-default-permissions|sec:role-get-default-collections|sec:role-doc-permissions|sec:role-doc-collections|sec:role-add-roles|sec:remove-user|sec:remove-role-from-users|sec:remove-role-from-role|sec:remove-role-from-privileges|sec:remove-role-from-amps|sec:remove-role|sec:remove-privilege|sec:remove-amp|sec:protect-collection|sec:privileges-collection|sec:privilege-set-roles|sec:privilege-set-name|sec:privilege-remove-roles|sec:privilege-get-roles|sec:privilege-add-roles|sec:priv-doc-permissions|sec:priv-doc-collections|sec:get-user-names|sec:get-unique-elem-id|sec:get-role-names|sec:get-role-ids|sec:get-privilege|sec:get-distinct-permissions|sec:get-collection|sec:get-amp|sec:create-user-with-role|sec:create-user|sec:create-role|sec:create-privilege|sec:create-amp|sec:collections-collection|sec:collection-set-permissions|sec:collection-remove-permissions|sec:collection-get-permissions|sec:collection-add-permissions|sec:check-admin|sec:amps-collection|sec:amp-set-roles|sec:amp-remove-roles|sec:amp-get-roles|sec:amp-doc-permissions|sec:amp-doc-collections|sec:amp-add-roles|search:unparse|search:suggest|search:snippet|search:search|search:resolve-nodes|search:resolve|search:remove-constraint|search:parse|search:get-default-options|search:estimate|search:check-options|prof:value|prof:reset|prof:report|prof:invoke|prof:eval|prof:enable|prof:disable|prof:allowed|ppt:clean|pki:template-set-request|pki:template-set-name|pki:template-set-key-type|pki:template-set-key-options|pki:template-set-description|pki:template-in-use|pki:template-get-version|pki:template-get-request|pki:template-get-name|pki:template-get-key-type|pki:template-get-key-options|pki:template-get-id|pki:template-get-description|pki:need-certificate|pki:is-temporary|pki:insert-trusted-certificates|pki:insert-template|pki:insert-signed-certificates|pki:insert-certificate-revocation-list|pki:get-trusted-certificate-ids|pki:get-template-ids|pki:get-template-certificate-authority|pki:get-template-by-name|pki:get-template|pki:get-pending-certificate-requests-xml|pki:get-pending-certificate-requests-pem|pki:get-pending-certificate-request|pki:get-certificates-for-template-xml|pki:get-certificates-for-template|pki:get-certificates|pki:get-certificate-xml|pki:get-certificate-pem|pki:get-certificate|pki:generate-temporary-certificate-if-necessary|pki:generate-temporary-certificate|pki:generate-template-certificate-authority|pki:generate-certificate-request|pki:delete-template|pki:delete-certificate|pki:create-template|pdf:make-toc|pdf:insert-toc-headers|pdf:get-toc|pdf:clean|p:status-transition|p:state-transition|p:remove|p:pipelines|p:insert|p:get-by-id|p:get|p:execute|p:create|p:condition|p:collection|p:action|ooxml:runs-merge|ooxml:package-uris|ooxml:package-parts-insert|ooxml:package-parts|msword:clean|mcgm:polygon|mcgm:point|mcgm:geospatial-query-from-elements|mcgm:geospatial-query|mcgm:circle|math:tanh|math:tan|math:sqrt|math:sinh|math:sin|math:pow|math:modf|math:log10|math:log|math:ldexp|math:frexp|math:fmod|math:floor|math:fabs|math:exp|math:cosh|math:cos|math:ceil|math:atan2|math:atan|math:asin|math:acos|map:put|map:map|map:keys|map:get|map:delete|map:count|map:clear|lnk:to|lnk:remove|lnk:insert|lnk:get|lnk:from|lnk:create|kml:polygon|kml:point|kml:interior-polygon|kml:geospatial-query-from-elements|kml:geospatial-query|kml:circle|kml:box|gml:polygon|gml:point|gml:interior-polygon|gml:geospatial-query-from-elements|gml:geospatial-query|gml:circle|gml:box|georss:point|georss:geospatial-query|georss:circle|geo:polygon|geo:point|geo:interior-polygon|geo:geospatial-query-from-elements|geo:geospatial-query|geo:circle|geo:box|fn:zero-or-one|fn:years-from-duration|fn:year-from-dateTime|fn:year-from-date|fn:upper-case|fn:unordered|fn:true|fn:translate|fn:trace|fn:tokenize|fn:timezone-from-time|fn:timezone-from-dateTime|fn:timezone-from-date|fn:sum|fn:subtract-dateTimes-yielding-yearMonthDuration|fn:subtract-dateTimes-yielding-dayTimeDuration|fn:substring-before|fn:substring-after|fn:substring|fn:subsequence|fn:string-to-codepoints|fn:string-pad|fn:string-length|fn:string-join|fn:string|fn:static-base-uri|fn:starts-with|fn:seconds-from-time|fn:seconds-from-duration|fn:seconds-from-dateTime|fn:round-half-to-even|fn:round|fn:root|fn:reverse|fn:resolve-uri|fn:resolve-QName|fn:replace|fn:remove|fn:QName|fn:prefix-from-QName|fn:position|fn:one-or-more|fn:number|fn:not|fn:normalize-unicode|fn:normalize-space|fn:node-name|fn:node-kind|fn:nilled|fn:namespace-uri-from-QName|fn:namespace-uri-for-prefix|fn:namespace-uri|fn:name|fn:months-from-duration|fn:month-from-dateTime|fn:month-from-date|fn:minutes-from-time|fn:minutes-from-duration|fn:minutes-from-dateTime|fn:min|fn:max|fn:matches|fn:lower-case|fn:local-name-from-QName|fn:local-name|fn:last|fn:lang|fn:iri-to-uri|fn:insert-before|fn:index-of|fn:in-scope-prefixes|fn:implicit-timezone|fn:idref|fn:id|fn:hours-from-time|fn:hours-from-duration|fn:hours-from-dateTime|fn:floor|fn:false|fn:expanded-QName|fn:exists|fn:exactly-one|fn:escape-uri|fn:escape-html-uri|fn:error|fn:ends-with|fn:encode-for-uri|fn:empty|fn:document-uri|fn:doc-available|fn:doc|fn:distinct-values|fn:distinct-nodes|fn:default-collation|fn:deep-equal|fn:days-from-duration|fn:day-from-dateTime|fn:day-from-date|fn:data|fn:current-time|fn:current-dateTime|fn:current-date|fn:count|fn:contains|fn:concat|fn:compare|fn:collection|fn:codepoints-to-string|fn:codepoint-equal|fn:ceiling|fn:boolean|fn:base-uri|fn:avg|fn:adjust-time-to-timezone|fn:adjust-dateTime-to-timezone|fn:adjust-date-to-timezone|fn:abs|feed:unsubscribe|feed:subscription|feed:subscribe|feed:request|feed:item|feed:description|excel:clean|entity:enrich|dom:set-pipelines|dom:set-permissions|dom:set-name|dom:set-evaluation-context|dom:set-domain-scope|dom:set-description|dom:remove-pipeline|dom:remove-permissions|dom:remove|dom:get|dom:evaluation-context|dom:domains|dom:domain-scope|dom:create|dom:configuration-set-restart-user|dom:configuration-set-permissions|dom:configuration-set-evaluation-context|dom:configuration-set-default-domain|dom:configuration-get|dom:configuration-create|dom:collection|dom:add-pipeline|dom:add-permissions|dls:retention-rules|dls:retention-rule-remove|dls:retention-rule-insert|dls:retention-rule|dls:purge|dls:node-expand|dls:link-references|dls:link-expand|dls:documents-query|dls:document-versions-query|dls:document-version-uri|dls:document-version-query|dls:document-version-delete|dls:document-version-as-of|dls:document-version|dls:document-update|dls:document-unmanage|dls:document-set-quality|dls:document-set-property|dls:document-set-properties|dls:document-set-permissions|dls:document-set-collections|dls:document-retention-rules|dls:document-remove-properties|dls:document-remove-permissions|dls:document-remove-collections|dls:document-purge|dls:document-manage|dls:document-is-managed|dls:document-insert-and-manage|dls:document-include-query|dls:document-history|dls:document-get-permissions|dls:document-extract-part|dls:document-delete|dls:document-checkout-status|dls:document-checkout|dls:document-checkin|dls:document-add-properties|dls:document-add-permissions|dls:document-add-collections|dls:break-checkout|dls:author-query|dls:as-of-query|dbk:convert|dbg:wait|dbg:value|dbg:stopped|dbg:stop|dbg:step|dbg:status|dbg:stack|dbg:out|dbg:next|dbg:line|dbg:invoke|dbg:function|dbg:finish|dbg:expr|dbg:eval|dbg:disconnect|dbg:detach|dbg:continue|dbg:connect|dbg:clear|dbg:breakpoints|dbg:break|dbg:attached|dbg:attach|cvt:save-converted-documents|cvt:part-uri|cvt:destination-uri|cvt:basepath|cvt:basename|cts:words|cts:word-query-weight|cts:word-query-text|cts:word-query-options|cts:word-query|cts:word-match|cts:walk|cts:uris|cts:uri-match|cts:train|cts:tokenize|cts:thresholds|cts:stem|cts:similar-query-weight|cts:similar-query-nodes|cts:similar-query|cts:shortest-distance|cts:search|cts:score|cts:reverse-query-weight|cts:reverse-query-nodes|cts:reverse-query|cts:remainder|cts:registered-query-weight|cts:registered-query-options|cts:registered-query-ids|cts:registered-query|cts:register|cts:query|cts:quality|cts:properties-query-query|cts:properties-query|cts:polygon-vertices|cts:polygon|cts:point-longitude|cts:point-latitude|cts:point|cts:or-query-queries|cts:or-query|cts:not-query-weight|cts:not-query-query|cts:not-query|cts:near-query-weight|cts:near-query-queries|cts:near-query-options|cts:near-query-distance|cts:near-query|cts:highlight|cts:geospatial-co-occurrences|cts:frequency|cts:fitness|cts:field-words|cts:field-word-query-weight|cts:field-word-query-text|cts:field-word-query-options|cts:field-word-query-field-name|cts:field-word-query|cts:field-word-match|cts:entity-highlight|cts:element-words|cts:element-word-query-weight|cts:element-word-query-text|cts:element-word-query-options|cts:element-word-query-element-name|cts:element-word-query|cts:element-word-match|cts:element-values|cts:element-value-ranges|cts:element-value-query-weight|cts:element-value-query-text|cts:element-value-query-options|cts:element-value-query-element-name|cts:element-value-query|cts:element-value-match|cts:element-value-geospatial-co-occurrences|cts:element-value-co-occurrences|cts:element-range-query-weight|cts:element-range-query-value|cts:element-range-query-options|cts:element-range-query-operator|cts:element-range-query-element-name|cts:element-range-query|cts:element-query-query|cts:element-query-element-name|cts:element-query|cts:element-pair-geospatial-values|cts:element-pair-geospatial-value-match|cts:element-pair-geospatial-query-weight|cts:element-pair-geospatial-query-region|cts:element-pair-geospatial-query-options|cts:element-pair-geospatial-query-longitude-name|cts:element-pair-geospatial-query-latitude-name|cts:element-pair-geospatial-query-element-name|cts:element-pair-geospatial-query|cts:element-pair-geospatial-boxes|cts:element-geospatial-values|cts:element-geospatial-value-match|cts:element-geospatial-query-weight|cts:element-geospatial-query-region|cts:element-geospatial-query-options|cts:element-geospatial-query-element-name|cts:element-geospatial-query|cts:element-geospatial-boxes|cts:element-child-geospatial-values|cts:element-child-geospatial-value-match|cts:element-child-geospatial-query-weight|cts:element-child-geospatial-query-region|cts:element-child-geospatial-query-options|cts:element-child-geospatial-query-element-name|cts:element-child-geospatial-query-child-name|cts:element-child-geospatial-query|cts:element-child-geospatial-boxes|cts:element-attribute-words|cts:element-attribute-word-query-weight|cts:element-attribute-word-query-text|cts:element-attribute-word-query-options|cts:element-attribute-word-query-element-name|cts:element-attribute-word-query-attribute-name|cts:element-attribute-word-query|cts:element-attribute-word-match|cts:element-attribute-values|cts:element-attribute-value-ranges|cts:element-attribute-value-query-weight|cts:element-attribute-value-query-text|cts:element-attribute-value-query-options|cts:element-attribute-value-query-element-name|cts:element-attribute-value-query-attribute-name|cts:element-attribute-value-query|cts:element-attribute-value-match|cts:element-attribute-value-geospatial-co-occurrences|cts:element-attribute-value-co-occurrences|cts:element-attribute-range-query-weight|cts:element-attribute-range-query-value|cts:element-attribute-range-query-options|cts:element-attribute-range-query-operator|cts:element-attribute-range-query-element-name|cts:element-attribute-range-query-attribute-name|cts:element-attribute-range-query|cts:element-attribute-pair-geospatial-values|cts:element-attribute-pair-geospatial-value-match|cts:element-attribute-pair-geospatial-query-weight|cts:element-attribute-pair-geospatial-query-region|cts:element-attribute-pair-geospatial-query-options|cts:element-attribute-pair-geospatial-query-longitude-name|cts:element-attribute-pair-geospatial-query-latitude-name|cts:element-attribute-pair-geospatial-query-element-name|cts:element-attribute-pair-geospatial-query|cts:element-attribute-pair-geospatial-boxes|cts:document-query-uris|cts:document-query|cts:distance|cts:directory-query-uris|cts:directory-query-depth|cts:directory-query|cts:destination|cts:deregister|cts:contains|cts:confidence|cts:collections|cts:collection-query-uris|cts:collection-query|cts:collection-match|cts:classify|cts:circle-radius|cts:circle-center|cts:circle|cts:box-west|cts:box-south|cts:box-north|cts:box-east|cts:box|cts:bearing|cts:arc-intersection|cts:and-query-queries|cts:and-query-options|cts:and-query|cts:and-not-query-positive-query|cts:and-not-query-negative-query|cts:and-not-query|css:get|css:convert|cpf:success|cpf:failure|cpf:document-set-state|cpf:document-set-processing-status|cpf:document-set-last-updated|cpf:document-set-error|cpf:document-get-state|cpf:document-get-processing-status|cpf:document-get-last-updated|cpf:document-get-error|cpf:check-transition|alert:spawn-matching-actions|alert:rule-user-id-query|alert:rule-set-user-id|alert:rule-set-query|alert:rule-set-options|alert:rule-set-name|alert:rule-set-description|alert:rule-set-action|alert:rule-remove|alert:rule-name-query|alert:rule-insert|alert:rule-id-query|alert:rule-get-user-id|alert:rule-get-query|alert:rule-get-options|alert:rule-get-name|alert:rule-get-id|alert:rule-get-description|alert:rule-get-action|alert:rule-action-query|alert:remove-triggers|alert:make-rule|alert:make-log-action|alert:make-config|alert:make-action|alert:invoke-matching-actions|alert:get-my-rules|alert:get-all-rules|alert:get-actions|alert:find-matching-rules|alert:create-triggers|alert:config-set-uri|alert:config-set-trigger-ids|alert:config-set-options|alert:config-set-name|alert:config-set-description|alert:config-set-cpf-domain-names|alert:config-set-cpf-domain-ids|alert:config-insert|alert:config-get-uri|alert:config-get-trigger-ids|alert:config-get-options|alert:config-get-name|alert:config-get-id|alert:config-get-description|alert:config-get-cpf-domain-names|alert:config-get-cpf-domain-ids|alert:config-get|alert:config-delete|alert:action-set-options|alert:action-set-name|alert:action-set-module-root|alert:action-set-module-db|alert:action-set-module|alert:action-set-description|alert:action-remove|alert:action-insert|alert:action-get-options|alert:action-get-name|alert:action-get-module-root|alert:action-get-module-db|alert:action-get-module|alert:action-get-description|zero-or-one|years-from-duration|year-from-dateTime|year-from-date|upper-case|unordered|true|translate|trace|tokenize|timezone-from-time|timezone-from-dateTime|timezone-from-date|sum|subtract-dateTimes-yielding-yearMonthDuration|subtract-dateTimes-yielding-dayTimeDuration|substring-before|substring-after|substring|subsequence|string-to-codepoints|string-pad|string-length|string-join|string|static-base-uri|starts-with|seconds-from-time|seconds-from-duration|seconds-from-dateTime|round-half-to-even|round|root|reverse|resolve-uri|resolve-QName|replace|remove|QName|prefix-from-QName|position|one-or-more|number|not|normalize-unicode|normalize-space|node-name|node-kind|nilled|namespace-uri-from-QName|namespace-uri-for-prefix|namespace-uri|name|months-from-duration|month-from-dateTime|month-from-date|minutes-from-time|minutes-from-duration|minutes-from-dateTime|min|max|matches|lower-case|local-name-from-QName|local-name|last|lang|iri-to-uri|insert-before|index-of|in-scope-prefixes|implicit-timezone|idref|id|hours-from-time|hours-from-duration|hours-from-dateTime|floor|false|expanded-QName|exists|exactly-one|escape-uri|escape-html-uri|error|ends-with|encode-for-uri|empty|document-uri|doc-available|doc|distinct-values|distinct-nodes|default-collation|deep-equal|days-from-duration|day-from-dateTime|day-from-date|data|current-time|current-dateTime|current-date|count|contains|concat|compare|collection|codepoints-to-string|codepoint-equal|ceiling|boolean|base-uri|avg|adjust-time-to-timezone|adjust-dateTime-to-timezone|adjust-date-to-timezone|abs)\b/], +["pln",/^[A-Za-z0-9_\-\:]+/],["pln",/^[\t\n\r \xA0]+/]]),["xq","xquery"]); diff --git a/app/src/main/assets/highlight/js/lang-xquery.js b/app/src/main/assets/highlight/js/lang-xquery.js new file mode 100644 index 00000000..a6d85372 --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-xquery.js @@ -0,0 +1,19 @@ +/* + + Copyright (C) 2011 Patrick Wied + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["var pln",/^\$[A-Za-z0-9_\-]+/,null,"$"]],[["pln",/^[\s=][<>][\s=]/],["lit",/^\@[\w-]+/],["tag",/^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],["com",/^\(:[\s\S]*?:\)/],["pln",/^[\/\{\};,\[\]\(\)]$/],["str",/^(?:\"(?:[^\"\\\{]|\\[\s\S])*(?:\"|$)|\'(?:[^\'\\\{]|\\[\s\S])*(?:\'|$))/,null,"\"'"],["kwd",/^(?:xquery|where|version|variable|union|typeswitch|treat|to|then|text|stable|sortby|some|self|schema|satisfies|returns|return|ref|processing-instruction|preceding-sibling|preceding|precedes|parent|only|of|node|namespace|module|let|item|intersect|instance|in|import|if|function|for|follows|following-sibling|following|external|except|every|else|element|descending|descendant-or-self|descendant|define|default|declare|comment|child|cast|case|before|attribute|assert|ascending|as|ancestor-or-self|ancestor|after|eq|order|by|or|and|schema-element|document-node|node|at)\b/], +["typ",/^(?:xs:yearMonthDuration|xs:unsignedLong|xs:time|xs:string|xs:short|xs:QName|xs:Name|xs:long|xs:integer|xs:int|xs:gYearMonth|xs:gYear|xs:gMonthDay|xs:gDay|xs:float|xs:duration|xs:double|xs:decimal|xs:dayTimeDuration|xs:dateTime|xs:date|xs:byte|xs:boolean|xs:anyURI|xf:yearMonthDuration)\b/,null],["fun pln",/^(?:xp:dereference|xinc:node-expand|xinc:link-references|xinc:link-expand|xhtml:restructure|xhtml:clean|xhtml:add-lists|xdmp:zip-manifest|xdmp:zip-get|xdmp:zip-create|xdmp:xquery-version|xdmp:word-convert|xdmp:with-namespaces|xdmp:version|xdmp:value|xdmp:user-roles|xdmp:user-last-login|xdmp:user|xdmp:url-encode|xdmp:url-decode|xdmp:uri-is-file|xdmp:uri-format|xdmp:uri-content-type|xdmp:unquote|xdmp:unpath|xdmp:triggers-database|xdmp:trace|xdmp:to-json|xdmp:tidy|xdmp:subbinary|xdmp:strftime|xdmp:spawn-in|xdmp:spawn|xdmp:sleep|xdmp:shutdown|xdmp:set-session-field|xdmp:set-response-encoding|xdmp:set-response-content-type|xdmp:set-response-code|xdmp:set-request-time-limit|xdmp:set|xdmp:servers|xdmp:server-status|xdmp:server-name|xdmp:server|xdmp:security-database|xdmp:security-assert|xdmp:schema-database|xdmp:save|xdmp:role-roles|xdmp:role|xdmp:rethrow|xdmp:restart|xdmp:request-timestamp|xdmp:request-status|xdmp:request-cancel|xdmp:request|xdmp:redirect-response|xdmp:random|xdmp:quote|xdmp:query-trace|xdmp:query-meters|xdmp:product-edition|xdmp:privilege-roles|xdmp:privilege|xdmp:pretty-print|xdmp:powerpoint-convert|xdmp:platform|xdmp:permission|xdmp:pdf-convert|xdmp:path|xdmp:octal-to-integer|xdmp:node-uri|xdmp:node-replace|xdmp:node-kind|xdmp:node-insert-child|xdmp:node-insert-before|xdmp:node-insert-after|xdmp:node-delete|xdmp:node-database|xdmp:mul64|xdmp:modules-root|xdmp:modules-database|xdmp:merging|xdmp:merge-cancel|xdmp:merge|xdmp:md5|xdmp:logout|xdmp:login|xdmp:log-level|xdmp:log|xdmp:lock-release|xdmp:lock-acquire|xdmp:load|xdmp:invoke-in|xdmp:invoke|xdmp:integer-to-octal|xdmp:integer-to-hex|xdmp:http-put|xdmp:http-post|xdmp:http-options|xdmp:http-head|xdmp:http-get|xdmp:http-delete|xdmp:hosts|xdmp:host-status|xdmp:host-name|xdmp:host|xdmp:hex-to-integer|xdmp:hash64|xdmp:hash32|xdmp:has-privilege|xdmp:groups|xdmp:group-serves|xdmp:group-servers|xdmp:group-name|xdmp:group-hosts|xdmp:group|xdmp:get-session-field-names|xdmp:get-session-field|xdmp:get-response-encoding|xdmp:get-response-code|xdmp:get-request-username|xdmp:get-request-user|xdmp:get-request-url|xdmp:get-request-protocol|xdmp:get-request-path|xdmp:get-request-method|xdmp:get-request-header-names|xdmp:get-request-header|xdmp:get-request-field-names|xdmp:get-request-field-filename|xdmp:get-request-field-content-type|xdmp:get-request-field|xdmp:get-request-client-certificate|xdmp:get-request-client-address|xdmp:get-request-body|xdmp:get-current-user|xdmp:get-current-roles|xdmp:get|xdmp:function-name|xdmp:function-module|xdmp:function|xdmp:from-json|xdmp:forests|xdmp:forest-status|xdmp:forest-restore|xdmp:forest-restart|xdmp:forest-name|xdmp:forest-delete|xdmp:forest-databases|xdmp:forest-counts|xdmp:forest-clear|xdmp:forest-backup|xdmp:forest|xdmp:filesystem-file|xdmp:filesystem-directory|xdmp:exists|xdmp:excel-convert|xdmp:eval-in|xdmp:eval|xdmp:estimate|xdmp:email|xdmp:element-content-type|xdmp:elapsed-time|xdmp:document-set-quality|xdmp:document-set-property|xdmp:document-set-properties|xdmp:document-set-permissions|xdmp:document-set-collections|xdmp:document-remove-properties|xdmp:document-remove-permissions|xdmp:document-remove-collections|xdmp:document-properties|xdmp:document-locks|xdmp:document-load|xdmp:document-insert|xdmp:document-get-quality|xdmp:document-get-properties|xdmp:document-get-permissions|xdmp:document-get-collections|xdmp:document-get|xdmp:document-forest|xdmp:document-delete|xdmp:document-add-properties|xdmp:document-add-permissions|xdmp:document-add-collections|xdmp:directory-properties|xdmp:directory-locks|xdmp:directory-delete|xdmp:directory-create|xdmp:directory|xdmp:diacritic-less|xdmp:describe|xdmp:default-permissions|xdmp:default-collections|xdmp:databases|xdmp:database-restore-validate|xdmp:database-restore-status|xdmp:database-restore-cancel|xdmp:database-restore|xdmp:database-name|xdmp:database-forests|xdmp:database-backup-validate|xdmp:database-backup-status|xdmp:database-backup-purge|xdmp:database-backup-cancel|xdmp:database-backup|xdmp:database|xdmp:collection-properties|xdmp:collection-locks|xdmp:collection-delete|xdmp:collation-canonical-uri|xdmp:castable-as|xdmp:can-grant-roles|xdmp:base64-encode|xdmp:base64-decode|xdmp:architecture|xdmp:apply|xdmp:amp-roles|xdmp:amp|xdmp:add64|xdmp:add-response-header|xdmp:access|trgr:trigger-set-recursive|trgr:trigger-set-permissions|trgr:trigger-set-name|trgr:trigger-set-module|trgr:trigger-set-event|trgr:trigger-set-description|trgr:trigger-remove-permissions|trgr:trigger-module|trgr:trigger-get-permissions|trgr:trigger-enable|trgr:trigger-disable|trgr:trigger-database-online-event|trgr:trigger-data-event|trgr:trigger-add-permissions|trgr:remove-trigger|trgr:property-content|trgr:pre-commit|trgr:post-commit|trgr:get-trigger-by-id|trgr:get-trigger|trgr:document-scope|trgr:document-content|trgr:directory-scope|trgr:create-trigger|trgr:collection-scope|trgr:any-property-content|thsr:set-entry|thsr:remove-term|thsr:remove-synonym|thsr:remove-entry|thsr:query-lookup|thsr:lookup|thsr:load|thsr:insert|thsr:expand|thsr:add-synonym|spell:suggest-detailed|spell:suggest|spell:remove-word|spell:make-dictionary|spell:load|spell:levenshtein-distance|spell:is-correct|spell:insert|spell:double-metaphone|spell:add-word|sec:users-collection|sec:user-set-roles|sec:user-set-password|sec:user-set-name|sec:user-set-description|sec:user-set-default-permissions|sec:user-set-default-collections|sec:user-remove-roles|sec:user-privileges|sec:user-get-roles|sec:user-get-description|sec:user-get-default-permissions|sec:user-get-default-collections|sec:user-doc-permissions|sec:user-doc-collections|sec:user-add-roles|sec:unprotect-collection|sec:uid-for-name|sec:set-realm|sec:security-version|sec:security-namespace|sec:security-installed|sec:security-collection|sec:roles-collection|sec:role-set-roles|sec:role-set-name|sec:role-set-description|sec:role-set-default-permissions|sec:role-set-default-collections|sec:role-remove-roles|sec:role-privileges|sec:role-get-roles|sec:role-get-description|sec:role-get-default-permissions|sec:role-get-default-collections|sec:role-doc-permissions|sec:role-doc-collections|sec:role-add-roles|sec:remove-user|sec:remove-role-from-users|sec:remove-role-from-role|sec:remove-role-from-privileges|sec:remove-role-from-amps|sec:remove-role|sec:remove-privilege|sec:remove-amp|sec:protect-collection|sec:privileges-collection|sec:privilege-set-roles|sec:privilege-set-name|sec:privilege-remove-roles|sec:privilege-get-roles|sec:privilege-add-roles|sec:priv-doc-permissions|sec:priv-doc-collections|sec:get-user-names|sec:get-unique-elem-id|sec:get-role-names|sec:get-role-ids|sec:get-privilege|sec:get-distinct-permissions|sec:get-collection|sec:get-amp|sec:create-user-with-role|sec:create-user|sec:create-role|sec:create-privilege|sec:create-amp|sec:collections-collection|sec:collection-set-permissions|sec:collection-remove-permissions|sec:collection-get-permissions|sec:collection-add-permissions|sec:check-admin|sec:amps-collection|sec:amp-set-roles|sec:amp-remove-roles|sec:amp-get-roles|sec:amp-doc-permissions|sec:amp-doc-collections|sec:amp-add-roles|search:unparse|search:suggest|search:snippet|search:search|search:resolve-nodes|search:resolve|search:remove-constraint|search:parse|search:get-default-options|search:estimate|search:check-options|prof:value|prof:reset|prof:report|prof:invoke|prof:eval|prof:enable|prof:disable|prof:allowed|ppt:clean|pki:template-set-request|pki:template-set-name|pki:template-set-key-type|pki:template-set-key-options|pki:template-set-description|pki:template-in-use|pki:template-get-version|pki:template-get-request|pki:template-get-name|pki:template-get-key-type|pki:template-get-key-options|pki:template-get-id|pki:template-get-description|pki:need-certificate|pki:is-temporary|pki:insert-trusted-certificates|pki:insert-template|pki:insert-signed-certificates|pki:insert-certificate-revocation-list|pki:get-trusted-certificate-ids|pki:get-template-ids|pki:get-template-certificate-authority|pki:get-template-by-name|pki:get-template|pki:get-pending-certificate-requests-xml|pki:get-pending-certificate-requests-pem|pki:get-pending-certificate-request|pki:get-certificates-for-template-xml|pki:get-certificates-for-template|pki:get-certificates|pki:get-certificate-xml|pki:get-certificate-pem|pki:get-certificate|pki:generate-temporary-certificate-if-necessary|pki:generate-temporary-certificate|pki:generate-template-certificate-authority|pki:generate-certificate-request|pki:delete-template|pki:delete-certificate|pki:create-template|pdf:make-toc|pdf:insert-toc-headers|pdf:get-toc|pdf:clean|p:status-transition|p:state-transition|p:remove|p:pipelines|p:insert|p:get-by-id|p:get|p:execute|p:create|p:condition|p:collection|p:action|ooxml:runs-merge|ooxml:package-uris|ooxml:package-parts-insert|ooxml:package-parts|msword:clean|mcgm:polygon|mcgm:point|mcgm:geospatial-query-from-elements|mcgm:geospatial-query|mcgm:circle|math:tanh|math:tan|math:sqrt|math:sinh|math:sin|math:pow|math:modf|math:log10|math:log|math:ldexp|math:frexp|math:fmod|math:floor|math:fabs|math:exp|math:cosh|math:cos|math:ceil|math:atan2|math:atan|math:asin|math:acos|map:put|map:map|map:keys|map:get|map:delete|map:count|map:clear|lnk:to|lnk:remove|lnk:insert|lnk:get|lnk:from|lnk:create|kml:polygon|kml:point|kml:interior-polygon|kml:geospatial-query-from-elements|kml:geospatial-query|kml:circle|kml:box|gml:polygon|gml:point|gml:interior-polygon|gml:geospatial-query-from-elements|gml:geospatial-query|gml:circle|gml:box|georss:point|georss:geospatial-query|georss:circle|geo:polygon|geo:point|geo:interior-polygon|geo:geospatial-query-from-elements|geo:geospatial-query|geo:circle|geo:box|fn:zero-or-one|fn:years-from-duration|fn:year-from-dateTime|fn:year-from-date|fn:upper-case|fn:unordered|fn:true|fn:translate|fn:trace|fn:tokenize|fn:timezone-from-time|fn:timezone-from-dateTime|fn:timezone-from-date|fn:sum|fn:subtract-dateTimes-yielding-yearMonthDuration|fn:subtract-dateTimes-yielding-dayTimeDuration|fn:substring-before|fn:substring-after|fn:substring|fn:subsequence|fn:string-to-codepoints|fn:string-pad|fn:string-length|fn:string-join|fn:string|fn:static-base-uri|fn:starts-with|fn:seconds-from-time|fn:seconds-from-duration|fn:seconds-from-dateTime|fn:round-half-to-even|fn:round|fn:root|fn:reverse|fn:resolve-uri|fn:resolve-QName|fn:replace|fn:remove|fn:QName|fn:prefix-from-QName|fn:position|fn:one-or-more|fn:number|fn:not|fn:normalize-unicode|fn:normalize-space|fn:node-name|fn:node-kind|fn:nilled|fn:namespace-uri-from-QName|fn:namespace-uri-for-prefix|fn:namespace-uri|fn:name|fn:months-from-duration|fn:month-from-dateTime|fn:month-from-date|fn:minutes-from-time|fn:minutes-from-duration|fn:minutes-from-dateTime|fn:min|fn:max|fn:matches|fn:lower-case|fn:local-name-from-QName|fn:local-name|fn:last|fn:lang|fn:iri-to-uri|fn:insert-before|fn:index-of|fn:in-scope-prefixes|fn:implicit-timezone|fn:idref|fn:id|fn:hours-from-time|fn:hours-from-duration|fn:hours-from-dateTime|fn:floor|fn:false|fn:expanded-QName|fn:exists|fn:exactly-one|fn:escape-uri|fn:escape-html-uri|fn:error|fn:ends-with|fn:encode-for-uri|fn:empty|fn:document-uri|fn:doc-available|fn:doc|fn:distinct-values|fn:distinct-nodes|fn:default-collation|fn:deep-equal|fn:days-from-duration|fn:day-from-dateTime|fn:day-from-date|fn:data|fn:current-time|fn:current-dateTime|fn:current-date|fn:count|fn:contains|fn:concat|fn:compare|fn:collection|fn:codepoints-to-string|fn:codepoint-equal|fn:ceiling|fn:boolean|fn:base-uri|fn:avg|fn:adjust-time-to-timezone|fn:adjust-dateTime-to-timezone|fn:adjust-date-to-timezone|fn:abs|feed:unsubscribe|feed:subscription|feed:subscribe|feed:request|feed:item|feed:description|excel:clean|entity:enrich|dom:set-pipelines|dom:set-permissions|dom:set-name|dom:set-evaluation-context|dom:set-domain-scope|dom:set-description|dom:remove-pipeline|dom:remove-permissions|dom:remove|dom:get|dom:evaluation-context|dom:domains|dom:domain-scope|dom:create|dom:configuration-set-restart-user|dom:configuration-set-permissions|dom:configuration-set-evaluation-context|dom:configuration-set-default-domain|dom:configuration-get|dom:configuration-create|dom:collection|dom:add-pipeline|dom:add-permissions|dls:retention-rules|dls:retention-rule-remove|dls:retention-rule-insert|dls:retention-rule|dls:purge|dls:node-expand|dls:link-references|dls:link-expand|dls:documents-query|dls:document-versions-query|dls:document-version-uri|dls:document-version-query|dls:document-version-delete|dls:document-version-as-of|dls:document-version|dls:document-update|dls:document-unmanage|dls:document-set-quality|dls:document-set-property|dls:document-set-properties|dls:document-set-permissions|dls:document-set-collections|dls:document-retention-rules|dls:document-remove-properties|dls:document-remove-permissions|dls:document-remove-collections|dls:document-purge|dls:document-manage|dls:document-is-managed|dls:document-insert-and-manage|dls:document-include-query|dls:document-history|dls:document-get-permissions|dls:document-extract-part|dls:document-delete|dls:document-checkout-status|dls:document-checkout|dls:document-checkin|dls:document-add-properties|dls:document-add-permissions|dls:document-add-collections|dls:break-checkout|dls:author-query|dls:as-of-query|dbk:convert|dbg:wait|dbg:value|dbg:stopped|dbg:stop|dbg:step|dbg:status|dbg:stack|dbg:out|dbg:next|dbg:line|dbg:invoke|dbg:function|dbg:finish|dbg:expr|dbg:eval|dbg:disconnect|dbg:detach|dbg:continue|dbg:connect|dbg:clear|dbg:breakpoints|dbg:break|dbg:attached|dbg:attach|cvt:save-converted-documents|cvt:part-uri|cvt:destination-uri|cvt:basepath|cvt:basename|cts:words|cts:word-query-weight|cts:word-query-text|cts:word-query-options|cts:word-query|cts:word-match|cts:walk|cts:uris|cts:uri-match|cts:train|cts:tokenize|cts:thresholds|cts:stem|cts:similar-query-weight|cts:similar-query-nodes|cts:similar-query|cts:shortest-distance|cts:search|cts:score|cts:reverse-query-weight|cts:reverse-query-nodes|cts:reverse-query|cts:remainder|cts:registered-query-weight|cts:registered-query-options|cts:registered-query-ids|cts:registered-query|cts:register|cts:query|cts:quality|cts:properties-query-query|cts:properties-query|cts:polygon-vertices|cts:polygon|cts:point-longitude|cts:point-latitude|cts:point|cts:or-query-queries|cts:or-query|cts:not-query-weight|cts:not-query-query|cts:not-query|cts:near-query-weight|cts:near-query-queries|cts:near-query-options|cts:near-query-distance|cts:near-query|cts:highlight|cts:geospatial-co-occurrences|cts:frequency|cts:fitness|cts:field-words|cts:field-word-query-weight|cts:field-word-query-text|cts:field-word-query-options|cts:field-word-query-field-name|cts:field-word-query|cts:field-word-match|cts:entity-highlight|cts:element-words|cts:element-word-query-weight|cts:element-word-query-text|cts:element-word-query-options|cts:element-word-query-element-name|cts:element-word-query|cts:element-word-match|cts:element-values|cts:element-value-ranges|cts:element-value-query-weight|cts:element-value-query-text|cts:element-value-query-options|cts:element-value-query-element-name|cts:element-value-query|cts:element-value-match|cts:element-value-geospatial-co-occurrences|cts:element-value-co-occurrences|cts:element-range-query-weight|cts:element-range-query-value|cts:element-range-query-options|cts:element-range-query-operator|cts:element-range-query-element-name|cts:element-range-query|cts:element-query-query|cts:element-query-element-name|cts:element-query|cts:element-pair-geospatial-values|cts:element-pair-geospatial-value-match|cts:element-pair-geospatial-query-weight|cts:element-pair-geospatial-query-region|cts:element-pair-geospatial-query-options|cts:element-pair-geospatial-query-longitude-name|cts:element-pair-geospatial-query-latitude-name|cts:element-pair-geospatial-query-element-name|cts:element-pair-geospatial-query|cts:element-pair-geospatial-boxes|cts:element-geospatial-values|cts:element-geospatial-value-match|cts:element-geospatial-query-weight|cts:element-geospatial-query-region|cts:element-geospatial-query-options|cts:element-geospatial-query-element-name|cts:element-geospatial-query|cts:element-geospatial-boxes|cts:element-child-geospatial-values|cts:element-child-geospatial-value-match|cts:element-child-geospatial-query-weight|cts:element-child-geospatial-query-region|cts:element-child-geospatial-query-options|cts:element-child-geospatial-query-element-name|cts:element-child-geospatial-query-child-name|cts:element-child-geospatial-query|cts:element-child-geospatial-boxes|cts:element-attribute-words|cts:element-attribute-word-query-weight|cts:element-attribute-word-query-text|cts:element-attribute-word-query-options|cts:element-attribute-word-query-element-name|cts:element-attribute-word-query-attribute-name|cts:element-attribute-word-query|cts:element-attribute-word-match|cts:element-attribute-values|cts:element-attribute-value-ranges|cts:element-attribute-value-query-weight|cts:element-attribute-value-query-text|cts:element-attribute-value-query-options|cts:element-attribute-value-query-element-name|cts:element-attribute-value-query-attribute-name|cts:element-attribute-value-query|cts:element-attribute-value-match|cts:element-attribute-value-geospatial-co-occurrences|cts:element-attribute-value-co-occurrences|cts:element-attribute-range-query-weight|cts:element-attribute-range-query-value|cts:element-attribute-range-query-options|cts:element-attribute-range-query-operator|cts:element-attribute-range-query-element-name|cts:element-attribute-range-query-attribute-name|cts:element-attribute-range-query|cts:element-attribute-pair-geospatial-values|cts:element-attribute-pair-geospatial-value-match|cts:element-attribute-pair-geospatial-query-weight|cts:element-attribute-pair-geospatial-query-region|cts:element-attribute-pair-geospatial-query-options|cts:element-attribute-pair-geospatial-query-longitude-name|cts:element-attribute-pair-geospatial-query-latitude-name|cts:element-attribute-pair-geospatial-query-element-name|cts:element-attribute-pair-geospatial-query|cts:element-attribute-pair-geospatial-boxes|cts:document-query-uris|cts:document-query|cts:distance|cts:directory-query-uris|cts:directory-query-depth|cts:directory-query|cts:destination|cts:deregister|cts:contains|cts:confidence|cts:collections|cts:collection-query-uris|cts:collection-query|cts:collection-match|cts:classify|cts:circle-radius|cts:circle-center|cts:circle|cts:box-west|cts:box-south|cts:box-north|cts:box-east|cts:box|cts:bearing|cts:arc-intersection|cts:and-query-queries|cts:and-query-options|cts:and-query|cts:and-not-query-positive-query|cts:and-not-query-negative-query|cts:and-not-query|css:get|css:convert|cpf:success|cpf:failure|cpf:document-set-state|cpf:document-set-processing-status|cpf:document-set-last-updated|cpf:document-set-error|cpf:document-get-state|cpf:document-get-processing-status|cpf:document-get-last-updated|cpf:document-get-error|cpf:check-transition|alert:spawn-matching-actions|alert:rule-user-id-query|alert:rule-set-user-id|alert:rule-set-query|alert:rule-set-options|alert:rule-set-name|alert:rule-set-description|alert:rule-set-action|alert:rule-remove|alert:rule-name-query|alert:rule-insert|alert:rule-id-query|alert:rule-get-user-id|alert:rule-get-query|alert:rule-get-options|alert:rule-get-name|alert:rule-get-id|alert:rule-get-description|alert:rule-get-action|alert:rule-action-query|alert:remove-triggers|alert:make-rule|alert:make-log-action|alert:make-config|alert:make-action|alert:invoke-matching-actions|alert:get-my-rules|alert:get-all-rules|alert:get-actions|alert:find-matching-rules|alert:create-triggers|alert:config-set-uri|alert:config-set-trigger-ids|alert:config-set-options|alert:config-set-name|alert:config-set-description|alert:config-set-cpf-domain-names|alert:config-set-cpf-domain-ids|alert:config-insert|alert:config-get-uri|alert:config-get-trigger-ids|alert:config-get-options|alert:config-get-name|alert:config-get-id|alert:config-get-description|alert:config-get-cpf-domain-names|alert:config-get-cpf-domain-ids|alert:config-get|alert:config-delete|alert:action-set-options|alert:action-set-name|alert:action-set-module-root|alert:action-set-module-db|alert:action-set-module|alert:action-set-description|alert:action-remove|alert:action-insert|alert:action-get-options|alert:action-get-name|alert:action-get-module-root|alert:action-get-module-db|alert:action-get-module|alert:action-get-description|zero-or-one|years-from-duration|year-from-dateTime|year-from-date|upper-case|unordered|true|translate|trace|tokenize|timezone-from-time|timezone-from-dateTime|timezone-from-date|sum|subtract-dateTimes-yielding-yearMonthDuration|subtract-dateTimes-yielding-dayTimeDuration|substring-before|substring-after|substring|subsequence|string-to-codepoints|string-pad|string-length|string-join|string|static-base-uri|starts-with|seconds-from-time|seconds-from-duration|seconds-from-dateTime|round-half-to-even|round|root|reverse|resolve-uri|resolve-QName|replace|remove|QName|prefix-from-QName|position|one-or-more|number|not|normalize-unicode|normalize-space|node-name|node-kind|nilled|namespace-uri-from-QName|namespace-uri-for-prefix|namespace-uri|name|months-from-duration|month-from-dateTime|month-from-date|minutes-from-time|minutes-from-duration|minutes-from-dateTime|min|max|matches|lower-case|local-name-from-QName|local-name|last|lang|iri-to-uri|insert-before|index-of|in-scope-prefixes|implicit-timezone|idref|id|hours-from-time|hours-from-duration|hours-from-dateTime|floor|false|expanded-QName|exists|exactly-one|escape-uri|escape-html-uri|error|ends-with|encode-for-uri|empty|document-uri|doc-available|doc|distinct-values|distinct-nodes|default-collation|deep-equal|days-from-duration|day-from-dateTime|day-from-date|data|current-time|current-dateTime|current-date|count|contains|concat|compare|collection|codepoints-to-string|codepoint-equal|ceiling|boolean|base-uri|avg|adjust-time-to-timezone|adjust-dateTime-to-timezone|adjust-date-to-timezone|abs)\b/], +["pln",/^[A-Za-z0-9_\-\:]+/],["pln",/^[\t\n\r \xA0]+/]]),["xq","xquery"]); diff --git a/app/src/main/assets/highlight/js/lang-yaml.js b/app/src/main/assets/highlight/js/lang-yaml.js new file mode 100644 index 00000000..a2b4b079 --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-yaml.js @@ -0,0 +1,18 @@ +/* + + Copyright (C) 2015 ribrdb @ code.google.com + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["pun",/^[:|>?]+/,null,":|>?"],["dec",/^%(?:YAML|TAG)[^#\r\n]+/,null,"%"],["typ",/^[&]\S+/,null,"&"],["typ",/^!\S*/,null,"!"],["str",/^"(?:[^\\"]|\\.)*(?:"|$)/,null,'"'],["str",/^'(?:[^']|'')*(?:'|$)/,null,"'"],["com",/^#[^\r\n]*/,null,"#"],["pln",/^\s+/,null," \t\r\n"]],[["dec",/^(?:---|\.\.\.)(?:[\r\n]|$)/],["pun",/^-/],["kwd",/^[\w-]+:[ \r\n]/],["pln", +/^\w+/]]),["yaml","yml"]); diff --git a/app/src/main/assets/highlight/js/lang-yml.js b/app/src/main/assets/highlight/js/lang-yml.js new file mode 100644 index 00000000..a2b4b079 --- /dev/null +++ b/app/src/main/assets/highlight/js/lang-yml.js @@ -0,0 +1,18 @@ +/* + + Copyright (C) 2015 ribrdb @ code.google.com + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +PR.registerLangHandler(PR.createSimpleLexer([["pun",/^[:|>?]+/,null,":|>?"],["dec",/^%(?:YAML|TAG)[^#\r\n]+/,null,"%"],["typ",/^[&]\S+/,null,"&"],["typ",/^!\S*/,null,"!"],["str",/^"(?:[^\\"]|\\.)*(?:"|$)/,null,'"'],["str",/^'(?:[^']|'')*(?:'|$)/,null,"'"],["com",/^#[^\r\n]*/,null,"#"],["pln",/^\s+/,null," \t\r\n"]],[["dec",/^(?:---|\.\.\.)(?:[\r\n]|$)/],["pun",/^-/],["kwd",/^[\w-]+:[ \r\n]/],["pln", +/^\w+/]]),["yaml","yml"]); diff --git a/app/src/main/assets/highlight/js/prettify.js b/app/src/main/assets/highlight/js/prettify.js new file mode 100644 index 00000000..0a2b435c --- /dev/null +++ b/app/src/main/assets/highlight/js/prettify.js @@ -0,0 +1,46 @@ +!function(){/* + + Copyright (C) 2006 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +window.PR_SHOULD_USE_CONTINUATION=!0; +(function(){function T(a){function d(e){var b=e.charCodeAt(0);if(92!==b)return b;var a=e.charAt(1);return(b=w[a])?b:"0"<=a&&"7">=a?parseInt(e.substring(1),8):"u"===a||"x"===a?parseInt(e.substring(2),16):e.charCodeAt(1)}function f(e){if(32>e)return(16>e?"\\x0":"\\x")+e.toString(16);e=String.fromCharCode(e);return"\\"===e||"-"===e||"]"===e||"^"===e?"\\"+e:e}function b(e){var b=e.substring(1,e.length-1).match(/\\u[0-9A-Fa-f]{4}|\\x[0-9A-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\s\S]|-|[^-\\]/g);e= +[];var a="^"===b[0],c=["["];a&&c.push("^");for(var a=a?1:0,g=b.length;ak||122k||90k||122h[0]&&(h[1]+1>h[0]&&c.push("-"),c.push(f(h[1])));c.push("]");return c.join("")}function v(e){for(var a=e.source.match(/(?:\[(?:[^\x5C\x5D]|\\[\s\S])*\]|\\u[A-Fa-f0-9]{4}|\\x[A-Fa-f0-9]{2}|\\[0-9]+|\\[^ux0-9]|\(\?[:!=]|[\(\)\^]|[^\x5B\x5C\(\)\^]+)/g),c=a.length,d=[],g=0,h=0;g/,null])):d.push(["com",/^#[^\r\n]*/,null,"#"]));a.cStyleComments&&(f.push(["com",/^\/\/[^\r\n]*/,null]),f.push(["com",/^\/\*[\s\S]*?(?:\*\/|$)/,null]));if(b=a.regexLiterals){var v=(b=1|\\/=?|::?|<>?>?=?|,|;|\\?|@|\\[|~|{|\\^\\^?=?|\\|\\|?=?|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*("+ +("/(?=[^/*"+b+"])(?:[^/\\x5B\\x5C"+b+"]|\\x5C"+v+"|\\x5B(?:[^\\x5C\\x5D"+b+"]|\\x5C"+v+")*(?:\\x5D|$))+/")+")")])}(b=a.types)&&f.push(["typ",b]);b=(""+a.keywords).replace(/^ | $/g,"");b.length&&f.push(["kwd",new RegExp("^(?:"+b.replace(/[\s,]+/g,"|")+")\\b"),null]);d.push(["pln",/^\s+/,null," \r\n\t\u00a0"]);b="^.[^\\s\\w.$@'\"`/\\\\]*";a.regexLiterals&&(b+="(?!s*/)");f.push(["lit",/^@[a-z_$][a-z_$@0-9]*/i,null],["typ",/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],["pln",/^[a-z_$][a-z_$@0-9]*/i, +null],["lit",/^(?:0x[a-f0-9]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+\-]?\d+)?)[a-z]*/i,null,"0123456789"],["pln",/^\\[\s\S]?/,null],["pun",new RegExp(b),null]);return G(d,f)}function L(a,d,f){function b(a){var c=a.nodeType;if(1==c&&!A.test(a.className))if("br"===a.nodeName)v(a),a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)b(a);else if((3==c||4==c)&&f){var d=a.nodeValue,q=d.match(n);q&&(c=d.substring(0,q.index),a.nodeValue=c,(d=d.substring(q.index+q[0].length))&& +a.parentNode.insertBefore(l.createTextNode(d),a.nextSibling),v(a),c||a.parentNode.removeChild(a))}}function v(a){function b(a,c){var d=c?a.cloneNode(!1):a,k=a.parentNode;if(k){var k=b(k,1),e=a.nextSibling;k.appendChild(d);for(var f=e;f;f=e)e=f.nextSibling,k.appendChild(f)}return d}for(;!a.nextSibling;)if(a=a.parentNode,!a)return;a=b(a.nextSibling,0);for(var d;(d=a.parentNode)&&1===d.nodeType;)a=d;c.push(a)}for(var A=/(?:^|\s)nocode(?:\s|$)/,n=/\r\n?|\n/,l=a.ownerDocument,m=l.createElement("li");a.firstChild;)m.appendChild(a.firstChild); +for(var c=[m],p=0;p=+v[1],d=/\n/g,A=a.a,n=A.length,f=0,l=a.c,m=l.length,b=0,c=a.g,p=c.length,w=0;c[p]=n;var r,e;for(e=r=0;e=h&&(b+=2);f>=k&&(w+=2)}}finally{g&&(g.style.display=a)}}catch(x){E.console&&console.log(x&&x.stack||x)}}var E=window,C=["break,continue,do,else,for,if,return,while"], +F=[[C,"auto,case,char,const,default,double,enum,extern,float,goto,inline,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],H=[F,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,delegate,dynamic_cast,explicit,export,friend,generic,late_check,mutable,namespace,nullptr,property,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"], +O=[F,"abstract,assert,boolean,byte,extends,finally,final,implements,import,instanceof,interface,null,native,package,strictfp,super,synchronized,throws,transient"],P=[F,"abstract,as,base,bool,by,byte,checked,decimal,delegate,descending,dynamic,event,finally,fixed,foreach,from,group,implicit,in,interface,internal,into,is,let,lock,null,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var,virtual,where"],F=[F,"debugger,eval,export,function,get,instanceof,null,set,undefined,var,with,Infinity,NaN"], +Q=[C,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],R=[C,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],C=[C,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],S=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)\b/, +W=/\S/,X=y({keywords:[H,P,O,F,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",Q,R,C],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),I={};t(X,["default-code"]);t(G([],[["pln",/^[^]*(?:>|$)/],["com",/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-", +/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),"default-markup htm html mxml xhtml xml xsl".split(" "));t(G([["pln",/^[\s]+/,null," \t\r\n"],["atv",/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/], +["pun",/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);t(G([],[["atv",/^[\s\S]+/]]),["uq.val"]);t(y({keywords:H,hashComments:!0,cStyleComments:!0,types:S}),"c cc cpp cxx cyc m".split(" "));t(y({keywords:"null,true,false"}),["json"]);t(y({keywords:P,hashComments:!0,cStyleComments:!0, +verbatimStrings:!0,types:S}),["cs"]);t(y({keywords:O,cStyleComments:!0}),["java"]);t(y({keywords:C,hashComments:!0,multiLineStrings:!0}),["bash","bsh","csh","sh"]);t(y({keywords:Q,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),["cv","py","python"]);t(y({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:2}), +["perl","pl","pm"]);t(y({keywords:R,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb","ruby"]);t(y({keywords:F,cStyleComments:!0,regexLiterals:!0}),["javascript","js"]);t(y({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,throw,true,try,unless,until,when,while,yes",hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);t(G([],[["str",/^[\s\S]+/]]),["regex"]); +var Y=E.PR={createSimpleLexer:G,registerLangHandler:t,sourceDecorator:y,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ",prettyPrintOne:E.prettyPrintOne=function(a,d,f){f=f||!1;d=d||null;var b=document.createElement("div");b.innerHTML="

"+a+"
";b=b.firstChild;f&&L(b,f,!0);M({j:d,m:f,h:b,l:1,a:null,i:null,c:null, +g:null});return b.innerHTML},prettyPrint:E.prettyPrint=function(a,d){function f(){for(var b=E.PR_SHOULD_USE_CONTINUATION?c.now()+250:Infinity;p:first-child { + margin-top: 0!important +} + +.markdown-body>:last-child { + margin-bottom: 0!important +} + +.markdown-body a:not([href]) { + color: inherit; + text-decoration: none +} + +.markdown-body .absent { + color: #c00 +} + +.markdown-body .anchor { + float: left; + padding-right: 4px; + margin-left: -20px; + line-height: 1 +} + +.markdown-body .anchor:focus { + outline: none +} + +.markdown-body p, .markdown-body blockquote, .markdown-body ul, .markdown-body ol, .markdown-body dl, .markdown-body table, .markdown-body pre { + margin-top: 0; + margin-bottom: 16px +} + +.markdown-body hr { + height: .25em; + padding: 0; + margin: 24px 0; + background-color: #e7e7e7; + border: 0 +} + +.markdown-body blockquote { + padding: 0 1em; + color: #777; + border-left: .25em solid #ddd +} + +.markdown-body blockquote>:first-child { + margin-top: 0 +} + +.markdown-body blockquote>:last-child { + margin-bottom: 0 +} + +.markdown-body kbd { + display: inline-block; + padding: 3px 5px; + font-size: 11px; + line-height: 10px; + color: #555; + vertical-align: middle; + background-color: #fcfcfc; + border: solid 1px #ccc; + border-bottom-color: #bbb; + border-radius: 3px; + box-shadow: inset 0 -1px 0 #bbb +} + +.markdown-body .loweralpha { + list-style-type: lower-alpha +} + +.markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6 { + margin-top: 24px; + margin-bottom: 16px; + font-weight: 600; + line-height: 1.25 +} + +.markdown-body h1 .octicon-link, .markdown-body h2 .octicon-link, .markdown-body h3 .octicon-link, .markdown-body h4 .octicon-link, .markdown-body h5 .octicon-link, .markdown-body h6 .octicon-link { + color: #000; + vertical-align: middle; + visibility: hidden +} + +.markdown-body h1:hover .anchor, .markdown-body h2:hover .anchor, .markdown-body h3:hover .anchor, .markdown-body h4:hover .anchor, .markdown-body h5:hover .anchor, .markdown-body h6:hover .anchor { + text-decoration: none +} + +.markdown-body h1:hover .anchor .octicon-link, .markdown-body h2:hover .anchor .octicon-link, .markdown-body h3:hover .anchor .octicon-link, .markdown-body h4:hover .anchor .octicon-link, .markdown-body h5:hover .anchor .octicon-link, .markdown-body h6:hover .anchor .octicon-link { + visibility: visible +} + +.markdown-body h1 tt, .markdown-body h1 code, .markdown-body h2 tt, .markdown-body h2 code, .markdown-body h3 tt, .markdown-body h3 code, .markdown-body h4 tt, .markdown-body h4 code, .markdown-body h5 tt, .markdown-body h5 code, .markdown-body h6 tt, .markdown-body h6 code { + font-size: inherit +} + +.markdown-body h1 { + padding-bottom: .3em; + font-size: 2em; + border-bottom: 1px solid #eee +} + +.markdown-body h2 { + padding-bottom: .3em; + font-size: 1.5em; + border-bottom: 1px solid #eee +} + +.markdown-body h3 { + font-size: 1.25em +} + +.markdown-body h4 { + font-size: 1em +} + +.markdown-body h5 { + font-size: .875em +} + +.markdown-body h6 { + font-size: .85em; + color: #777 +} + +.markdown-body ul, .markdown-body ol { + padding-left: 2em +} + +.markdown-body ul.no-list, .markdown-body ol.no-list { + padding: 0; + list-style-type: none +} + +.markdown-body ul ul, .markdown-body ul ol, .markdown-body ol ol, .markdown-body ol ul { + margin-top: 0; + margin-bottom: 0 +} + +.markdown-body li>p { + margin-top: 16px +} + +.markdown-body li+li { + margin-top: .25em +} + +.markdown-body dl { + padding: 0 +} + +.markdown-body dl dt { + padding: 0; + margin-top: 16px; + font-size: 1em; + font-style: italic; + font-weight: 700 +} + +.markdown-body dl dd { + padding: 0 16px; + margin-bottom: 16px +} + +.markdown-body table { + display: block; + width: 100%; + overflow: auto +} + +.markdown-body table th { + font-weight: 700 +} + +.markdown-body table th, .markdown-body table td { + padding: 6px 13px; + border: 1px solid #ddd +} + +.markdown-body table tr { + background-color: #fff; + border-top: 1px solid #ccc +} + +.markdown-body table tr:nth-child(2n) { + background-color: #f8f8f8 +} + +.markdown-body img { + max-width: 100%; + box-sizing: content-box; + background-color: #fff +} + +.markdown-body img[align=right] { + padding-left: 20px +} + +.markdown-body img[align=left] { + padding-right: 20px +} + +.markdown-body .emoji { + max-width: none; + vertical-align: text-top; + background-color: transparent +} + +.markdown-body span.frame { + display: block; + overflow: hidden +} + +.markdown-body span.frame>span { + display: block; + float: left; + width: auto; + padding: 7px; + margin: 13px 0 0; + overflow: hidden; + border: 1px solid #ddd +} + +.markdown-body span.frame span img { + display: block; + float: left +} + +.markdown-body span.frame span span { + display: block; + padding: 5px 0 0; + clear: both; + color: #333 +} + +.markdown-body span.align-center { + display: block; + overflow: hidden; + clear: both +} + +.markdown-body span.align-center>span { + display: block; + margin: 13px auto 0; + overflow: hidden; + text-align: center +} + +.markdown-body span.align-center span img { + margin: 0 auto; + text-align: center +} + +.markdown-body span.align-right { + display: block; + overflow: hidden; + clear: both +} + +.markdown-body span.align-right>span { + display: block; + margin: 13px 0 0; + overflow: hidden; + text-align: right +} + +.markdown-body span.align-right span img { + margin: 0; + text-align: right +} + +.markdown-body span.float-left { + display: block; + float: left; + margin-right: 13px; + overflow: hidden +} + +.markdown-body span.float-left span { + margin: 13px 0 0 +} + +.markdown-body span.float-right { + display: block; + float: right; + margin-left: 13px; + overflow: hidden +} + +.markdown-body span.float-right>span { + display: block; + margin: 13px auto 0; + overflow: hidden; + text-align: right +} + +.markdown-body code, .markdown-body tt { + padding: 0; + padding-top: .2em; + padding-bottom: .2em; + margin: 0; + font-size: 85%; + background-color: rgba(0, 0, 0, 0.04); + border-radius: 3px +} + +.markdown-body code::before, .markdown-body code::after, .markdown-body tt::before, .markdown-body tt::after { + letter-spacing: -.2em; + content: "\00a0" +} + +.markdown-body code br, .markdown-body tt br { + display: none +} + +.markdown-body del code { + text-decoration: inherit +} + +.markdown-body pre { + word-wrap: normal +} + +.markdown-body pre>code { + padding: 0; + margin: 0; + font-size: 100%; + word-break: normal; + white-space: pre; + background: transparent; + border: 0 +} + +.markdown-body .highlight { + margin-bottom: 16px +} + +.markdown-body .highlight pre { + margin-bottom: 0; + word-break: normal +} + +.markdown-body .highlight pre, .markdown-body pre { + padding: 16px; + overflow: auto; + font-size: 85%; + line-height: 1.45; + background-color: #f7f7f7; + border-radius: 3px +} + +.markdown-body pre code, .markdown-body pre tt { + display: inline; + max-width: auto; + padding: 0; + margin: 0; + overflow: visible; + line-height: inherit; + word-wrap: normal; + background-color: transparent; + border: 0 +} + +.markdown-body pre code::before, .markdown-body pre code::after, .markdown-body pre tt::before, .markdown-body pre tt::after { + content: normal +} + +.markdown-body .csv-data td, .markdown-body .csv-data th { + padding: 5px; + overflow: hidden; + font-size: 12px; + line-height: 1; + text-align: left; + white-space: nowrap +} + +.markdown-body .csv-data .blob-num { + padding: 10px 8px 9px; + text-align: right; + background: #fff; + border: 0 +} + +.markdown-body .csv-data tr { + border-top: 0 +} + +.markdown-body .csv-data th { + font-weight: 700; + background: #f8f8f8; + border-top: 0 +} + +.markdown-body { + font-size: 15px +} + +.markdown-body blockquote { + margin-right: 0; + margin-left: 0 +} + +.markdown-body code { + white-space: normal +} + +.markdown-body.email-format { + line-height: 1.5em!important +} + +.markdown-body.email-format div { + white-space: pre-wrap +} + +.markdown-body .email-hidden-reply { + display: none; + white-space: pre-wrap +} + +.markdown-body .email-hidden-reply.expanded { + display: block +} + +.markdown-body .email-quoted-reply, .markdown-body .email-signature-reply { + padding: 0 15px; + margin-bottom: 15px; + color: #767676; + border-left: 4px solid #ddd +} + +.pl-c { + color: #969896 +} + +.pl-c1, .pl-s .pl-v { + color: #0086b3 +} + +.pl-e, .pl-en { + color: #795da3 +} + +.pl-smi, .pl-s .pl-s1 { + color: #333 +} + +.pl-ent { + color: #63a35c +} + +.pl-k { + color: #a71d5d +} + +.pl-s, .pl-pds, .pl-s .pl-pse .pl-s1, .pl-sr, .pl-sr .pl-cce, .pl-sr .pl-sre, .pl-sr .pl-sra { + color: #183691 +} + +.pl-v { + color: #ed6a43 +} + +.pl-id { + color: #b52a1d +} + +.pl-ii { + color: #f8f8f8; + background-color: #b52a1d +} + +.pl-sr .pl-cce { + font-weight: 700; + color: #63a35c +} + +.pl-ml { + color: #693a17 +} + +.pl-mh, .pl-mh .pl-en, .pl-ms { + font-weight: 700; + color: #1d3e81 +} + +.pl-mq { + color: teal +} + +.pl-mi { + font-style: italic; + color: #333 +} + +.pl-mb { + font-weight: 700; + color: #333 +} + +.pl-md { + color: #bd2c00; + background-color: #ffecec +} + +.pl-mi1 { + color: #55a532; + background-color: #eaffea +} + +.pl-mdr { + font-weight: 700; + color: #795da3 +} + +.pl-mo { + color: #1d3e81 +} + +kbd { + display: inline-block; + padding: 3px 5px; + font: 11px Consolas, "Liberation Mono", Menlo, Courier, monospace; + line-height: 10px; + color: #555; + vertical-align: middle; + background-color: #fcfcfc; + border: solid 1px #ccc; + border-bottom-color: #bbb; + border-radius: 3px; + box-shadow: inset 0 -1px 0 #bbb +} + +.badmono { + font-family: sans-serif; + font-weight: 700 +} diff --git a/app/src/main/assets/md/intercept-touch.js b/app/src/main/assets/md/intercept-touch.js new file mode 100644 index 00000000..9dc266d6 --- /dev/null +++ b/app/src/main/assets/md/intercept-touch.js @@ -0,0 +1,26 @@ +document.addEventListener("DOMContentLoaded", function(event) { + document.querySelectorAll('img').forEach(function(img){ + img.onerror = function(){this.style.display='none';}; + }); +}); + +window.onload = function() { + addTouchEvents(document.getElementsByTagName("pre")); + addTouchEvents(document.getElementsByTagName("table")); + addEventListener(document.getElementsByClassName("highlight")); +}; + +function addTouchEvents(elements) { + for (var i = 0; i < elements.length; i++) { + elements[i].addEventListener("touchstart", touchStart, false); + elements[i].addEventListener("touchend", touchEnd, false); + } +} + +function touchStart(event) { + Android.startIntercept(); +} + +function touchEnd(event) { + Android.stopIntercept(); +} diff --git a/app/src/main/java/com/fastaccess/App.java b/app/src/main/java/com/fastaccess/App.java new file mode 100644 index 00000000..0b105a16 --- /dev/null +++ b/app/src/main/java/com/fastaccess/App.java @@ -0,0 +1,41 @@ +package com.fastaccess; + +import android.app.Application; +import android.support.annotation.NonNull; + +import com.commonsware.cwac.anddown.AndDown; +import com.fastaccess.helper.TypeFaceHelper; +import com.fastaccess.provider.uil.UILProvider; +import com.siimkinks.sqlitemagic.SqliteMagic; + + +/** + * Created by Kosh on 03 Feb 2017, 12:07 AM + */ + +public class App extends Application { + private static App instance; + + private AndDown andDown; + + @Override public void onCreate() { + super.onCreate(); + instance = this; + SqliteMagic.setLoggingEnabled(BuildConfig.DEBUG); + SqliteMagic.init(this); + UILProvider.initUIL(this); + TypeFaceHelper.generateTypeface(this); + } + + @NonNull public static App getInstance() { + return instance; + } + + + @NonNull public AndDown getAndDown() { + if (andDown == null) { + andDown = new AndDown(); + } + return andDown; + } +} diff --git a/app/src/main/java/com/fastaccess/data/dao/AccessTokenModel.java b/app/src/main/java/com/fastaccess/data/dao/AccessTokenModel.java new file mode 100644 index 00000000..dfe20522 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/AccessTokenModel.java @@ -0,0 +1,16 @@ +package com.fastaccess.data.dao; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Created by Kosh on 09 Nov 2016, 11:28 PM + */ + + +@Getter @Setter @NoArgsConstructor +public class AccessTokenModel { + private String accessToken; + private String tokenType; +} diff --git a/app/src/main/java/com/fastaccess/data/dao/CommentRequestModel.java b/app/src/main/java/com/fastaccess/data/dao/CommentRequestModel.java new file mode 100644 index 00000000..0f24df63 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/CommentRequestModel.java @@ -0,0 +1,29 @@ +package com.fastaccess.data.dao; + +import android.os.Parcel; +import android.os.Parcelable; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Created by Kosh on 20 Nov 2016, 10:40 AM + */ + +@Getter @Setter @NoArgsConstructor +public class CommentRequestModel implements Parcelable { + private String body; + + @Override public int describeContents() { return 0; } + + @Override public void writeToParcel(Parcel dest, int flags) {dest.writeString(this.body);} + + @SuppressWarnings("WeakerAccess") protected CommentRequestModel(Parcel in) {this.body = in.readString();} + + public static final Creator CREATOR = new Creator() { + @Override public CommentRequestModel createFromParcel(Parcel source) {return new CommentRequestModel(source);} + + @Override public CommentRequestModel[] newArray(int size) {return new CommentRequestModel[size];} + }; +} diff --git a/app/src/main/java/com/fastaccess/data/dao/CommentsModel.java b/app/src/main/java/com/fastaccess/data/dao/CommentsModel.java new file mode 100644 index 00000000..f41ce898 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/CommentsModel.java @@ -0,0 +1,219 @@ +package com.fastaccess.data.dao; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; + +import com.siimkinks.sqlitemagic.CommentsModelTable; +import com.siimkinks.sqlitemagic.Delete; +import com.siimkinks.sqlitemagic.Select; +import com.siimkinks.sqlitemagic.annotation.Column; +import com.siimkinks.sqlitemagic.annotation.Id; +import com.siimkinks.sqlitemagic.annotation.Table; + +import java.util.Date; +import java.util.List; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import rx.Completable; +import rx.Observable; + +/** + * Created by Kosh on 20 Nov 2016, 10:34 AM + */ + +@Getter @Setter @NoArgsConstructor @Table(persistAll = true) +public class CommentsModel implements Parcelable { + + @Id(autoIncrement = false) @Column long id; + @Column(handleRecursively = false, onDeleteCascade = true) UserModel user; + @Column String url; + @Column String body; + @Column String bodyHtml; + @Column String htmlUrl; + @Column Date createdAt; + @Column Date updatedAt; + @Column int position; + @Column int line; + @Column String path; + @Column String commitId; + @Column String repoId; + @Column String login; + @Column String gistId; + @Column String issueId; + @Column String pullRequestId; + + public Completable save() { + return persist().observe().toCompletable(); + } + + public static Completable saveForGist(@NonNull List models, @NonNull String gistId) { + return Delete.from(CommentsModelTable.COMMENTS_MODEL) + .where(CommentsModelTable.COMMENTS_MODEL.GIST_ID.is(gistId)) + .observe() + .toCompletable() + .andThen(Observable.from(models) + .map(model -> { + model.setGistId(gistId); + return model.save(); + })) + .toCompletable(); + } + + public static Completable saveForCommits(@NonNull List models, @NonNull String repoId, @NonNull String login, @NonNull String + commitId) { + return Delete.from(CommentsModelTable.COMMENTS_MODEL) + .where(CommentsModelTable.COMMENTS_MODEL.COMMIT_ID.is(commitId) + .and(CommentsModelTable.COMMENTS_MODEL.REPO_ID.is(repoId)) + .and(CommentsModelTable.COMMENTS_MODEL.LOGIN.is(login))) + .observe() + .toCompletable() + .andThen(Observable.from(models) + .map(model -> { + model.setLogin(login); + model.setRepoId(repoId); + model.setCommitId(commitId); + return model.save(); + })) + .toCompletable(); + } + + public static Completable saveForIssues(@NonNull List models, @NonNull String repoId, @NonNull String login, @NonNull String + issueId) { + return Delete.from(CommentsModelTable.COMMENTS_MODEL) + .where(CommentsModelTable.COMMENTS_MODEL.ISSUE_ID.is(issueId) + .and(CommentsModelTable.COMMENTS_MODEL.REPO_ID.is(repoId)) + .and(CommentsModelTable.COMMENTS_MODEL.LOGIN.is(login))) + .observe() + .toCompletable() + .andThen(Observable.from(models) + .map(model -> { + model.setLogin(login); + model.setRepoId(repoId); + model.setIssueId(issueId); + return model.save(); + })) + .toCompletable(); + } + + public static Completable saveForPullRequest(@NonNull List models, @NonNull String repoId, @NonNull String login, @NonNull String + pullRequestId) { + return Delete.from(CommentsModelTable.COMMENTS_MODEL) + .where(CommentsModelTable.COMMENTS_MODEL.PULL_REQUEST_ID.is(pullRequestId) + .and(CommentsModelTable.COMMENTS_MODEL.REPO_ID.is(repoId)) + .and(CommentsModelTable.COMMENTS_MODEL.LOGIN.is(login))) + .observe() + .toCompletable() + .andThen(Observable.from(models) + .map(model -> { + model.setLogin(login); + model.setRepoId(repoId); + model.setPullRequestId(pullRequestId); + return model.save(); + })) + .toCompletable(); + } + + public static Observable> getGistComments(@NonNull String gistId) { + return Select.from(CommentsModelTable.COMMENTS_MODEL) + .where(CommentsModelTable.COMMENTS_MODEL.GIST_ID.is(gistId)) + .orderBy(CommentsModelTable.COMMENTS_MODEL.UPDATED_AT.desc()) + .observe() + .runQuery(); + } + + public static Observable> getCommitComments(@NonNull String repoId, @NonNull String login, @NonNull String commitId) { + return Select.from(CommentsModelTable.COMMENTS_MODEL) + .where(CommentsModelTable.COMMENTS_MODEL.COMMIT_ID.is(commitId) + .and(CommentsModelTable.COMMENTS_MODEL.REPO_ID.is(repoId)) + .and(CommentsModelTable.COMMENTS_MODEL.LOGIN.is(login))) + .orderBy(CommentsModelTable.COMMENTS_MODEL.UPDATED_AT.desc()) + .observe() + .runQuery(); + } + + public static Observable> getIssueComments(@NonNull String repoId, @NonNull String login, @NonNull String issueId) { + return Select.from(CommentsModelTable.COMMENTS_MODEL) + .where(CommentsModelTable.COMMENTS_MODEL.ISSUE_ID.is(issueId) + .and(CommentsModelTable.COMMENTS_MODEL.REPO_ID.is(repoId)) + .and(CommentsModelTable.COMMENTS_MODEL.LOGIN.is(login))) + .orderBy(CommentsModelTable.COMMENTS_MODEL.UPDATED_AT.desc()) + .observe() + .runQuery(); + } + + public static Observable> getPullRequestComments(@NonNull String repoId, @NonNull String login, @NonNull String + pullRequestId) { + return Select.from(CommentsModelTable.COMMENTS_MODEL) + .where(CommentsModelTable.COMMENTS_MODEL.PULL_REQUEST_ID.is(pullRequestId) + .and(CommentsModelTable.COMMENTS_MODEL.REPO_ID.is(repoId)) + .and(CommentsModelTable.COMMENTS_MODEL.LOGIN.is(login))) + .orderBy(CommentsModelTable.COMMENTS_MODEL.UPDATED_AT.desc()) + .observe() + .runQuery(); + } + + @Override public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + CommentsModel that = (CommentsModel) o; + + return id == that.id; + + } + + @Override public int hashCode() { + return (int) (id ^ (id >>> 32)); + } + + @Override public int describeContents() { return 0; } + + @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(this.id); + dest.writeParcelable(this.user, flags); + dest.writeString(this.url); + dest.writeString(this.body); + dest.writeString(this.bodyHtml); + dest.writeString(this.htmlUrl); + dest.writeLong(this.createdAt != null ? this.createdAt.getTime() : -1); + dest.writeLong(this.updatedAt != null ? this.updatedAt.getTime() : -1); + dest.writeInt(this.position); + dest.writeInt(this.line); + dest.writeString(this.path); + dest.writeString(this.commitId); + dest.writeString(this.repoId); + dest.writeString(this.gistId); + dest.writeString(this.issueId); + dest.writeString(this.pullRequestId); + } + + protected CommentsModel(Parcel in) { + this.id = in.readLong(); + this.user = in.readParcelable(UserModel.class.getClassLoader()); + this.url = in.readString(); + this.body = in.readString(); + this.bodyHtml = in.readString(); + this.htmlUrl = in.readString(); + long tmpCreatedAt = in.readLong(); + this.createdAt = tmpCreatedAt == -1 ? null : new Date(tmpCreatedAt); + long tmpUpdatedAt = in.readLong(); + this.updatedAt = tmpUpdatedAt == -1 ? null : new Date(tmpUpdatedAt); + this.position = in.readInt(); + this.line = in.readInt(); + this.path = in.readString(); + this.commitId = in.readString(); + this.repoId = in.readString(); + this.gistId = in.readString(); + this.issueId = in.readString(); + this.pullRequestId = in.readString(); + } + + public static final Creator CREATOR = new Creator() { + @Override public CommentsModel createFromParcel(Parcel source) {return new CommentsModel(source);} + + @Override public CommentsModel[] newArray(int size) {return new CommentsModel[size];} + }; +} diff --git a/app/src/main/java/com/fastaccess/data/dao/CommitFileListModel.java b/app/src/main/java/com/fastaccess/data/dao/CommitFileListModel.java new file mode 100644 index 00000000..6bd6ed29 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/CommitFileListModel.java @@ -0,0 +1,9 @@ +package com.fastaccess.data.dao; + +import java.util.ArrayList; + +/** + * Created by Kosh on 12 Feb 2017, 12:06 AM + */ + +public class CommitFileListModel extends ArrayList {} diff --git a/app/src/main/java/com/fastaccess/data/dao/CommitFileModel.java b/app/src/main/java/com/fastaccess/data/dao/CommitFileModel.java new file mode 100644 index 00000000..78db70cf --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/CommitFileModel.java @@ -0,0 +1,75 @@ +package com.fastaccess.data.dao; + +import android.os.Parcel; +import android.os.Parcelable; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Created by Kosh on 01 Jan 2017, 9:00 PM + */ +@Getter @Setter @NoArgsConstructor +public class CommitFileModel implements Parcelable { + + private String sha; + private String filename; + private String status; + private int additions; + private int deletions; + private int changes; + private String blobUrl; + private String rawUrl; + private String contentsUrl; + private String patch; + + @Override public int describeContents() { return 0; } + + @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.sha); + dest.writeString(this.filename); + dest.writeString(this.status); + dest.writeInt(this.additions); + dest.writeInt(this.deletions); + dest.writeInt(this.changes); + dest.writeString(this.blobUrl); + dest.writeString(this.rawUrl); + dest.writeString(this.contentsUrl); + dest.writeString(this.patch); + } + + @SuppressWarnings("WeakerAccess") protected CommitFileModel(Parcel in) { + this.sha = in.readString(); + this.filename = in.readString(); + this.status = in.readString(); + this.additions = in.readInt(); + this.deletions = in.readInt(); + this.changes = in.readInt(); + this.blobUrl = in.readString(); + this.rawUrl = in.readString(); + this.contentsUrl = in.readString(); + this.patch = in.readString(); + } + + public static final Creator CREATOR = new Creator() { + @Override public CommitFileModel createFromParcel(Parcel source) {return new CommitFileModel(source);} + + @Override public CommitFileModel[] newArray(int size) {return new CommitFileModel[size];} + }; + + @Override public String toString() { + return "CommitFileModel{" + + "sha='" + sha + '\'' + + ", filename='" + filename + '\'' + + ", status='" + status + '\'' + + ", additions=" + additions + + ", deletions=" + deletions + + ", changes=" + changes + + ", blobUrl='" + blobUrl + '\'' + + ", rawUrl='" + rawUrl + '\'' + + ", contentsUrl='" + contentsUrl + '\'' + + ", patch='" + patch + '\'' + + '}'; + } +} diff --git a/app/src/main/java/com/fastaccess/data/dao/CommitListModel.java b/app/src/main/java/com/fastaccess/data/dao/CommitListModel.java new file mode 100644 index 00000000..b156dbbf --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/CommitListModel.java @@ -0,0 +1,9 @@ +package com.fastaccess.data.dao; + +import java.util.ArrayList; + +/** + * Created by Kosh on 12 Feb 2017, 12:10 AM + */ + +public class CommitListModel extends ArrayList {} diff --git a/app/src/main/java/com/fastaccess/data/dao/CommitModel.java b/app/src/main/java/com/fastaccess/data/dao/CommitModel.java new file mode 100644 index 00000000..e6f879d1 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/CommitModel.java @@ -0,0 +1,158 @@ +package com.fastaccess.data.dao; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; + +import com.google.gson.annotations.SerializedName; +import com.siimkinks.sqlitemagic.CommitModelTable; +import com.siimkinks.sqlitemagic.Delete; +import com.siimkinks.sqlitemagic.Select; +import com.siimkinks.sqlitemagic.annotation.Column; +import com.siimkinks.sqlitemagic.annotation.Table; + +import java.util.List; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import rx.Completable; +import rx.Observable; + +/** + * Created by Kosh on 08 Dec 2016, 8:55 PM + */ + +@Getter @Setter @NoArgsConstructor @Table(persistAll = true) +public class CommitModel implements Parcelable { + @Column String url; + @Column String ref; + @Column RepoModel repo; + @Column String sha; + @Column @SerializedName("distincted") boolean distincted; + @Column @SerializedName("commit") GitCommitModel gitCommit; + @Column UserModel author; + @Column UserModel committer; + @Column UserModel user; + @Column CommitListModel parents; + @Column GithubState stats; + @Column CommitFileListModel files; + @Column String htmlUrl; + @Column String login; + @Column String repoId; + @Column long pullRequestNumber; + + + public Completable save() { + return this.persist().observe() + .toCompletable(); + } + + public static Completable save(@NonNull List models, @NonNull String repoId, @NonNull String login) { + return Delete.from(CommitModelTable.COMMIT_MODEL) + .where(CommitModelTable.COMMIT_MODEL.REPO_ID.is(repoId)) + .observe() + .toCompletable() + .andThen(Observable.from(models) + .map(commitModel -> { + commitModel.setRepoId(repoId); + commitModel.setLogin(login); + return commitModel.save(); + })) + .toCompletable(); + } + + public static Completable save(@NonNull List models, @NonNull String repoId, @NonNull String login, long number) { + return Delete.from(CommitModelTable.COMMIT_MODEL) + .where(CommitModelTable.COMMIT_MODEL.REPO_ID.is(repoId)) + .observe() + .toCompletable() + .andThen(Observable.from(models) + .map(commitModel -> { + commitModel.setRepoId(repoId); + commitModel.setLogin(login); + commitModel.setPullRequestNumber(number); + return commitModel.save(); + })) + .toCompletable(); + } + + public static Observable> getCommits(@NonNull String repoId, @NonNull String login) { + return Select.from(CommitModelTable.COMMIT_MODEL) + .where(CommitModelTable.COMMIT_MODEL.REPO_ID.is(repoId) + .and(CommitModelTable.COMMIT_MODEL.LOGIN.is(login)) + .and(CommitModelTable.COMMIT_MODEL.PULL_REQUEST_NUMBER.is(0L))) + .queryDeep() + .observe() + .runQuery(); + } + + public static Observable> getCommits(@NonNull String repoId, @NonNull String login, long pullRequestNumber) { + return Select.from(CommitModelTable.COMMIT_MODEL) + .where(CommitModelTable.COMMIT_MODEL.REPO_ID.is(repoId) + .and(CommitModelTable.COMMIT_MODEL.LOGIN.is(login)) + .and(CommitModelTable.COMMIT_MODEL.PULL_REQUEST_NUMBER.is(pullRequestNumber))) + .queryDeep() + .observe() + .runQuery(); + } + + public static Observable getCommit(@NonNull String sha, @NonNull String repoId, @NonNull String login) { + return Select.from(CommitModelTable.COMMIT_MODEL) + .where(CommitModelTable.COMMIT_MODEL.REPO_ID.is(repoId) + .and(CommitModelTable.COMMIT_MODEL.LOGIN.is(login)) + .and(CommitModelTable.COMMIT_MODEL.SHA.is(sha))) + .queryDeep() + .takeFirst() + .observe() + .runQuery(); + } + + @Override public int describeContents() { return 0; } + + @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.url); + dest.writeString(this.ref); + dest.writeParcelable(this.repo, flags); + dest.writeString(this.sha); + dest.writeByte(this.distincted ? (byte) 1 : (byte) 0); + dest.writeParcelable(this.gitCommit, flags); + dest.writeParcelable(this.author, flags); + dest.writeParcelable(this.committer, flags); + dest.writeParcelable(this.user, flags); + dest.writeList(this.parents); + dest.writeParcelable(this.stats, flags); + dest.writeList(this.files); + dest.writeString(this.htmlUrl); + dest.writeString(this.login); + dest.writeString(this.repoId); + dest.writeLong(this.id); + } + + protected CommitModel(Parcel in) { + this.url = in.readString(); + this.ref = in.readString(); + this.repo = in.readParcelable(RepoModel.class.getClassLoader()); + this.sha = in.readString(); + this.distincted = in.readByte() != 0; + this.gitCommit = in.readParcelable(GitCommitModel.class.getClassLoader()); + this.author = in.readParcelable(UserModel.class.getClassLoader()); + this.committer = in.readParcelable(UserModel.class.getClassLoader()); + this.user = in.readParcelable(UserModel.class.getClassLoader()); + in.readList(this.parents, this.parents.getClass().getClassLoader()); + this.stats = in.readParcelable(GithubState.class.getClassLoader()); + in.readList(this.files, this.files.getClass().getClassLoader()); + this.htmlUrl = in.readString(); + this.login = in.readString(); + this.repoId = in.readString(); + this.id = in.readLong(); + } + + public static final Creator CREATOR = new Creator() { + @Override public CommitModel createFromParcel(Parcel source) {return new CommitModel(source);} + + @Override public CommitModel[] newArray(int size) {return new CommitModel[size];} + }; +} + + diff --git a/app/src/main/java/com/fastaccess/data/dao/CreateGistModel.java b/app/src/main/java/com/fastaccess/data/dao/CreateGistModel.java new file mode 100644 index 00000000..0e930d79 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/CreateGistModel.java @@ -0,0 +1,43 @@ +package com.fastaccess.data.dao; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.google.gson.annotations.SerializedName; + +import java.util.HashMap; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Created by Kosh on 18 Feb 2017, 11:15 PM + */ + +@Setter @Getter @NoArgsConstructor +public class CreateGistModel implements Parcelable { + private HashMap files; + private String description; + @SerializedName("public") private boolean publicGist; + + @Override public int describeContents() { return 0; } + + @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeSerializable(this.files); + dest.writeString(this.description); + dest.writeByte(this.publicGist ? (byte) 1 : (byte) 0); + } + + @SuppressWarnings({"WeakerAccess", "unchecked"}) protected CreateGistModel(Parcel in) { + this.files = (HashMap) in.readSerializable(); + this.description = in.readString(); + this.publicGist = in.readByte() != 0; + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override public CreateGistModel createFromParcel(Parcel source) {return new CreateGistModel(source);} + + @Override public CreateGistModel[] newArray(int size) {return new CreateGistModel[size];} + }; +} diff --git a/app/src/main/java/com/fastaccess/data/dao/EventsModel.java b/app/src/main/java/com/fastaccess/data/dao/EventsModel.java new file mode 100644 index 00000000..8f203119 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/EventsModel.java @@ -0,0 +1,83 @@ +package com.fastaccess.data.dao; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; + +import com.fastaccess.data.dao.types.EventsType; +import com.google.gson.annotations.SerializedName; +import com.siimkinks.sqlitemagic.Delete; +import com.siimkinks.sqlitemagic.EventsModelTable; +import com.siimkinks.sqlitemagic.Select; +import com.siimkinks.sqlitemagic.annotation.Column; +import com.siimkinks.sqlitemagic.annotation.Id; +import com.siimkinks.sqlitemagic.annotation.Table; + +import java.util.Date; +import java.util.List; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import rx.Completable; +import rx.Observable; + +/** + * Created by Kosh on 08 Feb 2017, 10:02 PM + */ + +@Getter @Setter @NoArgsConstructor @Table(persistAll = true) +public class EventsModel implements Parcelable { + @Id(autoIncrement = false) @Column long id; + @Column EventsType type; + @Column UserModel actor; + @Column RepoModel repo; + @Column PayloadModel payload; + @Column Date createdAt; + @SerializedName("public") @Column boolean publicEvent; + + public static Completable save(@NonNull List events) { + return Delete.from(EventsModelTable.EVENTS_MODEL) + .observe() + .toCompletable() + .andThen(persist(events).observe()); + } + + @NonNull public static Observable> getEvents() { + return Select.from(EventsModelTable.EVENTS_MODEL) + .orderBy(EventsModelTable.EVENTS_MODEL.CREATED_AT.desc()) + .queryDeep() + .observe() + .runQuery(); + } + + @Override public int describeContents() { return 0; } + + @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(this.id); + dest.writeInt(this.type == null ? -1 : this.type.ordinal()); + dest.writeParcelable(this.actor, flags); + dest.writeParcelable(this.repo, flags); + dest.writeParcelable(this.payload, flags); + dest.writeLong(this.createdAt != null ? this.createdAt.getTime() : -1); + dest.writeByte(this.publicEvent ? (byte) 1 : (byte) 0); + } + + protected EventsModel(Parcel in) { + this.id = in.readLong(); + int tmpType = in.readInt(); + this.type = tmpType == -1 ? null : EventsType.values()[tmpType]; + this.actor = in.readParcelable(UserModel.class.getClassLoader()); + this.repo = in.readParcelable(RepoModel.class.getClassLoader()); + this.payload = in.readParcelable(PayloadModel.class.getClassLoader()); + long tmpCreatedAt = in.readLong(); + this.createdAt = tmpCreatedAt == -1 ? null : new Date(tmpCreatedAt); + this.publicEvent = in.readByte() != 0; + } + + public static final Creator CREATOR = new Creator() { + @Override public EventsModel createFromParcel(Parcel source) {return new EventsModel(source);} + + @Override public EventsModel[] newArray(int size) {return new EventsModel[size];} + }; +} diff --git a/app/src/main/java/com/fastaccess/data/dao/FileModel.java b/app/src/main/java/com/fastaccess/data/dao/FileModel.java new file mode 100644 index 00000000..dc42f318 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/FileModel.java @@ -0,0 +1,71 @@ +package com.fastaccess.data.dao; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; + +import com.siimkinks.sqlitemagic.FileModelTable; +import com.siimkinks.sqlitemagic.Select; +import com.siimkinks.sqlitemagic.annotation.Column; +import com.siimkinks.sqlitemagic.annotation.Table; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import rx.Completable; +import rx.Observable; + +/** + * Created by Kosh on 06 Dec 2016, 10:42 PM + */ + +@Getter @Setter @NoArgsConstructor @Table(persistAll = true) +public class FileModel implements Parcelable { + + @Column boolean isMarkdown; + @Column String content; + @Column String fullUrl; + @Column boolean isRepo; + + public Completable save() { + return this.persist() + .observe() + .toCompletable(); + } + + public static Completable save(@NonNull FileModel model) { + return model.save(); + } + + public static Observable get(@NonNull String url) { + return Select.from(FileModelTable.FILE_MODEL) + .where(FileModelTable.FILE_MODEL.FULL_URL.is(url)) + .takeFirst() + .observe() + .runQueryOnceOrDefault(null); + } + + @Override public int describeContents() { return 0; } + + @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeByte(this.isMarkdown ? (byte) 1 : (byte) 0); + dest.writeString(this.content); + dest.writeString(this.fullUrl); + dest.writeByte(this.isRepo ? (byte) 1 : (byte) 0); + dest.writeLong(this.id); + } + + protected FileModel(Parcel in) { + this.isMarkdown = in.readByte() != 0; + this.content = in.readString(); + this.fullUrl = in.readString(); + this.isRepo = in.readByte() != 0; + this.id = in.readLong(); + } + + public static final Creator CREATOR = new Creator() { + @Override public FileModel createFromParcel(Parcel source) {return new FileModel(source);} + + @Override public FileModel[] newArray(int size) {return new FileModel[size];} + }; +} diff --git a/app/src/main/java/com/fastaccess/data/dao/FilesListModel.java b/app/src/main/java/com/fastaccess/data/dao/FilesListModel.java new file mode 100644 index 00000000..61f02089 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/FilesListModel.java @@ -0,0 +1,62 @@ +package com.fastaccess.data.dao; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.siimkinks.sqlitemagic.annotation.Table; + +import java.io.Serializable; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Created by Kosh on 12 Nov 2016, 11:09 AM + */ + +@Getter @Setter @NoArgsConstructor @Table(persistAll = true) +public class FilesListModel implements Parcelable, Serializable { + + String filename; + String type; + String language; + String rawUrl; + long size; + String content; + boolean needFetching; + + @Override public int describeContents() { return 0; } + + @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.filename); + dest.writeString(this.type); + dest.writeString(this.language); + dest.writeString(this.rawUrl); + dest.writeLong(this.size); + dest.writeString(this.content); + dest.writeByte(this.needFetching ? (byte) 1 : (byte) 0); + dest.writeLong(this.id); + } + + protected FilesListModel(Parcel in) { + this.filename = in.readString(); + this.type = in.readString(); + this.language = in.readString(); + this.rawUrl = in.readString(); + this.size = in.readLong(); + this.content = in.readString(); + this.needFetching = in.readByte() != 0; + this.id = in.readLong(); + } + + public static final Creator CREATOR = new Creator() { + @Override public FilesListModel createFromParcel(Parcel source) {return new FilesListModel(source);} + + @Override public FilesListModel[] newArray(int size) {return new FilesListModel[size];} + }; + + public long getId() { + return id; + } +} diff --git a/app/src/main/java/com/fastaccess/data/dao/FragmentPagerAdapterModel.java b/app/src/main/java/com/fastaccess/data/dao/FragmentPagerAdapterModel.java new file mode 100644 index 00000000..9e569aff --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/FragmentPagerAdapterModel.java @@ -0,0 +1,140 @@ +package com.fastaccess.data.dao; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.v4.app.Fragment; + +import com.annimon.stream.Collectors; +import com.annimon.stream.Stream; +import com.fastaccess.R; +import com.fastaccess.data.dao.types.IssueState; +import com.fastaccess.ui.modules.gists.gist.comments.GistCommentsView; +import com.fastaccess.ui.modules.gists.gist.files.GistFilesListView; +import com.fastaccess.ui.modules.profile.followers.ProfileFollowersView; +import com.fastaccess.ui.modules.profile.following.ProfileFollowingView; +import com.fastaccess.ui.modules.profile.gists.ProfileGistsView; +import com.fastaccess.ui.modules.profile.overview.ProfileOverviewView; +import com.fastaccess.ui.modules.profile.repos.ProfileReposView; +import com.fastaccess.ui.modules.profile.starred.ProfileStarredView; +import com.fastaccess.ui.modules.repos.code.commit.RepoCommitsView; +import com.fastaccess.ui.modules.repos.code.commit.details.comments.CommitCommentsView; +import com.fastaccess.ui.modules.repos.code.commit.details.files.CommitFilesView; +import com.fastaccess.ui.modules.repos.code.contributors.RepoContributorsView; +import com.fastaccess.ui.modules.repos.code.files.paths.RepoFilePathView; +import com.fastaccess.ui.modules.repos.code.prettifier.ViewerView; +import com.fastaccess.ui.modules.repos.code.releases.RepoReleasesView; +import com.fastaccess.ui.modules.repos.issues.issue.RepoIssuesView; +import com.fastaccess.ui.modules.repos.issues.issue.details.comments.IssueCommentsView; +import com.fastaccess.ui.modules.repos.issues.issue.details.events.IssueDetailsView; +import com.fastaccess.ui.modules.repos.pull_requests.pull_request.RepoPullRequestView; +import com.fastaccess.ui.modules.repos.pull_requests.pull_request.details.commits.PullRequestCommitsView; +import com.fastaccess.ui.modules.repos.pull_requests.pull_request.details.events.PullRequestDetailsView; +import com.fastaccess.ui.modules.search.code.SearchCodeView; +import com.fastaccess.ui.modules.search.issues.SearchIssuesView; +import com.fastaccess.ui.modules.search.repos.SearchReposView; +import com.fastaccess.ui.modules.search.users.SearchUsersView; + +import java.util.List; + +import lombok.Getter; +import lombok.Setter; + +/** + * Created by Kosh on 03 Dec 2016, 9:26 AM + */ + +@Getter @Setter +public class FragmentPagerAdapterModel { + + private String title; + private Fragment fragment; + + private FragmentPagerAdapterModel(String title, Fragment fragment) { + this.title = title; + this.fragment = fragment; + } + + @NonNull public static List buildForProfile(@NonNull Context context, @NonNull String login) { + return Stream.of(new FragmentPagerAdapterModel(context.getString(R.string.overview), ProfileOverviewView.newInstance(login)), + new FragmentPagerAdapterModel(context.getString(R.string.repos), ProfileReposView.newInstance(login)), + new FragmentPagerAdapterModel(context.getString(R.string.starred), ProfileStarredView.newInstance(login)), + new FragmentPagerAdapterModel(context.getString(R.string.gists), ProfileGistsView.newInstance(login)), + new FragmentPagerAdapterModel(context.getString(R.string.followers), ProfileFollowersView.newInstance(login)), + new FragmentPagerAdapterModel(context.getString(R.string.following), ProfileFollowingView.newInstance(login))) + .collect(Collectors.toList()); + } + + public static List buildForRepoCode(@NonNull Context context, @NonNull String repoId, + @NonNull String login, @NonNull String url) { + return Stream.of(new FragmentPagerAdapterModel(context.getString(R.string.readme), ViewerView.newInstance(url, true)), + new FragmentPagerAdapterModel(context.getString(R.string.files), RepoFilePathView.newInstance(login, repoId, null)), + new FragmentPagerAdapterModel(context.getString(R.string.commits), RepoCommitsView.newInstance(repoId, login)), + new FragmentPagerAdapterModel(context.getString(R.string.releases), RepoReleasesView.newInstance(repoId, login)), + new FragmentPagerAdapterModel(context.getString(R.string.contributors), RepoContributorsView.newInstance(repoId, login))) + .collect(Collectors.toList()); + } + + @NonNull public static List buildForSearch(@NonNull Context context) { + return Stream.of(new FragmentPagerAdapterModel(context.getString(R.string.repos), SearchReposView.newInstance()), + new FragmentPagerAdapterModel(context.getString(R.string.users), SearchUsersView.newInstance()), + new FragmentPagerAdapterModel(context.getString(R.string.issues), SearchIssuesView.newInstance()), + new FragmentPagerAdapterModel(context.getString(R.string.code), SearchCodeView.newInstance())) + .collect(Collectors.toList()); + } + + @NonNull public static List buildForIssues(@NonNull Context context, @NonNull IssueModel issueModel) { + String login = issueModel.getLogin(); + String repoId = issueModel.getRepoId(); + int number = issueModel.getNumber(); + return Stream.of(new FragmentPagerAdapterModel(context.getString(R.string.details), IssueDetailsView.newInstance(issueModel)), + new FragmentPagerAdapterModel(context.getString(R.string.comments), IssueCommentsView.newInstance(login, repoId, number))) + .collect(Collectors.toList()); + } + + + @NonNull public static List buildForPullRequest(@NonNull Context context, @NonNull PullRequestModel pullRequest) { + String login = pullRequest.getLogin(); + String repoId = pullRequest.getRepoId(); + int number = pullRequest.getNumber(); + return Stream.of(new FragmentPagerAdapterModel(context.getString(R.string.details), PullRequestDetailsView.newInstance(pullRequest)), + new FragmentPagerAdapterModel(context.getString(R.string.commits), PullRequestCommitsView.newInstance(repoId, login, number)), + new FragmentPagerAdapterModel(context.getString(R.string.comments), IssueCommentsView.newInstance(login, repoId, number))) + .collect(Collectors.toList()); + } + + + @NonNull public static List buildForRepoIssue(@NonNull Context context, @NonNull String login, + @NonNull String repoId) { + return Stream.of(new FragmentPagerAdapterModel(context.getString(R.string.opened), + RepoIssuesView.newInstance(repoId, login, IssueState.open)), + new FragmentPagerAdapterModel(context.getString(R.string.closed), + RepoIssuesView.newInstance(repoId, login, IssueState.closed))) + .collect(Collectors.toList()); + } + + @NonNull public static List buildForRepoPullRequest(@NonNull Context context, @NonNull String login, + @NonNull String repoId) { + return Stream.of(new FragmentPagerAdapterModel(context.getString(R.string.opened), + RepoPullRequestView.newInstance(repoId, login, IssueState.open)), + new FragmentPagerAdapterModel(context.getString(R.string.closed), + RepoPullRequestView.newInstance(repoId, login, IssueState.closed))) + .collect(Collectors.toList()); + } + + @NonNull public static List buildForCommit(@NonNull Context context, @NonNull CommitModel commitModel) { + String login = commitModel.getLogin(); + String repoId = commitModel.getRepoId(); + String sha = commitModel.getSha(); + return Stream.of(new FragmentPagerAdapterModel(context.getString(R.string.commits), CommitFilesView.newInstance(commitModel.getFiles())) + , new FragmentPagerAdapterModel(context.getString(R.string.comments), CommitCommentsView.newInstance(login, repoId, sha))) + .collect(Collectors.toList()); + } + + @NonNull public static List buildForGist(@NonNull Context context, @NonNull GistsModel gistsModel) { + + return Stream.of(new FragmentPagerAdapterModel(context.getString(R.string.files), GistFilesListView.newInstance(gistsModel.getFiles())), + new FragmentPagerAdapterModel(context.getString(R.string.comments), GistCommentsView.newInstance(gistsModel.getGistId()))) + .collect(Collectors.toList()); + } + +} diff --git a/app/src/main/java/com/fastaccess/data/dao/GistHubErrorsModel.java b/app/src/main/java/com/fastaccess/data/dao/GistHubErrorsModel.java new file mode 100644 index 00000000..6111e847 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/GistHubErrorsModel.java @@ -0,0 +1,15 @@ +package com.fastaccess.data.dao; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Created by Kosh on 18 Feb 2017, 2:10 PM + */ + +@Getter @Setter @NoArgsConstructor class GistHubErrorsModel { + private String resource; + private String field; + private String code; +} diff --git a/app/src/main/java/com/fastaccess/data/dao/GistsModel.java b/app/src/main/java/com/fastaccess/data/dao/GistsModel.java new file mode 100644 index 00000000..532d8fe0 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/GistsModel.java @@ -0,0 +1,207 @@ +package com.fastaccess.data.dao; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.annimon.stream.LongStream; +import com.annimon.stream.Stream; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.ui.widgets.SpannableBuilder; +import com.google.gson.annotations.SerializedName; +import com.siimkinks.sqlitemagic.Delete; +import com.siimkinks.sqlitemagic.GistsModelTable; +import com.siimkinks.sqlitemagic.Select; +import com.siimkinks.sqlitemagic.annotation.Column; +import com.siimkinks.sqlitemagic.annotation.Id; +import com.siimkinks.sqlitemagic.annotation.Table; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import rx.Completable; +import rx.Observable; + +/** + * Created by Kosh on 09 Feb 2017, 2:45 AM + */ + +@Getter @Setter @NoArgsConstructor @Table(persistAll = true) +public class GistsModel implements Parcelable { + + @SerializedName("nooope") @Id @Column long id; + @Column String url; + @Column String forksUrl; + @Column String commitsUrl; + @SerializedName("id") @Column String gistId; + @Column String gitPullUrl; + @Column String gitPushUrl; + @Column String htmlUrl; + @Column boolean publicX; + @Column Date createdAt; + @Column Date updatedAt; + @Column String description; + @Column int comments; + @Column String commentsUrl; + @Column boolean truncated; + @Column String ownerName; + @Column(onDeleteCascade = true, handleRecursively = false) GithubFileModel files; + @Column(onDeleteCascade = true, handleRecursively = false) UserModel user; + @Column(onDeleteCascade = true, handleRecursively = false) UserModel owner; + + public static Completable save(@NonNull List gists) { + return Delete.from(GistsModelTable.GISTS_MODEL) + .where(GistsModelTable.GISTS_MODEL.OWNER_NAME.isNull()) + .observe() + .toCompletable() + .andThen(persist(gists).observe()); + + } + + public static Completable save(@NonNull List gists, @NonNull String ownerName) { + return Delete.from(GistsModelTable.GISTS_MODEL) + .where(GistsModelTable.GISTS_MODEL.OWNER_NAME.is(ownerName)) + .observe() + .toCompletable() + .andThen(Observable.from(gists) + .map(gistsModel -> { + gistsModel.setOwnerName(ownerName); + return gistsModel.persist().observe(); + })) + .toCompletable(); + } + + @NonNull public static Observable> getMyGists(@NonNull String ownerName) { + return Select.from(GistsModelTable.GISTS_MODEL) + .where(GistsModelTable.GISTS_MODEL.OWNER_NAME.is(ownerName)) + .queryDeep() + .observe() + .runQuery(); + } + + @NonNull public static Observable> getGists() { + return Select.from(GistsModelTable.GISTS_MODEL) + .where(GistsModelTable.GISTS_MODEL.OWNER_NAME.isNull()) + .queryDeep() + .observe() + .runQuery(); + } + + @Nullable public static Observable getGist(@NonNull String gistId) { + return Select.from(GistsModelTable.GISTS_MODEL) + .where(GistsModelTable.GISTS_MODEL.GIST_ID.is(gistId)) + .queryDeep() + .takeFirst() + .observe() + .runQuery(); + } + + @Override public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + GistsModel that = (GistsModel) o; + return url != null ? url.equals(that.url) : that.url == null; + } + + @Override public int hashCode() { + return url != null ? url.hashCode() : 0; + } + + @NonNull public List getFilesAsList() { + List models = new ArrayList<>(); + if (files != null) { + models.addAll(files.values()); + } + return models; + } + + @NonNull public SpannableBuilder getDisplayTitle(boolean isFromProfile) { + SpannableBuilder spannableBuilder = SpannableBuilder.builder(); + if (!isFromProfile) { + if (getOwner() != null) { + spannableBuilder.bold(getOwner().getLogin()); + } else if (getUser() != null) { + spannableBuilder.bold(getUser().getLogin()); + } else { + spannableBuilder.bold("Anonymous"); + } + } + if (!InputHelper.isEmpty(getDescription())) { + if (!InputHelper.isEmpty(spannableBuilder.toString())) { + spannableBuilder.append("/"); + } + spannableBuilder.append(getDescription()); + } + if (InputHelper.isEmpty(spannableBuilder.toString())) { + if (isFromProfile) spannableBuilder.bold("N/A"); + } + return spannableBuilder; + } + + public long getSize() { + List models = getFilesAsList(); + if (!models.isEmpty()) { + return Stream.of(models).flatMapToLong(filesListModel -> LongStream.of(filesListModel.getSize())).sum(); + } + return 0; + } + + @Override public int describeContents() { return 0; } + + @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(this.id); + dest.writeString(this.url); + dest.writeString(this.forksUrl); + dest.writeString(this.commitsUrl); + dest.writeString(this.gistId); + dest.writeString(this.gitPullUrl); + dest.writeString(this.gitPushUrl); + dest.writeString(this.htmlUrl); + dest.writeByte(this.publicX ? (byte) 1 : (byte) 0); + dest.writeLong(this.createdAt != null ? this.createdAt.getTime() : -1); + dest.writeLong(this.updatedAt != null ? this.updatedAt.getTime() : -1); + dest.writeString(this.description); + dest.writeInt(this.comments); + dest.writeString(this.commentsUrl); + dest.writeByte(this.truncated ? (byte) 1 : (byte) 0); + dest.writeString(this.ownerName); + dest.writeSerializable(this.files); + dest.writeParcelable(this.user, flags); + dest.writeParcelable(this.owner, flags); + } + + protected GistsModel(Parcel in) { + this.id = in.readLong(); + this.url = in.readString(); + this.forksUrl = in.readString(); + this.commitsUrl = in.readString(); + this.gistId = in.readString(); + this.gitPullUrl = in.readString(); + this.gitPushUrl = in.readString(); + this.htmlUrl = in.readString(); + this.publicX = in.readByte() != 0; + long tmpCreatedAt = in.readLong(); + this.createdAt = tmpCreatedAt == -1 ? null : new Date(tmpCreatedAt); + long tmpUpdatedAt = in.readLong(); + this.updatedAt = tmpUpdatedAt == -1 ? null : new Date(tmpUpdatedAt); + this.description = in.readString(); + this.comments = in.readInt(); + this.commentsUrl = in.readString(); + this.truncated = in.readByte() != 0; + this.ownerName = in.readString(); + this.files = (GithubFileModel) in.readSerializable(); + this.user = in.readParcelable(UserModel.class.getClassLoader()); + this.owner = in.readParcelable(UserModel.class.getClassLoader()); + } + + public static final Creator CREATOR = new Creator() { + @Override public GistsModel createFromParcel(Parcel source) {return new GistsModel(source);} + + @Override public GistsModel[] newArray(int size) {return new GistsModel[size];} + }; +} diff --git a/app/src/main/java/com/fastaccess/data/dao/GitCommitListModel.java b/app/src/main/java/com/fastaccess/data/dao/GitCommitListModel.java new file mode 100644 index 00000000..99827b7d --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/GitCommitListModel.java @@ -0,0 +1,9 @@ +package com.fastaccess.data.dao; + +import java.util.ArrayList; + +/** + * Created by Kosh on 12 Feb 2017, 12:14 AM + */ + +public class GitCommitListModel extends ArrayList {} diff --git a/app/src/main/java/com/fastaccess/data/dao/GitCommitModel.java b/app/src/main/java/com/fastaccess/data/dao/GitCommitModel.java new file mode 100644 index 00000000..47ca9cc3 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/GitCommitModel.java @@ -0,0 +1,62 @@ +package com.fastaccess.data.dao; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.google.gson.annotations.SerializedName; +import com.siimkinks.sqlitemagic.annotation.Column; +import com.siimkinks.sqlitemagic.annotation.Table; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Created by Kosh on 08 Dec 2016, 8:59 PM + */ + +@Getter @Setter @NoArgsConstructor @Table(persistAll = true) +public class GitCommitModel implements Parcelable { + @Column String sha; + @Column String url; + @Column String message; + @Column UserModel author; + @Column UserModel committer; + @Column UserModel tree; + @Column @SerializedName("distinct") boolean distincted; + @Column GitCommitListModel parents; + @Column int commentCount; + + @Override public int describeContents() { return 0; } + + @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.sha); + dest.writeString(this.url); + dest.writeString(this.message); + dest.writeParcelable(this.author, flags); + dest.writeParcelable(this.committer, flags); + dest.writeParcelable(this.tree, flags); + dest.writeByte(this.distincted ? (byte) 1 : (byte) 0); + dest.writeList(this.parents); + dest.writeInt(this.commentCount); + } + + protected GitCommitModel(Parcel in) { + this.sha = in.readString(); + this.url = in.readString(); + this.message = in.readString(); + this.author = in.readParcelable(UserModel.class.getClassLoader()); + this.committer = in.readParcelable(UserModel.class.getClassLoader()); + this.tree = in.readParcelable(UserModel.class.getClassLoader()); + this.distincted = in.readByte() != 0; + in.readList(parents, parents.getClass().getClassLoader()); + in.readList(this.parents, GitCommitModel.class.getClassLoader()); + this.commentCount = in.readInt(); + } + + public static final Creator CREATOR = new Creator() { + @Override public GitCommitModel createFromParcel(Parcel source) {return new GitCommitModel(source);} + + @Override public GitCommitModel[] newArray(int size) {return new GitCommitModel[size];} + }; +} diff --git a/app/src/main/java/com/fastaccess/data/dao/GitHubErrorResponse.java b/app/src/main/java/com/fastaccess/data/dao/GitHubErrorResponse.java new file mode 100644 index 00000000..a11bcf31 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/GitHubErrorResponse.java @@ -0,0 +1,19 @@ +package com.fastaccess.data.dao; + +import java.util.List; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Created by Kosh on 18 Feb 2017, 2:09 PM + */ + +@Getter @Setter @NoArgsConstructor +public class GitHubErrorResponse { + private String message; + private String documentation_url; + private List errors; + +} diff --git a/app/src/main/java/com/fastaccess/data/dao/GithubFileModel.java b/app/src/main/java/com/fastaccess/data/dao/GithubFileModel.java new file mode 100644 index 00000000..b3d5a06f --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/GithubFileModel.java @@ -0,0 +1,11 @@ +package com.fastaccess.data.dao; + +import java.io.Serializable; +import java.util.HashMap; + +/** + * Created by Kosh on 10 Feb 2017, 9:46 PM + */ + + +public class GithubFileModel extends HashMap implements Serializable {} \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/data/dao/GithubState.java b/app/src/main/java/com/fastaccess/data/dao/GithubState.java new file mode 100644 index 00000000..2cdb93fa --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/GithubState.java @@ -0,0 +1,42 @@ +package com.fastaccess.data.dao; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.siimkinks.sqlitemagic.annotation.Column; +import com.siimkinks.sqlitemagic.annotation.Table; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Created by Kosh on 08 Dec 2016, 8:57 PM + */ + +@Getter @Setter @NoArgsConstructor @Table(persistAll = true) +public class GithubState implements Parcelable { + @Column int additions; + @Column int deletions; + @Column int total; + + @Override public int describeContents() { return 0; } + + @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(this.additions); + dest.writeInt(this.deletions); + dest.writeInt(this.total); + } + + protected GithubState(Parcel in) { + this.additions = in.readInt(); + this.deletions = in.readInt(); + this.total = in.readInt(); + } + + public static final Creator CREATOR = new Creator() { + @Override public GithubState createFromParcel(Parcel source) {return new GithubState(source);} + + @Override public GithubState[] newArray(int size) {return new GithubState[size];} + }; +} diff --git a/app/src/main/java/com/fastaccess/data/dao/IssueEventAdapterModel.java b/app/src/main/java/com/fastaccess/data/dao/IssueEventAdapterModel.java new file mode 100644 index 00000000..aece3ebd --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/IssueEventAdapterModel.java @@ -0,0 +1,67 @@ +package com.fastaccess.data.dao; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.Nullable; + +import com.annimon.stream.Stream; + +import java.util.ArrayList; +import java.util.List; + +import lombok.Getter; +import lombok.Setter; + +/** + * Created by Kosh on 10 Dec 2016, 3:34 PM + */ + +@Getter @Setter +public class IssueEventAdapterModel implements Parcelable { + + public static final int HEADER = 1; + public static final int ROW = 2; + private int type; + + private IssueEventModel issueEvent; + private IssueModel issueModel; + + private IssueEventAdapterModel(int type, IssueEventModel model) { + this.type = type; + this.issueEvent = model; + } + + public IssueEventAdapterModel(int type, IssueModel issueModel) { + this.type = type; + this.issueModel = issueModel; + } + + public static ArrayList addEvents(@Nullable List modelList) { + ArrayList models = new ArrayList<>(); + if (modelList == null || modelList.isEmpty()) return models; + Stream.of(modelList).forEach(issueEventModel -> models.add(new IssueEventAdapterModel(ROW, issueEventModel))); + return models; + } + + @Override public int describeContents() { return 0; } + + @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(this.type); + dest.writeParcelable(this.issueEvent, flags); + dest.writeParcelable(this.issueModel, flags); + } + + public IssueEventAdapterModel() {} + + @SuppressWarnings("WeakerAccess") protected IssueEventAdapterModel(Parcel in) { + this.type = in.readInt(); + this.issueEvent = in.readParcelable(IssueEventModel.class.getClassLoader()); + this.issueModel = in.readParcelable(IssueModel.class.getClassLoader()); + } + + public static final Creator CREATOR = new Creator() { + @Override public IssueEventAdapterModel createFromParcel(Parcel source) {return new IssueEventAdapterModel(source);} + + @Override public IssueEventAdapterModel[] newArray(int size) {return new IssueEventAdapterModel[size];} + }; +} diff --git a/app/src/main/java/com/fastaccess/data/dao/IssueEventModel.java b/app/src/main/java/com/fastaccess/data/dao/IssueEventModel.java new file mode 100644 index 00000000..9dc9a92c --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/IssueEventModel.java @@ -0,0 +1,79 @@ +package com.fastaccess.data.dao; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.fastaccess.data.dao.types.IssueEventType; +import com.siimkinks.sqlitemagic.annotation.Column; +import com.siimkinks.sqlitemagic.annotation.Id; +import com.siimkinks.sqlitemagic.annotation.Table; + +import java.util.Date; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Created by Kosh on 10 Dec 2016, 3:34 PM + */ + +@Getter @Setter @NoArgsConstructor @Table(persistAll = true) +public class IssueEventModel implements Parcelable { + + @Column @Id(autoIncrement = false) long id; + @Column String url; + @Column UserModel actor; + @Column UserModel assigner; + @Column UserModel assignee; + @Column IssueEventType event; + @Column MilestoneModel milestone; + @Column RenameModel rename; + @Column IssueModel source; + @Column LabelModel label; + @Column String commitId; + @Column String commitUrl; + @Column Date createdAt; + + @Override public int describeContents() { return 0; } + + @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(this.id); + dest.writeString(this.url); + dest.writeParcelable(this.actor, flags); + dest.writeParcelable(this.assigner, flags); + dest.writeParcelable(this.assignee, flags); + dest.writeInt(this.event == null ? -1 : this.event.ordinal()); + dest.writeParcelable(this.milestone, flags); + dest.writeParcelable(this.rename, flags); + dest.writeParcelable(this.source, flags); + dest.writeParcelable(this.label, flags); + dest.writeString(this.commitId); + dest.writeString(this.commitUrl); + dest.writeLong(this.createdAt != null ? this.createdAt.getTime() : -1); + } + + protected IssueEventModel(Parcel in) { + this.id = in.readLong(); + this.url = in.readString(); + this.actor = in.readParcelable(UserModel.class.getClassLoader()); + this.assigner = in.readParcelable(UserModel.class.getClassLoader()); + this.assignee = in.readParcelable(UserModel.class.getClassLoader()); + int tmpEvent = in.readInt(); + this.event = tmpEvent == -1 ? null : IssueEventType.values()[tmpEvent]; + this.milestone = in.readParcelable(MilestoneModel.class.getClassLoader()); + this.rename = in.readParcelable(RenameModel.class.getClassLoader()); + this.source = in.readParcelable(IssueModel.class.getClassLoader()); + this.label = in.readParcelable(LabelModel.class.getClassLoader()); + this.commitId = in.readString(); + this.commitUrl = in.readString(); + long tmpCreatedAt = in.readLong(); + this.createdAt = tmpCreatedAt == -1 ? null : new Date(tmpCreatedAt); + } + + public static final Creator CREATOR = new Creator() { + @Override public IssueEventModel createFromParcel(Parcel source) {return new IssueEventModel(source);} + + @Override public IssueEventModel[] newArray(int size) {return new IssueEventModel[size];} + }; +} diff --git a/app/src/main/java/com/fastaccess/data/dao/IssueModel.java b/app/src/main/java/com/fastaccess/data/dao/IssueModel.java new file mode 100644 index 00000000..ab458ec3 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/IssueModel.java @@ -0,0 +1,170 @@ +package com.fastaccess.data.dao; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; + +import com.fastaccess.data.dao.types.IssueState; +import com.siimkinks.sqlitemagic.Delete; +import com.siimkinks.sqlitemagic.IssueModelTable; +import com.siimkinks.sqlitemagic.Select; +import com.siimkinks.sqlitemagic.annotation.Column; +import com.siimkinks.sqlitemagic.annotation.Id; +import com.siimkinks.sqlitemagic.annotation.Table; + +import java.util.Date; +import java.util.List; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import rx.Completable; +import rx.Observable; + +/** + * Created by Kosh on 08 Dec 2016, 9:02 PM + */ + +@Getter @Setter @NoArgsConstructor @Table(persistAll = true) +public class IssueModel implements Parcelable { + @Column String url; + @Column String body; + @Column String title; + @Column @Id(autoIncrement = false) long id; + @Column int comments; + @Column int number; + @Column boolean locked; + @Column IssueState state; + @Column UserModel user; + @Column UserModel assignee; + @Column UsersListModel assignees; + @Column LabelListModel labels; + @Column MilestoneModel milestone; + @Column(handleRecursively = false) RepoModel repository; + @Column String repoUrl; + @Column String bodyHtml; + @Column String htmlUrl; + @Column(handleRecursively = false) PullRequestModel pullRequest; + @Column Date closedAt; + @Column Date createdAt; + @Column Date updatedAt; + @Column UserModel closedBy; + @Column String repoId; + @Column String login; + + public Completable save() { + return persist().observe().toCompletable(); + } + + public static Completable save(@NonNull List models, @NonNull String repoId, @NonNull String login) { + return Delete.from(IssueModelTable.ISSUE_MODEL) + .where(IssueModelTable.ISSUE_MODEL.REPO_ID.is(repoId)) + .observe() + .toCompletable() + .andThen(Observable.from(models) + .map(issueModel -> { + issueModel.setRepoId(repoId); + issueModel.setLogin(login); + return issueModel.save(); + })) + .toCompletable(); + } + + public static Observable> getIssues(@NonNull String repoId, @NonNull String login, @NonNull IssueState issueState) { + return Select.from(IssueModelTable.ISSUE_MODEL) + .where(IssueModelTable.ISSUE_MODEL.REPO_ID.is(repoId) + .and(IssueModelTable.ISSUE_MODEL.LOGIN.is(login)) + .and(IssueModelTable.ISSUE_MODEL.STATE.is(issueState))) + .orderBy(IssueModelTable.ISSUE_MODEL.UPDATED_AT.desc()) + .queryDeep() + .observe() + .runQuery(); + } + + public static Observable getIssue(long id) { + return Select.from(IssueModelTable.ISSUE_MODEL) + .where(IssueModelTable.ISSUE_MODEL.ID.is(id)) + .queryDeep() + .takeFirst() + .observe() + .runQuery(); + } + + public static Observable getIssueByNumber(int number) { + return Select.from(IssueModelTable.ISSUE_MODEL) + .where(IssueModelTable.ISSUE_MODEL.NUMBER.is(number)) + .queryDeep() + .takeFirst() + .observe() + .runQuery(); + } + + @Override public int describeContents() { return 0; } + + @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.url); + dest.writeString(this.body); + dest.writeString(this.title); + dest.writeLong(this.id); + dest.writeInt(this.comments); + dest.writeInt(this.number); + dest.writeByte(this.locked ? (byte) 1 : (byte) 0); + dest.writeInt(this.state == null ? -1 : this.state.ordinal()); + dest.writeParcelable(this.user, flags); + dest.writeParcelable(this.assignee, flags); + dest.writeList(this.assignees); + dest.writeList(this.labels); + dest.writeParcelable(this.milestone, flags); + dest.writeParcelable(this.repository, flags); + dest.writeString(this.repoUrl); + dest.writeString(this.bodyHtml); + dest.writeString(this.htmlUrl); + dest.writeParcelable(this.pullRequest, flags); + dest.writeLong(this.closedAt != null ? this.closedAt.getTime() : -1); + dest.writeLong(this.createdAt != null ? this.createdAt.getTime() : -1); + dest.writeLong(this.updatedAt != null ? this.updatedAt.getTime() : -1); + dest.writeParcelable(this.closedBy, flags); + dest.writeString(this.repoId); + dest.writeString(this.login); + } + + protected IssueModel(Parcel in) { + this.url = in.readString(); + this.body = in.readString(); + this.title = in.readString(); + this.id = in.readLong(); + this.comments = in.readInt(); + this.number = in.readInt(); + this.locked = in.readByte() != 0; + int tmpState = in.readInt(); + this.state = tmpState == -1 ? null : IssueState.values()[tmpState]; + this.user = in.readParcelable(UserModel.class.getClassLoader()); + this.assignee = in.readParcelable(UserModel.class.getClassLoader()); + this.assignees = new UsersListModel(); + in.readList(this.assignees, this.assignee.getClass().getClassLoader()); + this.labels = new LabelListModel(); + in.readList(this.labels, this.labels.getClass().getClassLoader()); + this.milestone = in.readParcelable(MilestoneModel.class.getClassLoader()); + this.repository = in.readParcelable(RepoModel.class.getClassLoader()); + this.repoUrl = in.readString(); + this.bodyHtml = in.readString(); + this.htmlUrl = in.readString(); + this.pullRequest = in.readParcelable(PullRequestModel.class.getClassLoader()); + long tmpClosedAt = in.readLong(); + this.closedAt = tmpClosedAt == -1 ? null : new Date(tmpClosedAt); + long tmpCreatedAt = in.readLong(); + this.createdAt = tmpCreatedAt == -1 ? null : new Date(tmpCreatedAt); + long tmpUpdatedAt = in.readLong(); + this.updatedAt = tmpUpdatedAt == -1 ? null : new Date(tmpUpdatedAt); + this.closedBy = in.readParcelable(UserModel.class.getClassLoader()); + this.repoId = in.readString(); + this.login = in.readString(); + } + + public static final Creator CREATOR = new Creator() { + @Override public IssueModel createFromParcel(Parcel source) {return new IssueModel(source);} + + @Override public IssueModel[] newArray(int size) {return new IssueModel[size];} + }; + +} diff --git a/app/src/main/java/com/fastaccess/data/dao/IssueRequestModel.java b/app/src/main/java/com/fastaccess/data/dao/IssueRequestModel.java new file mode 100644 index 00000000..6983fa6e --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/IssueRequestModel.java @@ -0,0 +1,73 @@ +package com.fastaccess.data.dao; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; + +import com.annimon.stream.Collectors; +import com.annimon.stream.Stream; +import com.fastaccess.data.dao.types.IssueState; + +import java.util.List; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Created by Kosh on 10 Dec 2016, 8:53 AM + */ + +@Getter @Setter @NoArgsConstructor +public class IssueRequestModel implements Parcelable { + + + private IssueState state; + private String title; + private String body; + private int milestone; + private String assignee; + private List labels; + + + public static IssueRequestModel clone(@NonNull IssueModel issue) { + IssueRequestModel model = new IssueRequestModel(); + if (issue.getLabels() != null) { + model.setLabels(Stream.of(issue.getLabels()).filter(value -> value.getName() != null) + .map(LabelModel::getName).collect(Collectors.toList())); + } + model.setAssignee(issue.getAssignee() != null ? issue.getAssignee().getLogin() : null); + model.setBody(issue.getBody()); + model.setMilestone(issue.getMilestone() != null ? issue.getMilestone().getNumber() : 0); + model.setState(issue.getState() == IssueState.closed ? IssueState.open : IssueState.closed); + model.setTitle(issue.getTitle()); + return model; + } + + @Override public int describeContents() { return 0; } + + @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(this.state == null ? -1 : this.state.ordinal()); + dest.writeString(this.title); + dest.writeString(this.body); + dest.writeInt(this.milestone); + dest.writeString(this.assignee); + dest.writeStringList(this.labels); + } + + @SuppressWarnings("WeakerAccess") protected IssueRequestModel(Parcel in) { + int tmpState = in.readInt(); + this.state = tmpState == -1 ? null : IssueState.values()[tmpState]; + this.title = in.readString(); + this.body = in.readString(); + this.milestone = in.readInt(); + this.assignee = in.readString(); + this.labels = in.createStringArrayList(); + } + + public static final Creator CREATOR = new Creator() { + @Override public IssueRequestModel createFromParcel(Parcel source) {return new IssueRequestModel(source);} + + @Override public IssueRequestModel[] newArray(int size) {return new IssueRequestModel[size];} + }; +} diff --git a/app/src/main/java/com/fastaccess/data/dao/LabelListModel.java b/app/src/main/java/com/fastaccess/data/dao/LabelListModel.java new file mode 100644 index 00000000..77ee5a82 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/LabelListModel.java @@ -0,0 +1,9 @@ +package com.fastaccess.data.dao; + +import java.util.ArrayList; + +/** + * Created by Kosh on 12 Feb 2017, 1:32 PM + */ + +public class LabelListModel extends ArrayList {} diff --git a/app/src/main/java/com/fastaccess/data/dao/LabelModel.java b/app/src/main/java/com/fastaccess/data/dao/LabelModel.java new file mode 100644 index 00000000..ac74f84c --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/LabelModel.java @@ -0,0 +1,42 @@ +package com.fastaccess.data.dao; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.siimkinks.sqlitemagic.annotation.Column; +import com.siimkinks.sqlitemagic.annotation.Table; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Created by Kosh on 08 Dec 2016, 9:05 PM + */ + +@Getter @Setter @NoArgsConstructor @Table(persistAll = true) +public class LabelModel implements Parcelable { + @Column String url; + @Column String name; + @Column String color; + + @Override public int describeContents() { return 0; } + + @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.url); + dest.writeString(this.name); + dest.writeString(this.color); + } + + protected LabelModel(Parcel in) { + this.url = in.readString(); + this.name = in.readString(); + this.color = in.readString(); + } + + public static final Creator CREATOR = new Creator() { + @Override public LabelModel createFromParcel(Parcel source) {return new LabelModel(source);} + + @Override public LabelModel[] newArray(int size) {return new LabelModel[size];} + }; +} diff --git a/app/src/main/java/com/fastaccess/data/dao/LicenseModel.java b/app/src/main/java/com/fastaccess/data/dao/LicenseModel.java new file mode 100644 index 00000000..da92d393 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/LicenseModel.java @@ -0,0 +1,52 @@ +package com.fastaccess.data.dao; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.siimkinks.sqlitemagic.annotation.Column; +import com.siimkinks.sqlitemagic.annotation.Id; +import com.siimkinks.sqlitemagic.annotation.Table; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Created by Kosh on 01 Jan 2017, 1:15 PM + */ + +@Getter @Setter @NoArgsConstructor @Table(persistAll = true) +public class LicenseModel implements Parcelable { + @Id @Column long id; + @Column String key; + @Column String name; + @Column String spdxId; + @Column String url; + @Column boolean featured; + + @Override public int describeContents() { return 0; } + + @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(this.id); + dest.writeString(this.key); + dest.writeString(this.name); + dest.writeString(this.spdxId); + dest.writeString(this.url); + dest.writeByte(this.featured ? (byte) 1 : (byte) 0); + } + + protected LicenseModel(Parcel in) { + this.id = in.readLong(); + this.key = in.readString(); + this.name = in.readString(); + this.spdxId = in.readString(); + this.url = in.readString(); + this.featured = in.readByte() != 0; + } + + public static final Creator CREATOR = new Creator() { + @Override public LicenseModel createFromParcel(Parcel source) {return new LicenseModel(source);} + + @Override public LicenseModel[] newArray(int size) {return new LicenseModel[size];} + }; +} diff --git a/app/src/main/java/com/fastaccess/data/dao/LoginModel.java b/app/src/main/java/com/fastaccess/data/dao/LoginModel.java new file mode 100644 index 00000000..cc2c45d9 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/LoginModel.java @@ -0,0 +1,163 @@ +package com.fastaccess.data.dao; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.siimkinks.sqlitemagic.LoginModelTable; +import com.siimkinks.sqlitemagic.Select; +import com.siimkinks.sqlitemagic.annotation.Column; +import com.siimkinks.sqlitemagic.annotation.Id; +import com.siimkinks.sqlitemagic.annotation.Table; +import com.siimkinks.sqlitemagic.annotation.Unique; + +import java.util.Date; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + + +/** + * Created by Kosh on 08 Feb 2017, 8:51 PM + */ + +@Getter @Setter @NoArgsConstructor @Table(persistAll = true) +public class LoginModel implements Parcelable { + + @Id(autoIncrement = false) @Column long id; + @Column @Unique String login; + @Column String avatarUrl; + @Column String gravatarId; + @Column String url; + @Column String htmlUrl; + @Column String followersUrl; + @Column String followingUrl; + @Column String gistsUrl; + @Column String starredUrl; + @Column String subscriptionsUrl; + @Column String organizationsUrl; + @Column String reposUrl; + @Column String eventsUrl; + @Column String receivedEventsUrl; + @Column String type; + @Column boolean siteAdmin; + @Column String name; + @Column String company; + @Column String blog; + @Column String location; + @Column String email; + @Column boolean hireable; + @Column String bio; + @Column long publicRepos; + @Column long publicGists; + @Column long followers; + @Column long following; + @Column Date createdAt; + @Column Date updatedAt; + @Column String token; + @Column int contributions; + + public void save() { + this.persist().execute(); + } + + public static LoginModel getUser() { + return Select.from(LoginModelTable.LOGIN_MODEL) + .where(LoginModelTable.LOGIN_MODEL.LOGIN.isNotNull() + .and(LoginModelTable.LOGIN_MODEL.TOKEN.isNotNull())) + .takeFirst() + .execute(); + } + + public static LoginModel getUser(String login) { + return Select.from(LoginModelTable.LOGIN_MODEL) + .where(LoginModelTable.LOGIN_MODEL.LOGIN.is(login) + .and(LoginModelTable.LOGIN_MODEL.TOKEN.isNotNull())) + .takeFirst() + .execute(); + } + + @Override public int describeContents() { return 0; } + + @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(this.id); + dest.writeString(this.login); + dest.writeString(this.avatarUrl); + dest.writeString(this.gravatarId); + dest.writeString(this.url); + dest.writeString(this.htmlUrl); + dest.writeString(this.followersUrl); + dest.writeString(this.followingUrl); + dest.writeString(this.gistsUrl); + dest.writeString(this.starredUrl); + dest.writeString(this.subscriptionsUrl); + dest.writeString(this.organizationsUrl); + dest.writeString(this.reposUrl); + dest.writeString(this.eventsUrl); + dest.writeString(this.receivedEventsUrl); + dest.writeString(this.type); + dest.writeByte(this.siteAdmin ? (byte) 1 : (byte) 0); + dest.writeString(this.name); + dest.writeString(this.company); + dest.writeString(this.blog); + dest.writeString(this.location); + dest.writeString(this.email); + dest.writeByte(this.hireable ? (byte) 1 : (byte) 0); + dest.writeString(this.bio); + dest.writeLong(this.publicRepos); + dest.writeLong(this.publicGists); + dest.writeLong(this.followers); + dest.writeLong(this.following); + dest.writeLong(this.createdAt != null ? this.createdAt.getTime() : -1); + dest.writeLong(this.updatedAt != null ? this.updatedAt.getTime() : -1); + dest.writeString(this.token); + dest.writeInt(this.contributions); + } + + protected LoginModel(Parcel in) { + this.id = in.readLong(); + this.login = in.readString(); + this.avatarUrl = in.readString(); + this.gravatarId = in.readString(); + this.url = in.readString(); + this.htmlUrl = in.readString(); + this.followersUrl = in.readString(); + this.followingUrl = in.readString(); + this.gistsUrl = in.readString(); + this.starredUrl = in.readString(); + this.subscriptionsUrl = in.readString(); + this.organizationsUrl = in.readString(); + this.reposUrl = in.readString(); + this.eventsUrl = in.readString(); + this.receivedEventsUrl = in.readString(); + this.type = in.readString(); + this.siteAdmin = in.readByte() != 0; + this.name = in.readString(); + this.company = in.readString(); + this.blog = in.readString(); + this.location = in.readString(); + this.email = in.readString(); + this.hireable = in.readByte() != 0; + this.bio = in.readString(); + this.publicRepos = in.readLong(); + this.publicGists = in.readLong(); + this.followers = in.readLong(); + this.following = in.readLong(); + long tmpCreatedAt = in.readLong(); + this.createdAt = tmpCreatedAt == -1 ? null : new Date(tmpCreatedAt); + long tmpUpdatedAt = in.readLong(); + this.updatedAt = tmpUpdatedAt == -1 ? null : new Date(tmpUpdatedAt); + this.token = in.readString(); + this.contributions = in.readInt(); + } + + public static final Creator CREATOR = new Creator() { + @Override public LoginModel createFromParcel(Parcel source) {return new LoginModel(source);} + + @Override public LoginModel[] newArray(int size) {return new LoginModel[size];} + }; + + @Override public String toString() { + return login; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/data/dao/MarkdownModel.java b/app/src/main/java/com/fastaccess/data/dao/MarkdownModel.java new file mode 100644 index 00000000..5c57cc28 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/MarkdownModel.java @@ -0,0 +1,39 @@ +package com.fastaccess.data.dao; + +import android.os.Parcel; +import android.os.Parcelable; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Created by Kosh on 18 Feb 2017, 7:20 PM + */ + +@Getter @Setter @NoArgsConstructor +public class MarkdownModel implements Parcelable { + private String text; + private String mode = "gfm"; + private String context = "markdown"; + + @Override public int describeContents() { return 0; } + + @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.text); + dest.writeString(this.mode); + dest.writeString(this.context); + } + + @SuppressWarnings("WeakerAccess") protected MarkdownModel(Parcel in) { + this.text = in.readString(); + this.mode = in.readString(); + this.context = in.readString(); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override public MarkdownModel createFromParcel(Parcel source) {return new MarkdownModel(source);} + + @Override public MarkdownModel[] newArray(int size) {return new MarkdownModel[size];} + }; +} diff --git a/app/src/main/java/com/fastaccess/data/dao/MergeRequestModel.java b/app/src/main/java/com/fastaccess/data/dao/MergeRequestModel.java new file mode 100644 index 00000000..fc3a5bf9 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/MergeRequestModel.java @@ -0,0 +1,43 @@ +package com.fastaccess.data.dao; + +import android.os.Parcel; +import android.os.Parcelable; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Created by Kosh on 16 Dec 2016, 11:42 PM + */ + +@Getter @Setter @NoArgsConstructor +public class MergeRequestModel implements Parcelable { + + private String commitMessage; + private String sha; + private String base; + private String head; + + @Override public int describeContents() { return 0; } + + @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.commitMessage); + dest.writeString(this.sha); + dest.writeString(this.base); + dest.writeString(this.head); + } + + @SuppressWarnings("WeakerAccess") protected MergeRequestModel(Parcel in) { + this.commitMessage = in.readString(); + this.sha = in.readString(); + this.base = in.readString(); + this.head = in.readString(); + } + + public static final Creator CREATOR = new Creator() { + @Override public MergeRequestModel createFromParcel(Parcel source) {return new MergeRequestModel(source);} + + @Override public MergeRequestModel[] newArray(int size) {return new MergeRequestModel[size];} + }; +} diff --git a/app/src/main/java/com/fastaccess/data/dao/MergeResponseModel.java b/app/src/main/java/com/fastaccess/data/dao/MergeResponseModel.java new file mode 100644 index 00000000..2c20c7b5 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/MergeResponseModel.java @@ -0,0 +1,43 @@ +package com.fastaccess.data.dao; + +import android.os.Parcel; +import android.os.Parcelable; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Created by Kosh on 16 Dec 2016, 11:40 PM + */ + +@Getter @Setter @NoArgsConstructor +public class MergeResponseModel implements Parcelable { + + private String sha; + private boolean merged; + private String message; + private String documentationUrl; + + @Override public int describeContents() { return 0; } + + @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.sha); + dest.writeByte(this.merged ? (byte) 1 : (byte) 0); + dest.writeString(this.message); + dest.writeString(this.documentationUrl); + } + + @SuppressWarnings("WeakerAccess") protected MergeResponseModel(Parcel in) { + this.sha = in.readString(); + this.merged = in.readByte() != 0; + this.message = in.readString(); + this.documentationUrl = in.readString(); + } + + public static final Creator CREATOR = new Creator() { + @Override public MergeResponseModel createFromParcel(Parcel source) {return new MergeResponseModel(source);} + + @Override public MergeResponseModel[] newArray(int size) {return new MergeResponseModel[size];} + }; +} diff --git a/app/src/main/java/com/fastaccess/data/dao/MilestoneModel.java b/app/src/main/java/com/fastaccess/data/dao/MilestoneModel.java new file mode 100644 index 00000000..1bc07b42 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/MilestoneModel.java @@ -0,0 +1,83 @@ +package com.fastaccess.data.dao; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.siimkinks.sqlitemagic.annotation.Column; +import com.siimkinks.sqlitemagic.annotation.Id; +import com.siimkinks.sqlitemagic.annotation.Table; + +import java.util.Date; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Created by Kosh on 08 Dec 2016, 8:47 PM + */ + +@Getter @Setter @NoArgsConstructor @Table(persistAll = true) +public class MilestoneModel implements Parcelable { + + @Column String url; + @Column String title; + @Column String state; + @Column String description; + @Column @Id(autoIncrement = false) long id; + @Column int number; + @Column UserModel creator; + @Column String htmlUr; + @Column int openIssues; + @Column int closedIssues; + @Column Date createdAt; + @Column Date updatedAt; + @Column Date closedAt; + @Column Date dueOn; + + @Override public int describeContents() { return 0; } + + @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.url); + dest.writeString(this.title); + dest.writeString(this.state); + dest.writeString(this.description); + dest.writeLong(this.id); + dest.writeInt(this.number); + dest.writeParcelable(this.creator, flags); + dest.writeString(this.htmlUr); + dest.writeInt(this.openIssues); + dest.writeInt(this.closedIssues); + dest.writeLong(this.createdAt != null ? this.createdAt.getTime() : -1); + dest.writeLong(this.updatedAt != null ? this.updatedAt.getTime() : -1); + dest.writeLong(this.closedAt != null ? this.closedAt.getTime() : -1); + dest.writeLong(this.dueOn != null ? this.dueOn.getTime() : -1); + } + + protected MilestoneModel(Parcel in) { + this.url = in.readString(); + this.title = in.readString(); + this.state = in.readString(); + this.description = in.readString(); + this.id = in.readLong(); + this.number = in.readInt(); + this.creator = in.readParcelable(UserModel.class.getClassLoader()); + this.htmlUr = in.readString(); + this.openIssues = in.readInt(); + this.closedIssues = in.readInt(); + long tmpCreatedAt = in.readLong(); + this.createdAt = tmpCreatedAt == -1 ? null : new Date(tmpCreatedAt); + long tmpUpdatedAt = in.readLong(); + this.updatedAt = tmpUpdatedAt == -1 ? null : new Date(tmpUpdatedAt); + long tmpClosedAt = in.readLong(); + this.closedAt = tmpClosedAt == -1 ? null : new Date(tmpClosedAt); + long tmpDueOn = in.readLong(); + this.dueOn = tmpDueOn == -1 ? null : new Date(tmpDueOn); + } + + public static final Creator CREATOR = new Creator() { + @Override public MilestoneModel createFromParcel(Parcel source) {return new MilestoneModel(source);} + + @Override public MilestoneModel[] newArray(int size) {return new MilestoneModel[size];} + }; +} diff --git a/app/src/main/java/com/fastaccess/data/dao/NameParser.java b/app/src/main/java/com/fastaccess/data/dao/NameParser.java new file mode 100644 index 00000000..b800d91c --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/NameParser.java @@ -0,0 +1,41 @@ +package com.fastaccess.data.dao; + +import android.net.Uri; +import android.support.annotation.Nullable; + +import com.fastaccess.helper.InputHelper; + +import java.util.List; + +import lombok.Getter; +import lombok.Setter; + +/** + * Created by Kosh on 11 Feb 2017, 11:03 PM + */ + +@Getter @Setter +public class NameParser { + + private String name; + private String username; + + public NameParser(@Nullable String url) { + if (!InputHelper.isEmpty(url)) { + Uri uri = Uri.parse(url); + List segments = uri.getPathSegments(); + if (segments == null || segments.size() < 2) { + return; + } + this.name = segments.get(1); + this.username = segments.get(0); + } + } + + @Override public String toString() { + return "NameParser{" + + "name='" + name + '\'' + + ", username='" + username + '\'' + + '}'; + } +} diff --git a/app/src/main/java/com/fastaccess/data/dao/Pageable.java b/app/src/main/java/com/fastaccess/data/dao/Pageable.java new file mode 100644 index 00000000..d84a3a6d --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/Pageable.java @@ -0,0 +1,55 @@ +package com.fastaccess.data.dao; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.List; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Created by Kosh on 15 Nov 2016, 7:04 PM + */ + + +@Getter @Setter @NoArgsConstructor +public class Pageable implements Parcelable { + + private int first; + private int next; + private int prev; + private int last; + private int totalCount; + private boolean incompleteResults; + private List items; + + @Override public int describeContents() { return 0; } + + @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(this.first); + dest.writeInt(this.next); + dest.writeInt(this.prev); + dest.writeInt(this.last); + dest.writeInt(this.totalCount); + dest.writeByte(this.incompleteResults ? (byte) 1 : (byte) 0); + dest.writeTypedList(this.items); + } + + @SuppressWarnings("WeakerAccess") protected Pageable(Parcel in) { + this.first = in.readInt(); + this.next = in.readInt(); + this.prev = in.readInt(); + this.last = in.readInt(); + this.totalCount = in.readInt(); + this.incompleteResults = in.readByte() != 0; + in.readList(this.items, this.items.getClass().getClassLoader()); + } + + public static final Creator CREATOR = new Creator() { + @Override public Pageable createFromParcel(Parcel source) {return new Pageable(source);} + + @Override public Pageable[] newArray(int size) {return new Pageable[size];} + }; +} diff --git a/app/src/main/java/com/fastaccess/data/dao/PayloadModel.java b/app/src/main/java/com/fastaccess/data/dao/PayloadModel.java new file mode 100644 index 00000000..e12d554d --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/PayloadModel.java @@ -0,0 +1,46 @@ +package com.fastaccess.data.dao; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.siimkinks.sqlitemagic.annotation.Column; +import com.siimkinks.sqlitemagic.annotation.Id; +import com.siimkinks.sqlitemagic.annotation.Table; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + + +/** + * Created by Kosh on 08 Feb 2017, 10:03 PM + */ + + +@Getter @Setter @NoArgsConstructor @Table(persistAll = true) +public class PayloadModel implements Parcelable { + + @Id @Column long id; + @Column String action; + @Column(onDeleteCascade = true, handleRecursively = false) RepoModel forkee; + + @Override public int describeContents() { return 0; } + + @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(this.id); + dest.writeString(this.action); + dest.writeParcelable(this.forkee, flags); + } + + protected PayloadModel(Parcel in) { + this.id = in.readLong(); + this.action = in.readString(); + this.forkee = in.readParcelable(RepoModel.class.getClassLoader()); + } + + public static final Creator CREATOR = new Creator() { + @Override public PayloadModel createFromParcel(Parcel source) {return new PayloadModel(source);} + + @Override public PayloadModel[] newArray(int size) {return new PayloadModel[size];} + }; +} diff --git a/app/src/main/java/com/fastaccess/data/dao/PullRequestAdapterModel.java b/app/src/main/java/com/fastaccess/data/dao/PullRequestAdapterModel.java new file mode 100644 index 00000000..97c42176 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/PullRequestAdapterModel.java @@ -0,0 +1,67 @@ +package com.fastaccess.data.dao; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.Nullable; + +import com.annimon.stream.Stream; + +import java.util.ArrayList; +import java.util.List; + +import lombok.Getter; +import lombok.Setter; + +/** + * Created by Kosh on 10 Dec 2016, 3:34 PM + */ + +@Getter @Setter +public class PullRequestAdapterModel implements Parcelable { + + public static final int HEADER = 1; + public static final int ROW = 2; + private int type; + + private IssueEventModel issueEvent; + private PullRequestModel pullRequest; + + private PullRequestAdapterModel(int type, IssueEventModel model) { + this.type = type; + this.issueEvent = model; + } + + public PullRequestAdapterModel(int type, PullRequestModel pullRequest) { + this.type = type; + this.pullRequest = pullRequest; + } + + public static ArrayList addEvents(@Nullable List modelList) { + ArrayList models = new ArrayList<>(); + if (modelList == null || modelList.isEmpty()) return models; + Stream.of(modelList).forEach(issueEventModel -> models.add(new PullRequestAdapterModel(ROW, issueEventModel))); + return models; + } + + @Override public int describeContents() { return 0; } + + @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(this.type); + dest.writeParcelable(this.issueEvent, flags); + dest.writeParcelable(this.pullRequest, flags); + } + + public PullRequestAdapterModel() {} + + @SuppressWarnings("WeakerAccess") protected PullRequestAdapterModel(Parcel in) { + this.type = in.readInt(); + this.issueEvent = in.readParcelable(IssueEventModel.class.getClassLoader()); + this.pullRequest = in.readParcelable(IssueModel.class.getClassLoader()); + } + + public static final Creator CREATOR = new Creator() { + @Override public PullRequestAdapterModel createFromParcel(Parcel source) {return new PullRequestAdapterModel(source);} + + @Override public PullRequestAdapterModel[] newArray(int size) {return new PullRequestAdapterModel[size];} + }; +} diff --git a/app/src/main/java/com/fastaccess/data/dao/PullRequestModel.java b/app/src/main/java/com/fastaccess/data/dao/PullRequestModel.java new file mode 100644 index 00000000..3d7a27be --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/PullRequestModel.java @@ -0,0 +1,223 @@ +package com.fastaccess.data.dao; + +import android.content.Context; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; + +import com.fastaccess.R; +import com.fastaccess.data.dao.types.IssueState; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.helper.ParseDateFormat; +import com.fastaccess.ui.widgets.SpannableBuilder; +import com.siimkinks.sqlitemagic.Delete; +import com.siimkinks.sqlitemagic.PullRequestModelTable; +import com.siimkinks.sqlitemagic.Select; +import com.siimkinks.sqlitemagic.annotation.Column; +import com.siimkinks.sqlitemagic.annotation.Id; +import com.siimkinks.sqlitemagic.annotation.Table; + +import java.util.Date; +import java.util.List; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import rx.Completable; +import rx.Observable; + +/** + * Created by Kosh on 08 Dec 2016, 8:51 PM + */ + +@Getter @Setter @NoArgsConstructor @Table(persistAll = true) +public class PullRequestModel implements Parcelable { + @Column String url; + @Column String body; + @Column String title; + @Column @Id(autoIncrement = false) long id; + @Column int comments; + @Column int number; + @Column boolean locked; + @Column boolean mergable; + @Column boolean merged; + @Column boolean mergeable; + @Column int commits; + @Column int additions; + @Column int deletions; + @Column IssueState state; + @Column UserModel user; + @Column UserModel assignee; + @Column LabelListModel labels; + @Column MilestoneModel milestone; + @Column(handleRecursively = false) CommitModel base; + @Column(handleRecursively = false) CommitModel head; + @Column String bodyHtml; + @Column String htmlUrl; + @Column(handleRecursively = false) PullRequestModel pullRequest; + @Column Date closedAt; + @Column Date createdAt; + @Column Date updatedAt; + @Column UserModel closedBy; + @Column int changedFiles; + @Column String diffUrl; + @Column String patchUrl; + @Column String mergeCommitSha; + @Column Date mergedAt; + @Column UserModel mergedBy; + @Column String mergeState; + @Column int reviewComments; + @Column String repoId; + @Column String login; + + public Completable save() { + return persist().observe().toCompletable(); + } + + public static Completable save(@NonNull List models, @NonNull String repoId, @NonNull String login) { + return Delete.from(PullRequestModelTable.PULL_REQUEST_MODEL) + .where(PullRequestModelTable.PULL_REQUEST_MODEL.REPO_ID.is(repoId)) + .observe() + .toCompletable() + .andThen(Observable.from(models) + .map(pull -> { + pull.setRepoId(repoId); + pull.setLogin(login); + return pull.save(); + })) + .toCompletable(); + } + + public static Observable> getPullRequests(@NonNull String repoId, @NonNull String login, @NonNull IssueState issueState) { + return Select.from(PullRequestModelTable.PULL_REQUEST_MODEL) + .where(PullRequestModelTable.PULL_REQUEST_MODEL.REPO_ID.is(repoId) + .and(PullRequestModelTable.PULL_REQUEST_MODEL.LOGIN.is(login)) + .and(PullRequestModelTable.PULL_REQUEST_MODEL.STATE.is(issueState))) + .orderBy(PullRequestModelTable.PULL_REQUEST_MODEL.UPDATED_AT.desc()) + .queryDeep() + .observe() + .runQuery(); + } + + public static Observable getPullRequest(long id) { + return Select.from(PullRequestModelTable.PULL_REQUEST_MODEL) + .where(PullRequestModelTable.PULL_REQUEST_MODEL.ID.is(id)) + .queryDeep() + .takeFirst() + .observe() + .runQuery(); + } + + @NonNull public static SpannableBuilder getMergeBy(@NonNull PullRequestModel pullRequest, @NonNull Context context) { + UserModel merger = pullRequest.getMergedBy() != null ? pullRequest.getMergedBy() : pullRequest.getBase().getUser(); + boolean isMerge = pullRequest.isMerged() || !InputHelper.isEmpty(pullRequest.getMergedAt()); + String status = !isMerge ? context.getString(pullRequest.getState().getStatus()) : context.getString(R.string.merged); + SpannableBuilder builder = SpannableBuilder.builder(); + builder.append(merger.getLogin()) + .append(" ") + .append(status) + .append(" "); + if (isMerge) { + builder.append(ParseDateFormat.getTimeAgo(pullRequest.getMergedAt())); + } else { + builder.append(ParseDateFormat.getTimeAgo( + pullRequest.getState() == IssueState.closed + ? pullRequest.getClosedAt() : pullRequest.getCreatedAt())); + } + return builder; + } + + @Override public int describeContents() { return 0; } + + @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.url); + dest.writeString(this.body); + dest.writeString(this.title); + dest.writeLong(this.id); + dest.writeInt(this.comments); + dest.writeInt(this.number); + dest.writeByte(this.locked ? (byte) 1 : (byte) 0); + dest.writeByte(this.mergable ? (byte) 1 : (byte) 0); + dest.writeByte(this.merged ? (byte) 1 : (byte) 0); + dest.writeByte(this.mergeable ? (byte) 1 : (byte) 0); + dest.writeInt(this.commits); + dest.writeInt(this.additions); + dest.writeInt(this.deletions); + dest.writeInt(this.state == null ? -1 : this.state.ordinal()); + dest.writeParcelable(this.user, flags); + dest.writeParcelable(this.assignee, flags); + dest.writeList(this.labels); + dest.writeParcelable(this.milestone, flags); + dest.writeParcelable(this.base, flags); + dest.writeParcelable(this.head, flags); + dest.writeString(this.bodyHtml); + dest.writeString(this.htmlUrl); + dest.writeParcelable(this.pullRequest, flags); + dest.writeLong(this.closedAt != null ? this.closedAt.getTime() : -1); + dest.writeLong(this.createdAt != null ? this.createdAt.getTime() : -1); + dest.writeLong(this.updatedAt != null ? this.updatedAt.getTime() : -1); + dest.writeParcelable(this.closedBy, flags); + dest.writeInt(this.changedFiles); + dest.writeString(this.diffUrl); + dest.writeString(this.patchUrl); + dest.writeString(this.mergeCommitSha); + dest.writeLong(this.mergedAt != null ? this.mergedAt.getTime() : -1); + dest.writeParcelable(this.mergedBy, flags); + dest.writeString(this.mergeState); + dest.writeInt(this.reviewComments); + dest.writeString(this.repoId); + dest.writeString(this.login); + } + + protected PullRequestModel(Parcel in) { + this.url = in.readString(); + this.body = in.readString(); + this.title = in.readString(); + this.id = in.readLong(); + this.comments = in.readInt(); + this.number = in.readInt(); + this.locked = in.readByte() != 0; + this.mergable = in.readByte() != 0; + this.merged = in.readByte() != 0; + this.mergeable = in.readByte() != 0; + this.commits = in.readInt(); + this.additions = in.readInt(); + this.deletions = in.readInt(); + int tmpState = in.readInt(); + this.state = tmpState == -1 ? null : IssueState.values()[tmpState]; + this.user = in.readParcelable(UserModel.class.getClassLoader()); + this.assignee = in.readParcelable(UserModel.class.getClassLoader()); + this.labels = new LabelListModel(); + in.readList(this.labels, this.labels.getClass().getClassLoader()); + this.milestone = in.readParcelable(MilestoneModel.class.getClassLoader()); + this.base = in.readParcelable(CommitModel.class.getClassLoader()); + this.head = in.readParcelable(CommitModel.class.getClassLoader()); + this.bodyHtml = in.readString(); + this.htmlUrl = in.readString(); + this.pullRequest = in.readParcelable(PullRequestModel.class.getClassLoader()); + long tmpClosedAt = in.readLong(); + this.closedAt = tmpClosedAt == -1 ? null : new Date(tmpClosedAt); + long tmpCreatedAt = in.readLong(); + this.createdAt = tmpCreatedAt == -1 ? null : new Date(tmpCreatedAt); + long tmpUpdatedAt = in.readLong(); + this.updatedAt = tmpUpdatedAt == -1 ? null : new Date(tmpUpdatedAt); + this.closedBy = in.readParcelable(UserModel.class.getClassLoader()); + this.changedFiles = in.readInt(); + this.diffUrl = in.readString(); + this.patchUrl = in.readString(); + this.mergeCommitSha = in.readString(); + long tmpMergedAt = in.readLong(); + this.mergedAt = tmpMergedAt == -1 ? null : new Date(tmpMergedAt); + this.mergedBy = in.readParcelable(UserModel.class.getClassLoader()); + this.mergeState = in.readString(); + this.reviewComments = in.readInt(); + this.repoId = in.readString(); + this.login = in.readString(); + } + + public static final Creator CREATOR = new Creator() { + @Override public PullRequestModel createFromParcel(Parcel source) {return new PullRequestModel(source);} + + @Override public PullRequestModel[] newArray(int size) {return new PullRequestModel[size];} + }; +} diff --git a/app/src/main/java/com/fastaccess/data/dao/PullsIssuesParser.java b/app/src/main/java/com/fastaccess/data/dao/PullsIssuesParser.java new file mode 100644 index 00000000..214bae2d --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/PullsIssuesParser.java @@ -0,0 +1,77 @@ +package com.fastaccess.data.dao; + +import android.net.Uri; +import android.support.annotation.NonNull; + +import com.fastaccess.helper.InputHelper; +import com.fastaccess.helper.Logger; + +import java.util.List; + +import lombok.Getter; +import lombok.Setter; + +/** + * Created by Kosh on 17 Dec 2016, 12:17 AM + */ + +@Getter @Setter +public class PullsIssuesParser { + + private String login; + private String repoId; + private int number; + + public static PullsIssuesParser getForPullRequest(@NonNull String url) { + Uri uri = Uri.parse(url); + List segments = uri.getPathSegments(); + Logger.e(url, uri, segments); + if (segments == null || segments.size() < 4) return null; + if (!"pull".equals(segments.get(2))) return null; + String owner = segments.get(0); + String repo = segments.get(1); + String number = segments.get(3); + if (InputHelper.isEmpty(number)) return null; + int issueNumber; + try { + issueNumber = Integer.parseInt(number); + } catch (NumberFormatException nfe) { + return null; + } + PullsIssuesParser model = new PullsIssuesParser(); + model.setLogin(owner); + model.setRepoId(repo); + model.setNumber(issueNumber); + return model; + } + + public static PullsIssuesParser getForIssue(@NonNull String url) { + Uri uri = Uri.parse(url); + List segments = uri.getPathSegments(); + if (segments == null || segments.size() < 4) return null; + if (!"issues".equals(segments.get(2))) return null; + String owner = segments.get(0); + String repo = segments.get(1); + String number = segments.get(3); + if (InputHelper.isEmpty(number)) return null; + int issueNumber; + try { + issueNumber = Integer.parseInt(number); + } catch (NumberFormatException nfe) { + return null; + } + PullsIssuesParser model = new PullsIssuesParser(); + model.setLogin(owner); + model.setRepoId(repo); + model.setNumber(issueNumber); + return model; + } + + @Override public String toString() { + return "PullsIssuesParser{" + + "login='" + login + '\'' + + ", repoId='" + repoId + '\'' + + ", number=" + number + + '}'; + } +} diff --git a/app/src/main/java/com/fastaccess/data/dao/ReleasesAssetsListModel.java b/app/src/main/java/com/fastaccess/data/dao/ReleasesAssetsListModel.java new file mode 100644 index 00000000..439b1d0f --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/ReleasesAssetsListModel.java @@ -0,0 +1,9 @@ +package com.fastaccess.data.dao; + +import java.util.ArrayList; + +/** + * Created by Kosh on 31 Dec 2016, 1:28 PM + */ + +public class ReleasesAssetsListModel extends ArrayList {} diff --git a/app/src/main/java/com/fastaccess/data/dao/ReleasesAssetsModel.java b/app/src/main/java/com/fastaccess/data/dao/ReleasesAssetsModel.java new file mode 100644 index 00000000..175b9135 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/ReleasesAssetsModel.java @@ -0,0 +1,71 @@ +package com.fastaccess.data.dao; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Date; + +import lombok.Getter; +import lombok.Setter; + +/** + * Created by Kosh on 31 Dec 2016, 1:28 PM + */ + +@Getter @Setter class ReleasesAssetsModel implements Parcelable { + private String url; + private String browserDownloadUrl; + private long id; + private String name; + private String label; + private String state; + private String contentType; + private int size; + private int downloadCount; + private Date createdAt; + private Date updatedAt; + private UserModel uploader; + + + @Override public int describeContents() { return 0; } + + @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.url); + dest.writeString(this.browserDownloadUrl); + dest.writeLong(this.id); + dest.writeString(this.name); + dest.writeString(this.label); + dest.writeString(this.state); + dest.writeString(this.contentType); + dest.writeInt(this.size); + dest.writeInt(this.downloadCount); + dest.writeLong(this.createdAt != null ? this.createdAt.getTime() : -1); + dest.writeLong(this.updatedAt != null ? this.updatedAt.getTime() : -1); + dest.writeParcelable(this.uploader, flags); + } + + public ReleasesAssetsModel() {} + + @SuppressWarnings("WeakerAccess") protected ReleasesAssetsModel(Parcel in) { + this.url = in.readString(); + this.browserDownloadUrl = in.readString(); + this.id = in.readLong(); + this.name = in.readString(); + this.label = in.readString(); + this.state = in.readString(); + this.contentType = in.readString(); + this.size = in.readInt(); + this.downloadCount = in.readInt(); + long tmpCreatedAt = in.readLong(); + this.createdAt = tmpCreatedAt == -1 ? null : new Date(tmpCreatedAt); + long tmpUpdatedAt = in.readLong(); + this.updatedAt = tmpUpdatedAt == -1 ? null : new Date(tmpUpdatedAt); + this.uploader = in.readParcelable(UserModel.class.getClassLoader()); + } + + public static final Creator CREATOR = new Creator() { + @Override public ReleasesAssetsModel createFromParcel(Parcel source) {return new ReleasesAssetsModel(source);} + + @Override public ReleasesAssetsModel[] newArray(int size) {return new ReleasesAssetsModel[size];} + }; +} diff --git a/app/src/main/java/com/fastaccess/data/dao/ReleasesModel.java b/app/src/main/java/com/fastaccess/data/dao/ReleasesModel.java new file mode 100644 index 00000000..16c5c1ed --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/ReleasesModel.java @@ -0,0 +1,149 @@ +package com.fastaccess.data.dao; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; + +import com.siimkinks.sqlitemagic.Delete; +import com.siimkinks.sqlitemagic.ReleasesModelTable; +import com.siimkinks.sqlitemagic.Select; +import com.siimkinks.sqlitemagic.annotation.Column; +import com.siimkinks.sqlitemagic.annotation.Id; +import com.siimkinks.sqlitemagic.annotation.Table; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import rx.Completable; +import rx.Observable; + +/** + * Created by Kosh on 31 Dec 2016, 1:28 PM + */ + +@Getter @Setter @NoArgsConstructor @Table(persistAll = true) +public class ReleasesModel implements Parcelable { + + @Column String url; + @Column String htmlUrl; + @Column String assetsUrl; + @Column String uploadUrl; + @Column String tarballUrl; + @Column String zipBallUrl; + @Column @Id(autoIncrement = false) long id; + @Column String tagName; + @Column String targetCommitish; + @Column String name; + @Column String body; + @Column boolean draft; + @Column boolean preRelease; + @Column Date createdAt; + @Column Date publishedAt; + @Column UserModel author; + @Column ReleasesAssetsListModel assets; + @Column String repoId; + @Column String login; + + public Completable save() { + return this.persist().observe().toCompletable(); + } + + public static Completable save(@NonNull List models, @NonNull String repoId, @NonNull String login) { + return Delete.from(ReleasesModelTable.RELEASES_MODEL) + .where(ReleasesModelTable.RELEASES_MODEL.REPO_ID.is(repoId)) + .observe() + .toCompletable() + .andThen(Observable.from(models) + .map(releasesModel -> { + releasesModel.setRepoId(repoId); + releasesModel.setLogin(login); + return releasesModel.save(); + })) + .toCompletable(); + } + + public static Completable delete(@NonNull String repoId, @NonNull String login) { + return Delete.from(ReleasesModelTable.RELEASES_MODEL) + .where(ReleasesModelTable.RELEASES_MODEL.REPO_ID.is(repoId) + .and(ReleasesModelTable.RELEASES_MODEL.LOGIN.is(login))) + .observe() + .toCompletable(); + } + + public static Observable get(long id) { + return Select.from(ReleasesModelTable.RELEASES_MODEL) + .where(ReleasesModelTable.RELEASES_MODEL.ID.is(id)) + .queryDeep() + .takeFirst() + .observe() + .runQuery(); + } + + public static Observable> get(@NonNull String repoId, @NonNull String login) { + return Select.from(ReleasesModelTable.RELEASES_MODEL) + .where(ReleasesModelTable.RELEASES_MODEL.REPO_ID.is(repoId) + .and(ReleasesModelTable.RELEASES_MODEL.LOGIN.is(login))) + .queryDeep() + .observe() + .runQueryOnceOrDefault(new ArrayList<>()); + } + + @Override public int describeContents() { return 0; } + + @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.url); + dest.writeString(this.htmlUrl); + dest.writeString(this.assetsUrl); + dest.writeString(this.uploadUrl); + dest.writeString(this.tarballUrl); + dest.writeString(this.zipBallUrl); + dest.writeLong(this.id); + dest.writeString(this.tagName); + dest.writeString(this.targetCommitish); + dest.writeString(this.name); + dest.writeString(this.body); + dest.writeByte(this.draft ? (byte) 1 : (byte) 0); + dest.writeByte(this.preRelease ? (byte) 1 : (byte) 0); + dest.writeLong(this.createdAt != null ? this.createdAt.getTime() : -1); + dest.writeLong(this.publishedAt != null ? this.publishedAt.getTime() : -1); + dest.writeParcelable(this.author, flags); + dest.writeList(this.assets); + dest.writeString(this.repoId); + dest.writeString(this.login); + } + + protected ReleasesModel(Parcel in) { + this.url = in.readString(); + this.htmlUrl = in.readString(); + this.assetsUrl = in.readString(); + this.uploadUrl = in.readString(); + this.tarballUrl = in.readString(); + this.zipBallUrl = in.readString(); + this.id = in.readLong(); + this.tagName = in.readString(); + this.targetCommitish = in.readString(); + this.name = in.readString(); + this.body = in.readString(); + this.draft = in.readByte() != 0; + this.preRelease = in.readByte() != 0; + long tmpCreatedAt = in.readLong(); + this.createdAt = tmpCreatedAt == -1 ? null : new Date(tmpCreatedAt); + long tmpPublishedAt = in.readLong(); + this.publishedAt = tmpPublishedAt == -1 ? null : new Date(tmpPublishedAt); + this.author = in.readParcelable(UserModel.class.getClassLoader()); + this.assets = new ReleasesAssetsListModel(); + in.readList(this.assets, this.assets.getClass().getClassLoader()); + this.repoId = in.readString(); + this.login = in.readString(); + } + + public static final Creator CREATOR = new Creator() { + @Override public ReleasesModel createFromParcel(Parcel source) {return new ReleasesModel(source);} + + @Override public ReleasesModel[] newArray(int size) {return new ReleasesModel[size];} + }; +} diff --git a/app/src/main/java/com/fastaccess/data/dao/RenameModel.java b/app/src/main/java/com/fastaccess/data/dao/RenameModel.java new file mode 100644 index 00000000..ff5f9b4e --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/RenameModel.java @@ -0,0 +1,46 @@ +package com.fastaccess.data.dao; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.google.gson.annotations.SerializedName; +import com.siimkinks.sqlitemagic.annotation.Column; +import com.siimkinks.sqlitemagic.annotation.Table; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Created by Kosh on 13 Dec 2016, 12:33 AM + */ + +@Getter @Setter @NoArgsConstructor @Table(persistAll = true) +public class RenameModel implements Parcelable { + @SerializedName("from") @Column String fromValue; + @SerializedName("to") @Column String toValue; + + public long getId() { + return id; + } + + @Override public int describeContents() { return 0; } + + @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.fromValue); + dest.writeString(this.toValue); + dest.writeLong(this.id); + } + + protected RenameModel(Parcel in) { + this.fromValue = in.readString(); + this.toValue = in.readString(); + this.id = in.readLong(); + } + + public static final Creator CREATOR = new Creator() { + @Override public RenameModel createFromParcel(Parcel source) {return new RenameModel(source);} + + @Override public RenameModel[] newArray(int size) {return new RenameModel[size];} + }; +} diff --git a/app/src/main/java/com/fastaccess/data/dao/RepoFilesModel.java b/app/src/main/java/com/fastaccess/data/dao/RepoFilesModel.java new file mode 100644 index 00000000..0c3ba541 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/RepoFilesModel.java @@ -0,0 +1,117 @@ +package com.fastaccess.data.dao; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; + +import com.annimon.stream.Stream; +import com.fastaccess.data.dao.types.FilesType; +import com.siimkinks.sqlitemagic.Delete; +import com.siimkinks.sqlitemagic.RepoFilesModelTable; +import com.siimkinks.sqlitemagic.Select; +import com.siimkinks.sqlitemagic.annotation.Column; +import com.siimkinks.sqlitemagic.annotation.Table; + +import java.util.List; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import rx.Completable; +import rx.Observable; + +/** + * Created by Kosh on 17 Feb 2017, 7:51 PM + */ + +@Getter @Setter @NoArgsConstructor @Table(persistAll = true) +public class RepoFilesModel implements Parcelable { + + @Column String name; + @Column String path; + @Column String sha; + @Column long size; + @Column String url; + @Column String htmlUrl; + @Column String gitUrl; + @Column String downloadUrl; + @Column FilesType type; + @Column String repoId; + @Column String login; + + public Completable save() { + return this.persist().observe().toCompletable(); + } + + public static Completable save(@NonNull List models, @NonNull String login, @NonNull String repoId) { + return Delete.from(RepoFilesModelTable.REPO_FILES_MODEL) + .where(RepoFilesModelTable.REPO_FILES_MODEL.REPO_ID.is(repoId) + .and(RepoFilesModelTable.REPO_FILES_MODEL.LOGIN.is(login))) + .observe() + .toCompletable() + .andThen(Observable.create(subscriber -> Stream.of(models).forEach(filesModel -> { + filesModel.setRepoId(repoId); + filesModel.setLogin(login); + filesModel.save(); + }))) + .toCompletable(); + } + + public static Observable> getFiles(@NonNull String login, @NonNull String repoId) { + return Select.from(RepoFilesModelTable.REPO_FILES_MODEL) + .where(RepoFilesModelTable.REPO_FILES_MODEL.LOGIN.is(login) + .and(RepoFilesModelTable.REPO_FILES_MODEL.REPO_ID.is(repoId))) + .orderBy(RepoFilesModelTable.REPO_FILES_MODEL.TYPE.asc()) + .observe() + .runQuery(); + } + + public static Observable getFile(@NonNull String login, @NonNull String repoId, @NonNull String sha) { + return Select.from(RepoFilesModelTable.REPO_FILES_MODEL) + .where(RepoFilesModelTable.REPO_FILES_MODEL.LOGIN.is(login) + .and(RepoFilesModelTable.REPO_FILES_MODEL.REPO_ID.is(repoId)) + .and(RepoFilesModelTable.REPO_FILES_MODEL.SHA.is(sha))) + .takeFirst() + .observe() + .runQuery(); + } + + @Override public int describeContents() { return 0; } + + @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.name); + dest.writeString(this.path); + dest.writeString(this.sha); + dest.writeLong(this.size); + dest.writeString(this.url); + dest.writeString(this.htmlUrl); + dest.writeString(this.gitUrl); + dest.writeString(this.downloadUrl); + dest.writeInt(this.type == null ? -1 : this.type.ordinal()); + dest.writeString(this.repoId); + dest.writeString(this.login); + dest.writeLong(this.id); + } + + protected RepoFilesModel(Parcel in) { + this.name = in.readString(); + this.path = in.readString(); + this.sha = in.readString(); + this.size = in.readLong(); + this.url = in.readString(); + this.htmlUrl = in.readString(); + this.gitUrl = in.readString(); + this.downloadUrl = in.readString(); + int tmpType = in.readInt(); + this.type = tmpType == -1 ? null : FilesType.values()[tmpType]; + this.repoId = in.readString(); + this.login = in.readString(); + this.id = in.readLong(); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override public RepoFilesModel createFromParcel(Parcel source) {return new RepoFilesModel(source);} + + @Override public RepoFilesModel[] newArray(int size) {return new RepoFilesModel[size];} + }; +} diff --git a/app/src/main/java/com/fastaccess/data/dao/RepoModel.java b/app/src/main/java/com/fastaccess/data/dao/RepoModel.java new file mode 100644 index 00000000..15ce5253 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/RepoModel.java @@ -0,0 +1,363 @@ +package com.fastaccess.data.dao; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; + +import com.annimon.stream.Stream; +import com.siimkinks.sqlitemagic.Delete; +import com.siimkinks.sqlitemagic.RepoModelTable; +import com.siimkinks.sqlitemagic.Select; +import com.siimkinks.sqlitemagic.annotation.Column; +import com.siimkinks.sqlitemagic.annotation.Id; +import com.siimkinks.sqlitemagic.annotation.Table; + +import java.util.Date; +import java.util.List; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import rx.Completable; +import rx.Observable; + +/** + * Created by Kosh on 08 Feb 2017, 10:07 PM + */ + +@Getter @Setter @NoArgsConstructor @Table(persistAll = true) +public class RepoModel implements Parcelable { + + @Id(autoIncrement = false) @Column long id; + @Column String name; + @Column String fullName; + @Column boolean privateX; + @Column String htmlUrl; + @Column String description; + @Column boolean fork; + @Column String url; + @Column String forksUrl; + @Column String keysUrl; + @Column String collaboratorsUrl; + @Column String teamsUrl; + @Column String hooksUrl; + @Column String issueEventsUrl; + @Column String eventsUrl; + @Column String assigneesUrl; + @Column String branchesUrl; + @Column String tagsUrl; + @Column String blobsUrl; + @Column String gitTagsUrl; + @Column String gitRefsUrl; + @Column String treesUrl; + @Column String statusesUrl; + @Column String languagesUrl; + @Column String stargazersUrl; + @Column String contributorsUrl; + @Column String subscribersUrl; + @Column String subscriptionUrl; + @Column String commitsUrl; + @Column String gitCommitsUrl; + @Column String commentsUrl; + @Column String issueCommentUrl; + @Column String contentsUrl; + @Column String compareUrl; + @Column String mergesUrl; + @Column String archiveUrl; + @Column String downloadsUrl; + @Column String issuesUrl; + @Column String pullsUrl; + @Column String milestonesUrl; + @Column String notificationsUrl; + @Column String labelsUrl; + @Column String releasesUrl; + @Column Date createdAt; + @Column Date updatedAt; + @Column Date pushedAt; + @Column String gitUrl; + @Column String sshUrl; + @Column String cloneUrl; + @Column String svnUrl; + @Column String homepage; + @Column long size; + @Column long stargazersCount; + @Column long watchersCount; + @Column String language; + @Column boolean hasIssues; + @Column boolean hasDownloads; + @Column boolean hasWiki; + @Column boolean hasPages; + @Column long forksCount; + @Column String mirrorUrl; + @Column long openIssuesCount; + @Column long forks; + @Column long openIssues; + @Column long watchers; + @Column String defaultBranch; + @Column(onDeleteCascade = true) UserModel owner; + @Column(onDeleteCascade = true) RepoPermissionsModel permissions; + @Column(onDeleteCascade = true) UserModel organization; + @Column(onDeleteCascade = true, handleRecursively = false) RepoModel parent; + @Column(onDeleteCascade = true, handleRecursively = false) RepoModel source; + @Column(onDeleteCascade = true) LicenseModel license; + @Column int networkCount; + @Column int subsCount; + @Column String starredUser; + @Column String reposOwner; + + public static Observable getRepo(@NonNull String name) { + return Select.from(RepoModelTable.REPO_MODEL) + .where(RepoModelTable.REPO_MODEL.NAME.is(name)) + .takeFirst() + .observe() + .runQueryOnceOrDefault(null); + } + + public static RepoModel getRepo(long id) { + return Select.from(RepoModelTable.REPO_MODEL) + .where(RepoModelTable.REPO_MODEL.ID.is(id)) + .takeFirst() + .execute(); + } + + public static Completable saveStarred(@NonNull List models, @NonNull String starredUser) { + return Observable.create(subscriber -> Stream.of(models).forEach(repoModel -> repoModel.setStarredUser(starredUser))) + .toCompletable() + .andThen(Delete.from(RepoModelTable.REPO_MODEL) + .where(RepoModelTable.REPO_MODEL.STARRED_USER.is(starredUser)) + .observe() + .toCompletable()) + .andThen(persist(models).observe()); + } + + public static Completable saveMyRepos(@NonNull List models, @NonNull String reposOwner) { + return Observable.create(subscriber -> Stream.of(models).forEach(repoModel -> repoModel.setReposOwner(reposOwner))) + .toCompletable() + .andThen(Delete.from(RepoModelTable.REPO_MODEL) + .where(RepoModelTable.REPO_MODEL.REPOS_OWNER.is(reposOwner)) + .observe() + .toCompletable()) + .andThen(persist(models).observe()); + } + + public static Observable> getStarred(@NonNull String starredUser) { + return Select.from(RepoModelTable.REPO_MODEL) + .where(RepoModelTable.REPO_MODEL.STARRED_USER.is(starredUser)) + .orderBy(RepoModelTable.REPO_MODEL.UPDATED_AT.desc()) + .observe() + .runQuery(); + } + + public static Observable> getMyRepos(@NonNull String reposOwner) { + return Select.from(RepoModelTable.REPO_MODEL) + .where(RepoModelTable.REPO_MODEL.REPOS_OWNER.is(reposOwner)) + .orderBy(RepoModelTable.REPO_MODEL.UPDATED_AT.desc()) + .observe() + .runQuery(); + } + + public UserModel getOwner() { + if (owner != null && owner.getLogin() == null) { + UserModel model = UserModel.getUser(owner.getId()); + if (model != null) owner = model; + } + return owner; + } + + public UserModel getOrganization() { + if (organization != null && organization.getLogin() == null) { + UserModel model = UserModel.getUser(organization.getId()); + if (model != null) organization = model; + } + return organization; + } + + public void setOrganization(UserModel organization) { + this.organization = organization; + } + + public RepoModel getParent() { + if (parent != null && parent.getFullName() == null) { + RepoModel model = RepoModel.getRepo(parent.getId()); + if (model != null) parent = model; + } + return parent; + } + + @Override public String toString() { + return "RepoModel{" + + "name='" + name + '\'' + + ", fullName='" + fullName + '\'' + + ", owner=" + owner + + '}'; + } + + @Override public int describeContents() { return 0; } + + @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(this.id); + dest.writeString(this.name); + dest.writeString(this.fullName); + dest.writeByte(this.privateX ? (byte) 1 : (byte) 0); + dest.writeString(this.htmlUrl); + dest.writeString(this.description); + dest.writeByte(this.fork ? (byte) 1 : (byte) 0); + dest.writeString(this.url); + dest.writeString(this.forksUrl); + dest.writeString(this.keysUrl); + dest.writeString(this.collaboratorsUrl); + dest.writeString(this.teamsUrl); + dest.writeString(this.hooksUrl); + dest.writeString(this.issueEventsUrl); + dest.writeString(this.eventsUrl); + dest.writeString(this.assigneesUrl); + dest.writeString(this.branchesUrl); + dest.writeString(this.tagsUrl); + dest.writeString(this.blobsUrl); + dest.writeString(this.gitTagsUrl); + dest.writeString(this.gitRefsUrl); + dest.writeString(this.treesUrl); + dest.writeString(this.statusesUrl); + dest.writeString(this.languagesUrl); + dest.writeString(this.stargazersUrl); + dest.writeString(this.contributorsUrl); + dest.writeString(this.subscribersUrl); + dest.writeString(this.subscriptionUrl); + dest.writeString(this.commitsUrl); + dest.writeString(this.gitCommitsUrl); + dest.writeString(this.commentsUrl); + dest.writeString(this.issueCommentUrl); + dest.writeString(this.contentsUrl); + dest.writeString(this.compareUrl); + dest.writeString(this.mergesUrl); + dest.writeString(this.archiveUrl); + dest.writeString(this.downloadsUrl); + dest.writeString(this.issuesUrl); + dest.writeString(this.pullsUrl); + dest.writeString(this.milestonesUrl); + dest.writeString(this.notificationsUrl); + dest.writeString(this.labelsUrl); + dest.writeString(this.releasesUrl); + dest.writeLong(this.createdAt != null ? this.createdAt.getTime() : -1); + dest.writeLong(this.updatedAt != null ? this.updatedAt.getTime() : -1); + dest.writeLong(this.pushedAt != null ? this.pushedAt.getTime() : -1); + dest.writeString(this.gitUrl); + dest.writeString(this.sshUrl); + dest.writeString(this.cloneUrl); + dest.writeString(this.svnUrl); + dest.writeString(this.homepage); + dest.writeLong(this.size); + dest.writeLong(this.stargazersCount); + dest.writeLong(this.watchersCount); + dest.writeString(this.language); + dest.writeByte(this.hasIssues ? (byte) 1 : (byte) 0); + dest.writeByte(this.hasDownloads ? (byte) 1 : (byte) 0); + dest.writeByte(this.hasWiki ? (byte) 1 : (byte) 0); + dest.writeByte(this.hasPages ? (byte) 1 : (byte) 0); + dest.writeLong(this.forksCount); + dest.writeString(this.mirrorUrl); + dest.writeLong(this.openIssuesCount); + dest.writeLong(this.forks); + dest.writeLong(this.openIssues); + dest.writeLong(this.watchers); + dest.writeString(this.defaultBranch); + dest.writeParcelable(this.owner, flags); + dest.writeParcelable(this.permissions, flags); + dest.writeParcelable(this.organization, flags); + dest.writeParcelable(this.parent, flags); + dest.writeParcelable(this.source, flags); + dest.writeParcelable(this.license, flags); + dest.writeInt(this.networkCount); + dest.writeInt(this.subsCount); + dest.writeString(this.starredUser); + dest.writeString(this.reposOwner); + } + + protected RepoModel(Parcel in) { + this.id = in.readLong(); + this.name = in.readString(); + this.fullName = in.readString(); + this.privateX = in.readByte() != 0; + this.htmlUrl = in.readString(); + this.description = in.readString(); + this.fork = in.readByte() != 0; + this.url = in.readString(); + this.forksUrl = in.readString(); + this.keysUrl = in.readString(); + this.collaboratorsUrl = in.readString(); + this.teamsUrl = in.readString(); + this.hooksUrl = in.readString(); + this.issueEventsUrl = in.readString(); + this.eventsUrl = in.readString(); + this.assigneesUrl = in.readString(); + this.branchesUrl = in.readString(); + this.tagsUrl = in.readString(); + this.blobsUrl = in.readString(); + this.gitTagsUrl = in.readString(); + this.gitRefsUrl = in.readString(); + this.treesUrl = in.readString(); + this.statusesUrl = in.readString(); + this.languagesUrl = in.readString(); + this.stargazersUrl = in.readString(); + this.contributorsUrl = in.readString(); + this.subscribersUrl = in.readString(); + this.subscriptionUrl = in.readString(); + this.commitsUrl = in.readString(); + this.gitCommitsUrl = in.readString(); + this.commentsUrl = in.readString(); + this.issueCommentUrl = in.readString(); + this.contentsUrl = in.readString(); + this.compareUrl = in.readString(); + this.mergesUrl = in.readString(); + this.archiveUrl = in.readString(); + this.downloadsUrl = in.readString(); + this.issuesUrl = in.readString(); + this.pullsUrl = in.readString(); + this.milestonesUrl = in.readString(); + this.notificationsUrl = in.readString(); + this.labelsUrl = in.readString(); + this.releasesUrl = in.readString(); + long tmpCreatedAt = in.readLong(); + this.createdAt = tmpCreatedAt == -1 ? null : new Date(tmpCreatedAt); + long tmpUpdatedAt = in.readLong(); + this.updatedAt = tmpUpdatedAt == -1 ? null : new Date(tmpUpdatedAt); + long tmpPushedAt = in.readLong(); + this.pushedAt = tmpPushedAt == -1 ? null : new Date(tmpPushedAt); + this.gitUrl = in.readString(); + this.sshUrl = in.readString(); + this.cloneUrl = in.readString(); + this.svnUrl = in.readString(); + this.homepage = in.readString(); + this.size = in.readLong(); + this.stargazersCount = in.readLong(); + this.watchersCount = in.readLong(); + this.language = in.readString(); + this.hasIssues = in.readByte() != 0; + this.hasDownloads = in.readByte() != 0; + this.hasWiki = in.readByte() != 0; + this.hasPages = in.readByte() != 0; + this.forksCount = in.readLong(); + this.mirrorUrl = in.readString(); + this.openIssuesCount = in.readLong(); + this.forks = in.readLong(); + this.openIssues = in.readLong(); + this.watchers = in.readLong(); + this.defaultBranch = in.readString(); + this.owner = in.readParcelable(UserModel.class.getClassLoader()); + this.permissions = in.readParcelable(RepoPermissionsModel.class.getClassLoader()); + this.organization = in.readParcelable(UserModel.class.getClassLoader()); + this.parent = in.readParcelable(RepoModel.class.getClassLoader()); + this.source = in.readParcelable(RepoModel.class.getClassLoader()); + this.license = in.readParcelable(LicenseModel.class.getClassLoader()); + this.networkCount = in.readInt(); + this.subsCount = in.readInt(); + this.starredUser = in.readString(); + this.reposOwner = in.readString(); + } + + public static final Creator CREATOR = new Creator() { + @Override public RepoModel createFromParcel(Parcel source) {return new RepoModel(source);} + + @Override public RepoModel[] newArray(int size) {return new RepoModel[size];} + }; +} diff --git a/app/src/main/java/com/fastaccess/data/dao/RepoPermissionsModel.java b/app/src/main/java/com/fastaccess/data/dao/RepoPermissionsModel.java new file mode 100644 index 00000000..10ededdd --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/RepoPermissionsModel.java @@ -0,0 +1,46 @@ +package com.fastaccess.data.dao; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.siimkinks.sqlitemagic.annotation.Column; +import com.siimkinks.sqlitemagic.annotation.Id; +import com.siimkinks.sqlitemagic.annotation.Table; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Created by Kosh on 03 Dec 2016, 11:12 AM + */ + +@Getter @Setter @NoArgsConstructor @Table(persistAll = true) +public class RepoPermissionsModel implements Parcelable { + @Id @Column long id; + @Column boolean admin; + @Column boolean push; + @Column boolean pull; + + @Override public int describeContents() { return 0; } + + @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(this.id); + dest.writeByte(this.admin ? (byte) 1 : (byte) 0); + dest.writeByte(this.push ? (byte) 1 : (byte) 0); + dest.writeByte(this.pull ? (byte) 1 : (byte) 0); + } + + protected RepoPermissionsModel(Parcel in) { + this.id = in.readLong(); + this.admin = in.readByte() != 0; + this.push = in.readByte() != 0; + this.pull = in.readByte() != 0; + } + + public static final Creator CREATOR = new Creator() { + @Override public RepoPermissionsModel createFromParcel(Parcel source) {return new RepoPermissionsModel(source);} + + @Override public RepoPermissionsModel[] newArray(int size) {return new RepoPermissionsModel[size];} + }; +} diff --git a/app/src/main/java/com/fastaccess/data/dao/SearchCodeModel.java b/app/src/main/java/com/fastaccess/data/dao/SearchCodeModel.java new file mode 100644 index 00000000..da157e85 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/SearchCodeModel.java @@ -0,0 +1,53 @@ +package com.fastaccess.data.dao; + +import android.os.Parcel; +import android.os.Parcelable; + +import lombok.Getter; +import lombok.Setter; + +/** + * Created by Kosh on 19 Feb 2017, 1:17 AM + */ + +@Getter @Setter +public class SearchCodeModel implements Parcelable { + private String name; + private String path; + private String sha; + private String url; + private String gitUrl; + private RepoModel repository; + private double score; + + + @Override public int describeContents() { return 0; } + + @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.name); + dest.writeString(this.path); + dest.writeString(this.sha); + dest.writeString(this.url); + dest.writeString(this.gitUrl); + dest.writeParcelable(this.repository, flags); + dest.writeDouble(this.score); + } + + public SearchCodeModel() {} + + @SuppressWarnings("WeakerAccess") protected SearchCodeModel(Parcel in) { + this.name = in.readString(); + this.path = in.readString(); + this.sha = in.readString(); + this.url = in.readString(); + this.gitUrl = in.readString(); + this.repository = in.readParcelable(RepoModel.class.getClassLoader()); + this.score = in.readDouble(); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override public SearchCodeModel createFromParcel(Parcel source) {return new SearchCodeModel(source);} + + @Override public SearchCodeModel[] newArray(int size) {return new SearchCodeModel[size];} + }; +} diff --git a/app/src/main/java/com/fastaccess/data/dao/SearchHistoryModel.java b/app/src/main/java/com/fastaccess/data/dao/SearchHistoryModel.java new file mode 100644 index 00000000..09784ecf --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/SearchHistoryModel.java @@ -0,0 +1,79 @@ +package com.fastaccess.data.dao; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.siimkinks.sqlitemagic.SearchHistoryModelTable; +import com.siimkinks.sqlitemagic.Select; +import com.siimkinks.sqlitemagic.annotation.Column; +import com.siimkinks.sqlitemagic.annotation.Table; +import com.siimkinks.sqlitemagic.annotation.Unique; + +import java.util.List; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import rx.Completable; +import rx.Observable; + +/** + * Created by Kosh on 01 Jan 2017, 11:20 PM + */ + +@Getter @Setter @NoArgsConstructor @Table(persistAll = true) +public class SearchHistoryModel implements Parcelable { + + @Column @Unique String text; + + public SearchHistoryModel(String text) { + this.text = text; + } + + @Override public int describeContents() { return 0; } + + @Override public void writeToParcel(Parcel dest, int flags) {dest.writeString(this.text);} + + protected SearchHistoryModel(Parcel in) {this.text = in.readString();} + + public static final Creator CREATOR = new Creator() { + @Override public SearchHistoryModel createFromParcel(Parcel source) {return new SearchHistoryModel(source);} + + @Override public SearchHistoryModel[] newArray(int size) {return new SearchHistoryModel[size];} + }; + + public Completable save() { + return persist().observe() + .onErrorReturn(throwable -> { + throwable.printStackTrace(); + return null; + }) + .toCompletable(); + } + + public static Observable> getHistory() { + return Select.from(SearchHistoryModelTable.SEARCH_HISTORY_MODEL) + .orderBy(SearchHistoryModelTable.SEARCH_HISTORY_MODEL.TEXT.asc()) + .limit(10) + .observe() + .runQuery(); + } + + @Override public String toString() { + return text; + } + + @Override public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + SearchHistoryModel that = (SearchHistoryModel) o; + + return text != null ? text.equals(that.text) : that.text == null; + + } + + @Override public int hashCode() { + return text != null ? text.hashCode() : 0; + } +} diff --git a/app/src/main/java/com/fastaccess/data/dao/SimpleUrlsModel.java b/app/src/main/java/com/fastaccess/data/dao/SimpleUrlsModel.java new file mode 100644 index 00000000..6a9ea11f --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/SimpleUrlsModel.java @@ -0,0 +1,46 @@ +package com.fastaccess.data.dao; + +import android.os.Parcel; +import android.os.Parcelable; + +import lombok.Getter; +import lombok.Setter; + +/** + * Created by Kosh on 31 Dec 2016, 3:32 PM + */ + + +@Getter @Setter +public class SimpleUrlsModel implements Parcelable { + + private String item; + private String url; + + public SimpleUrlsModel(String item, String url) { + this.item = item; + this.url = url; + } + + @Override public String toString() { + return item; + } + + @Override public int describeContents() { return 0; } + + @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.item); + dest.writeString(this.url); + } + + @SuppressWarnings("WeakerAccess") protected SimpleUrlsModel(Parcel in) { + this.item = in.readString(); + this.url = in.readString(); + } + + public static final Creator CREATOR = new Creator() { + @Override public SimpleUrlsModel createFromParcel(Parcel source) {return new SimpleUrlsModel(source);} + + @Override public SimpleUrlsModel[] newArray(int size) {return new SimpleUrlsModel[size];} + }; +} diff --git a/app/src/main/java/com/fastaccess/data/dao/SparseBooleanArrayParcelable.java b/app/src/main/java/com/fastaccess/data/dao/SparseBooleanArrayParcelable.java new file mode 100644 index 00000000..912eb792 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/SparseBooleanArrayParcelable.java @@ -0,0 +1,64 @@ +package com.fastaccess.data.dao; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.SparseBooleanArray; + + +/** + * Class from http://stackoverflow.com/a/16711258/1627904 + */ +public class SparseBooleanArrayParcelable extends SparseBooleanArray implements Parcelable { + public static Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public SparseBooleanArrayParcelable createFromParcel(Parcel source) { + SparseBooleanArrayParcelable read = new SparseBooleanArrayParcelable(); + int size = source.readInt(); + + int[] keys = new int[size]; + boolean[] values = new boolean[size]; + + source.readIntArray(keys); + source.readBooleanArray(values); + + for (int i = 0; i < size; i++) { + read.put(keys[i], values[i]); + } + + return read; + } + + @Override + public SparseBooleanArrayParcelable[] newArray(int size) { + return new SparseBooleanArrayParcelable[size]; + } + }; + + public SparseBooleanArrayParcelable() { + + } + + public SparseBooleanArrayParcelable(SparseBooleanArray sparseBooleanArray) { + for (int i = 0; i < sparseBooleanArray.size(); i++) { + this.put(sparseBooleanArray.keyAt(i), sparseBooleanArray.valueAt(i)); + } + } + + @Override public int describeContents() { + return 0; + } + + @Override public void writeToParcel(Parcel dest, int flags) { + int[] keys = new int[size()]; + boolean[] values = new boolean[size()]; + + for (int i = 0; i < size(); i++) { + keys[i] = keyAt(i); + values[i] = valueAt(i); + } + + dest.writeInt(size()); + dest.writeIntArray(keys); + dest.writeBooleanArray(values); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/data/dao/UserModel.java b/app/src/main/java/com/fastaccess/data/dao/UserModel.java new file mode 100644 index 00000000..7a15cbff --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/UserModel.java @@ -0,0 +1,238 @@ +package com.fastaccess.data.dao; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.siimkinks.sqlitemagic.Delete; +import com.siimkinks.sqlitemagic.Select; +import com.siimkinks.sqlitemagic.UserModelTable; +import com.siimkinks.sqlitemagic.annotation.Column; +import com.siimkinks.sqlitemagic.annotation.Id; +import com.siimkinks.sqlitemagic.annotation.Table; + +import java.util.Date; +import java.util.List; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import rx.Completable; +import rx.Observable; + + +/** + * Created by Kosh on 08 Feb 2017, 8:51 PM + */ + +@Getter @Setter @NoArgsConstructor @Table(persistAll = true) +public class UserModel implements Parcelable { + + @Id(autoIncrement = false) @Column long id; + @Column String login; + @Column String avatarUrl; + @Column String gravatarId; + @Column String url; + @Column String htmlUrl; + @Column String followersUrl; + @Column String followingUrl; + @Column String gistsUrl; + @Column String starredUrl; + @Column String subscriptionsUrl; + @Column String organizationsUrl; + @Column String reposUrl; + @Column String eventsUrl; + @Column String receivedEventsUrl; + @Column String type; + @Column boolean siteAdmin; + @Column String name; + @Column String company; + @Column String blog; + @Column String location; + @Column String email; + @Column boolean hireable; + @Column String bio; + @Column long publicRepos; + @Column long publicGists; + @Column long followers; + @Column long following; + @Column Date createdAt; + @Column Date updatedAt; + @Column int contributions; + @Column String followingName; + @Column String followerName; + @Column Date date; + @Column String repoId; + + public void save() { + this.persist().execute(); + } + + @Nullable public static UserModel getUser(String login) { + return Select.from(UserModelTable.USER_MODEL) + .where(UserModelTable.USER_MODEL.LOGIN.is(login)) + .takeFirst() + .execute(); + } + + @Nullable public static UserModel getUser(long id) { + return Select.from(UserModelTable.USER_MODEL) + .where(UserModelTable.USER_MODEL.ID.is(id)) + .takeFirst() + .execute(); + } + + public static Completable saveFollowers(@NonNull List models, @NonNull String followingName) { + return Delete.from(UserModelTable.USER_MODEL) + .where(UserModelTable.USER_MODEL.FOLLOWING_NAME.is(followingName)) + .observe() + .toCompletable() + .andThen(Observable.from(models) + .map(userModel -> { + userModel.setFollowingName(followingName); + return userModel.persist().observe(); + })) + .toCompletable(); + } + + public static Completable saveFollowings(@NonNull List models, @NonNull String followerName) { + return Delete.from(UserModelTable.USER_MODEL) + .where(UserModelTable.USER_MODEL.FOLLOWER_NAME.is(followerName)) + .observe() + .toCompletable() + .andThen(Observable.from(models) + .map(userModel -> { + userModel.setFollowerName(followerName); + return userModel.persist().observe(); + })) + .toCompletable(); + + } + + public static Completable saveContributors(@NonNull List models, @NonNull String repoId) { + return Delete.from(UserModelTable.USER_MODEL) + .where(UserModelTable.USER_MODEL.REPO_ID.is(repoId)) + .observe() + .toCompletable() + .andThen(Observable.from(models) + .map(userModel -> { + userModel.setRepoId(repoId); + return userModel.persist().observe(); + })) + .toCompletable(); + + } + + @NonNull public static Observable> getFollowers(@NonNull String following) { + return Select.from(UserModelTable.USER_MODEL) + .where(UserModelTable.USER_MODEL.FOLLOWING_NAME.is(following)) + .orderBy(UserModelTable.USER_MODEL.FOLLOWERS.desc()) + .observe() + .runQuery(); + } + + @NonNull public static Observable> getFollowing(@NonNull String follower) { + return Select.from(UserModelTable.USER_MODEL) + .where(UserModelTable.USER_MODEL.FOLLOWER_NAME.is(follower)) + .orderBy(UserModelTable.USER_MODEL.FOLLOWERS.desc()) + .observe() + .runQuery(); + } + + @NonNull public static Observable> getContributors(@NonNull String repoId) { + return Select.from(UserModelTable.USER_MODEL) + .where(UserModelTable.USER_MODEL.REPO_ID.is(repoId)) + .orderBy(UserModelTable.USER_MODEL.CONTRIBUTIONS.desc()) + .observe() + .runQuery(); + } + + @Override public int describeContents() { return 0; } + + @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(this.id); + dest.writeString(this.login); + dest.writeString(this.avatarUrl); + dest.writeString(this.gravatarId); + dest.writeString(this.url); + dest.writeString(this.htmlUrl); + dest.writeString(this.followersUrl); + dest.writeString(this.followingUrl); + dest.writeString(this.gistsUrl); + dest.writeString(this.starredUrl); + dest.writeString(this.subscriptionsUrl); + dest.writeString(this.organizationsUrl); + dest.writeString(this.reposUrl); + dest.writeString(this.eventsUrl); + dest.writeString(this.receivedEventsUrl); + dest.writeString(this.type); + dest.writeByte(this.siteAdmin ? (byte) 1 : (byte) 0); + dest.writeString(this.name); + dest.writeString(this.company); + dest.writeString(this.blog); + dest.writeString(this.location); + dest.writeString(this.email); + dest.writeByte(this.hireable ? (byte) 1 : (byte) 0); + dest.writeString(this.bio); + dest.writeLong(this.publicRepos); + dest.writeLong(this.publicGists); + dest.writeLong(this.followers); + dest.writeLong(this.following); + dest.writeLong(this.createdAt != null ? this.createdAt.getTime() : -1); + dest.writeLong(this.updatedAt != null ? this.updatedAt.getTime() : -1); + dest.writeInt(this.contributions); + dest.writeString(this.followingName); + dest.writeString(this.followerName); + } + + protected UserModel(Parcel in) { + this.id = in.readLong(); + this.login = in.readString(); + this.avatarUrl = in.readString(); + this.gravatarId = in.readString(); + this.url = in.readString(); + this.htmlUrl = in.readString(); + this.followersUrl = in.readString(); + this.followingUrl = in.readString(); + this.gistsUrl = in.readString(); + this.starredUrl = in.readString(); + this.subscriptionsUrl = in.readString(); + this.organizationsUrl = in.readString(); + this.reposUrl = in.readString(); + this.eventsUrl = in.readString(); + this.receivedEventsUrl = in.readString(); + this.type = in.readString(); + this.siteAdmin = in.readByte() != 0; + this.name = in.readString(); + this.company = in.readString(); + this.blog = in.readString(); + this.location = in.readString(); + this.email = in.readString(); + this.hireable = in.readByte() != 0; + this.bio = in.readString(); + this.publicRepos = in.readLong(); + this.publicGists = in.readLong(); + this.followers = in.readLong(); + this.following = in.readLong(); + long tmpCreatedAt = in.readLong(); + this.createdAt = tmpCreatedAt == -1 ? null : new Date(tmpCreatedAt); + long tmpUpdatedAt = in.readLong(); + this.updatedAt = tmpUpdatedAt == -1 ? null : new Date(tmpUpdatedAt); + this.contributions = in.readInt(); + this.followingName = in.readString(); + this.followerName = in.readString(); + } + + public static final Creator CREATOR = new Creator() { + @Override public UserModel createFromParcel(Parcel source) {return new UserModel(source);} + + @Override public UserModel[] newArray(int size) {return new UserModel[size];} + }; + + @Override public String toString() { + return "UserModel{" + + "id='" + id + '\'' + + '}'; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/data/dao/UsersListModel.java b/app/src/main/java/com/fastaccess/data/dao/UsersListModel.java new file mode 100644 index 00000000..7c6c1a54 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/UsersListModel.java @@ -0,0 +1,9 @@ +package com.fastaccess.data.dao; + +import java.util.ArrayList; + +/** + * Created by Kosh on 12 Feb 2017, 1:33 PM + */ + +public class UsersListModel extends ArrayList {} diff --git a/app/src/main/java/com/fastaccess/data/dao/transformer/CommitsFilesTransformer.java b/app/src/main/java/com/fastaccess/data/dao/transformer/CommitsFilesTransformer.java new file mode 100644 index 00000000..dae21690 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/transformer/CommitsFilesTransformer.java @@ -0,0 +1,24 @@ +package com.fastaccess.data.dao.transformer; + +import android.support.annotation.NonNull; + +import com.fastaccess.data.dao.CommitFileListModel; +import com.google.gson.Gson; +import com.siimkinks.sqlitemagic.annotation.transformer.DbValueToObject; +import com.siimkinks.sqlitemagic.annotation.transformer.ObjectToDbValue; +import com.siimkinks.sqlitemagic.annotation.transformer.Transformer; + +/** + * Created by Kosh on 11 Feb 2017, 11:43 PM + */ + +@Transformer public class CommitsFilesTransformer { + + @ObjectToDbValue @NonNull public static String objectToDbValue(CommitFileListModel javaObject) { + return javaObject == null ? "[]" : new Gson().toJson(javaObject); + } + + @DbValueToObject @NonNull public static CommitFileListModel dbValueToObject(String dbObject) { + return dbObject != null ? new Gson().fromJson(dbObject, CommitFileListModel.class) : new CommitFileListModel(); + } +} diff --git a/app/src/main/java/com/fastaccess/data/dao/transformer/CommitsListTransformer.java b/app/src/main/java/com/fastaccess/data/dao/transformer/CommitsListTransformer.java new file mode 100644 index 00000000..f548a3e0 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/transformer/CommitsListTransformer.java @@ -0,0 +1,24 @@ +package com.fastaccess.data.dao.transformer; + +import android.support.annotation.NonNull; + +import com.fastaccess.data.dao.CommitListModel; +import com.google.gson.Gson; +import com.siimkinks.sqlitemagic.annotation.transformer.DbValueToObject; +import com.siimkinks.sqlitemagic.annotation.transformer.ObjectToDbValue; +import com.siimkinks.sqlitemagic.annotation.transformer.Transformer; + +/** + * Created by Kosh on 11 Feb 2017, 11:43 PM + */ + +@Transformer public class CommitsListTransformer { + + @ObjectToDbValue @NonNull public static String objectToDbValue(CommitListModel javaObject) { + return javaObject == null ? "[]" : new Gson().toJson(javaObject); + } + + @DbValueToObject @NonNull public static CommitListModel dbValueToObject(String dbObject) { + return dbObject != null ? new Gson().fromJson(dbObject, CommitListModel.class) : new CommitListModel(); + } +} diff --git a/app/src/main/java/com/fastaccess/data/dao/transformer/EventTypeTransformer.java b/app/src/main/java/com/fastaccess/data/dao/transformer/EventTypeTransformer.java new file mode 100644 index 00000000..aba5c2b7 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/transformer/EventTypeTransformer.java @@ -0,0 +1,23 @@ +package com.fastaccess.data.dao.transformer; + +import android.support.annotation.NonNull; + +import com.fastaccess.data.dao.types.EventsType; +import com.siimkinks.sqlitemagic.annotation.transformer.DbValueToObject; +import com.siimkinks.sqlitemagic.annotation.transformer.ObjectToDbValue; +import com.siimkinks.sqlitemagic.annotation.transformer.Transformer; + +/** + * Created by Kosh on 11 Feb 2017, 11:43 PM + */ + +@Transformer public class EventTypeTransformer { + + @ObjectToDbValue @NonNull public static String objectToDbValue(EventsType javaObject) { + return javaObject == null ? EventsType.WatchEvent.name() : javaObject.name(); + } + + @DbValueToObject @NonNull public static EventsType dbValueToObject(String dbObject) { + return dbObject != null ? EventsType.valueOf(dbObject) : EventsType.WatchEvent; + } +} diff --git a/app/src/main/java/com/fastaccess/data/dao/transformer/FilesTypeTransformer.java b/app/src/main/java/com/fastaccess/data/dao/transformer/FilesTypeTransformer.java new file mode 100644 index 00000000..de6d167d --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/transformer/FilesTypeTransformer.java @@ -0,0 +1,23 @@ +package com.fastaccess.data.dao.transformer; + +import android.support.annotation.NonNull; + +import com.fastaccess.data.dao.types.FilesType; +import com.siimkinks.sqlitemagic.annotation.transformer.DbValueToObject; +import com.siimkinks.sqlitemagic.annotation.transformer.ObjectToDbValue; +import com.siimkinks.sqlitemagic.annotation.transformer.Transformer; + +/** + * Created by Kosh on 11 Feb 2017, 11:43 PM + */ + +@Transformer public class FilesTypeTransformer { + + @ObjectToDbValue @NonNull public static String objectToDbValue(FilesType javaObject) { + return javaObject == null ? FilesType.file.name() : javaObject.name(); + } + + @DbValueToObject @NonNull public static FilesType dbValueToObject(String dbObject) { + return dbObject != null ? FilesType.valueOf(dbObject) : FilesType.file; + } +} diff --git a/app/src/main/java/com/fastaccess/data/dao/transformer/GitCommitListTransformer.java b/app/src/main/java/com/fastaccess/data/dao/transformer/GitCommitListTransformer.java new file mode 100644 index 00000000..5fa84686 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/transformer/GitCommitListTransformer.java @@ -0,0 +1,24 @@ +package com.fastaccess.data.dao.transformer; + +import android.support.annotation.NonNull; + +import com.fastaccess.data.dao.GitCommitListModel; +import com.google.gson.Gson; +import com.siimkinks.sqlitemagic.annotation.transformer.DbValueToObject; +import com.siimkinks.sqlitemagic.annotation.transformer.ObjectToDbValue; +import com.siimkinks.sqlitemagic.annotation.transformer.Transformer; + +/** + * Created by Kosh on 11 Feb 2017, 11:43 PM + */ + +@Transformer public class GitCommitListTransformer { + + @ObjectToDbValue @NonNull public static String objectToDbValue(GitCommitListModel javaObject) { + return javaObject == null ? "[]" : new Gson().toJson(javaObject); + } + + @DbValueToObject @NonNull public static GitCommitListModel dbValueToObject(String dbObject) { + return dbObject != null ? new Gson().fromJson(dbObject, GitCommitListModel.class) : new GitCommitListModel(); + } +} diff --git a/app/src/main/java/com/fastaccess/data/dao/transformer/IssueEventTransformer.java b/app/src/main/java/com/fastaccess/data/dao/transformer/IssueEventTransformer.java new file mode 100644 index 00000000..4ea9ea56 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/transformer/IssueEventTransformer.java @@ -0,0 +1,23 @@ +package com.fastaccess.data.dao.transformer; + +import android.support.annotation.NonNull; + +import com.fastaccess.data.dao.types.IssueEventType; +import com.siimkinks.sqlitemagic.annotation.transformer.DbValueToObject; +import com.siimkinks.sqlitemagic.annotation.transformer.ObjectToDbValue; +import com.siimkinks.sqlitemagic.annotation.transformer.Transformer; + +/** + * Created by Kosh on 11 Feb 2017, 11:43 PM + */ + +@Transformer public class IssueEventTransformer { + + @ObjectToDbValue @NonNull public static String objectToDbValue(IssueEventType javaObject) { + return javaObject == null ? IssueEventType.labeled.name() : javaObject.name(); + } + + @DbValueToObject @NonNull public static IssueEventType dbValueToObject(String dbObject) { + return dbObject != null ? IssueEventType.valueOf(dbObject) : IssueEventType.labeled; + } +} diff --git a/app/src/main/java/com/fastaccess/data/dao/transformer/IssueStateTransformer.java b/app/src/main/java/com/fastaccess/data/dao/transformer/IssueStateTransformer.java new file mode 100644 index 00000000..f962f2b6 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/transformer/IssueStateTransformer.java @@ -0,0 +1,23 @@ +package com.fastaccess.data.dao.transformer; + +import android.support.annotation.NonNull; + +import com.fastaccess.data.dao.types.IssueState; +import com.siimkinks.sqlitemagic.annotation.transformer.DbValueToObject; +import com.siimkinks.sqlitemagic.annotation.transformer.ObjectToDbValue; +import com.siimkinks.sqlitemagic.annotation.transformer.Transformer; + +/** + * Created by Kosh on 11 Feb 2017, 11:43 PM + */ + +@Transformer public class IssueStateTransformer { + + @ObjectToDbValue @NonNull public static String objectToDbValue(IssueState javaObject) { + return javaObject == null ? IssueState.closed.name() : javaObject.name(); + } + + @DbValueToObject @NonNull public static IssueState dbValueToObject(String dbObject) { + return dbObject != null ? IssueState.valueOf(dbObject) : IssueState.closed; + } +} diff --git a/app/src/main/java/com/fastaccess/data/dao/transformer/LabelsListTransformer.java b/app/src/main/java/com/fastaccess/data/dao/transformer/LabelsListTransformer.java new file mode 100644 index 00000000..c1d72df4 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/transformer/LabelsListTransformer.java @@ -0,0 +1,24 @@ +package com.fastaccess.data.dao.transformer; + +import android.support.annotation.NonNull; + +import com.fastaccess.data.dao.LabelListModel; +import com.google.gson.Gson; +import com.siimkinks.sqlitemagic.annotation.transformer.DbValueToObject; +import com.siimkinks.sqlitemagic.annotation.transformer.ObjectToDbValue; +import com.siimkinks.sqlitemagic.annotation.transformer.Transformer; + +/** + * Created by Kosh on 11 Feb 2017, 11:43 PM + */ + +@Transformer public class LabelsListTransformer { + + @ObjectToDbValue @NonNull public static String objectToDbValue(LabelListModel javaObject) { + return javaObject == null ? "[]" : new Gson().toJson(javaObject); + } + + @DbValueToObject @NonNull public static LabelListModel dbValueToObject(String dbObject) { + return dbObject != null ? new Gson().fromJson(dbObject, LabelListModel.class) : new LabelListModel(); + } +} diff --git a/app/src/main/java/com/fastaccess/data/dao/transformer/MapTransformer.java b/app/src/main/java/com/fastaccess/data/dao/transformer/MapTransformer.java new file mode 100644 index 00000000..6f531f6c --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/transformer/MapTransformer.java @@ -0,0 +1,24 @@ +package com.fastaccess.data.dao.transformer; + +import android.support.annotation.NonNull; + +import com.fastaccess.data.dao.GithubFileModel; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.siimkinks.sqlitemagic.annotation.transformer.DbValueToObject; +import com.siimkinks.sqlitemagic.annotation.transformer.ObjectToDbValue; +import com.siimkinks.sqlitemagic.annotation.transformer.Transformer; + +import java.lang.reflect.Type; + +@Transformer public final class MapTransformer { + + @ObjectToDbValue @NonNull public static String objectToDbValue(GithubFileModel javaObject) { + return javaObject == null ? "{}" : new Gson().toJson(javaObject); + } + + @DbValueToObject @NonNull public static GithubFileModel dbValueToObject(String dbObject) { + Type type = new TypeToken() {}.getType(); + return dbObject != null ? new Gson().fromJson(dbObject, type) : new GithubFileModel(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/data/dao/transformer/ReleasesAssetsTransformer.java b/app/src/main/java/com/fastaccess/data/dao/transformer/ReleasesAssetsTransformer.java new file mode 100644 index 00000000..6ae1bd91 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/transformer/ReleasesAssetsTransformer.java @@ -0,0 +1,24 @@ +package com.fastaccess.data.dao.transformer; + +import android.support.annotation.NonNull; + +import com.fastaccess.data.dao.ReleasesAssetsListModel; +import com.google.gson.Gson; +import com.siimkinks.sqlitemagic.annotation.transformer.DbValueToObject; +import com.siimkinks.sqlitemagic.annotation.transformer.ObjectToDbValue; +import com.siimkinks.sqlitemagic.annotation.transformer.Transformer; + +/** + * Created by Kosh on 11 Feb 2017, 11:43 PM + */ + +@Transformer public class ReleasesAssetsTransformer { + + @ObjectToDbValue @NonNull public static String objectToDbValue(ReleasesAssetsListModel javaObject) { + return javaObject == null ? "[]" : new Gson().toJson(javaObject); + } + + @DbValueToObject @NonNull public static ReleasesAssetsListModel dbValueToObject(String dbObject) { + return dbObject != null ? new Gson().fromJson(dbObject, ReleasesAssetsListModel.class) : new ReleasesAssetsListModel(); + } +} diff --git a/app/src/main/java/com/fastaccess/data/dao/transformer/UsersListTransformer.java b/app/src/main/java/com/fastaccess/data/dao/transformer/UsersListTransformer.java new file mode 100644 index 00000000..5479520f --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/transformer/UsersListTransformer.java @@ -0,0 +1,24 @@ +package com.fastaccess.data.dao.transformer; + +import android.support.annotation.NonNull; + +import com.fastaccess.data.dao.UsersListModel; +import com.google.gson.Gson; +import com.siimkinks.sqlitemagic.annotation.transformer.DbValueToObject; +import com.siimkinks.sqlitemagic.annotation.transformer.ObjectToDbValue; +import com.siimkinks.sqlitemagic.annotation.transformer.Transformer; + +/** + * Created by Kosh on 11 Feb 2017, 11:43 PM + */ + +@Transformer public class UsersListTransformer { + + @ObjectToDbValue @NonNull public static String objectToDbValue(UsersListModel javaObject) { + return javaObject == null ? "[]" : new Gson().toJson(javaObject); + } + + @DbValueToObject @NonNull public static UsersListModel dbValueToObject(String dbObject) { + return dbObject != null ? new Gson().fromJson(dbObject, UsersListModel.class) : new UsersListModel(); + } +} diff --git a/app/src/main/java/com/fastaccess/data/dao/types/EventsType.java b/app/src/main/java/com/fastaccess/data/dao/types/EventsType.java new file mode 100644 index 00000000..47653a6e --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/types/EventsType.java @@ -0,0 +1,34 @@ +package com.fastaccess.data.dao.types; + + +public enum EventsType { + WatchEvent("starred"), + CreateEvent("created repository"), + CommitCommentEvent("committed"), + DownloadEvent("downloaded"), + FollowEvent("followed"), + ForkEvent("forked"), + GistEvent("created gist"), + GollumEvent("gollum"), + IssueCommentEvent("commented on issue"), + IssuesEvent("created issue"), + MemberEvent("member"), + PublicEvent("public"), + PullRequestEvent("pull request"), + PullRequestReviewCommentEvent("PR comment preview"), + PushEvent("pushed"), + StatusEvent("status"), + TeamAddEvent("team"), + DeleteEvent("deleted"), + ReleaseEvent("released"), + Unhandled("unknown"); + String type; + + EventsType(String type) { + this.type = type; + } + + public String getType() { + return type; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/data/dao/types/FilesType.java b/app/src/main/java/com/fastaccess/data/dao/types/FilesType.java new file mode 100644 index 00000000..e87c263b --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/types/FilesType.java @@ -0,0 +1,24 @@ +package com.fastaccess.data.dao.types; + +import android.support.annotation.DrawableRes; + +import com.fastaccess.R; + +/** + * Created by Kosh on 17 Feb 2017, 7:45 PM + */ + +public enum FilesType { + file(R.drawable.ic_file_document), + dir(R.drawable.ic_folder); + + int icon; + + FilesType(int icon) { + this.icon = icon; + } + + @DrawableRes public int getIcon() { + return icon; + } +} diff --git a/app/src/main/java/com/fastaccess/data/dao/types/GitEntryType.java b/app/src/main/java/com/fastaccess/data/dao/types/GitEntryType.java new file mode 100644 index 00000000..ac7457a6 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/types/GitEntryType.java @@ -0,0 +1,7 @@ +package com.fastaccess.data.dao.types; + +public enum GitEntryType { + commit, + tree, + blob +} \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/data/dao/types/IssueEventType.java b/app/src/main/java/com/fastaccess/data/dao/types/IssueEventType.java new file mode 100644 index 00000000..33e13220 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/types/IssueEventType.java @@ -0,0 +1,36 @@ +package com.fastaccess.data.dao.types; + +import com.fastaccess.R; +import com.google.gson.annotations.SerializedName; + +public enum IssueEventType { + assigned(R.drawable.ic_profile), + closed(R.drawable.ic_issue_closed), + commented(R.drawable.ic_comment), + committed(R.drawable.ic_announcement), + demilestoned(R.drawable.ic_milestone), + head_ref_deleted(R.drawable.ic_trash), + head_ref_restored(R.drawable.ic_redo), + labeled(R.drawable.ic_label), + locked(R.drawable.ic_lock), + mentioned(R.drawable.ic_at), + merged(R.drawable.ic_fork), + milestoned(R.drawable.ic_milestone), + referenced(R.drawable.ic_format_quote), + renamed(R.drawable.ic_edit), + reopened(R.drawable.ic_issue_opened), + subscribed(R.drawable.ic_announcement), + unassigned(R.drawable.ic_announcement), + unlabeled(R.drawable.ic_label), + unlocked(R.drawable.ic_unlock), + unsubscribed(R.drawable.ic_announcement), + @SerializedName("cross-referenced")crossReferenced(R.drawable.ic_format_quote); + + int iconResId; + + IssueEventType(int iconResId) {this.iconResId = iconResId;} + + public int getIconResId() { + return iconResId; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/data/dao/types/IssueState.java b/app/src/main/java/com/fastaccess/data/dao/types/IssueState.java new file mode 100644 index 00000000..1df6ffba --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/types/IssueState.java @@ -0,0 +1,20 @@ +package com.fastaccess.data.dao.types; + +import android.support.annotation.StringRes; + +import com.fastaccess.R; + +public enum IssueState { + open(R.string.opened), + closed(R.string.closed); + + int status; + + IssueState(@StringRes int status) { + this.status = status; + } + + @StringRes public int getStatus() { + return status; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/data/dao/types/NotificationReason.java b/app/src/main/java/com/fastaccess/data/dao/types/NotificationReason.java new file mode 100644 index 00000000..7600a115 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/types/NotificationReason.java @@ -0,0 +1,12 @@ +package com.fastaccess.data.dao.types; + +public enum NotificationReason { + subscribed, + manual, + author, + comment, + mention, + team_mention, + state_change, + assign, +} \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/data/service/GistService.java b/app/src/main/java/com/fastaccess/data/service/GistService.java new file mode 100644 index 00000000..4019058f --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/service/GistService.java @@ -0,0 +1,72 @@ +package com.fastaccess.data.service; + +import android.support.annotation.NonNull; + +import com.fastaccess.data.dao.CommentRequestModel; +import com.fastaccess.data.dao.CommentsModel; +import com.fastaccess.data.dao.CreateGistModel; +import com.fastaccess.data.dao.GistsModel; +import com.fastaccess.data.dao.Pageable; + +import retrofit2.Response; +import retrofit2.http.Body; +import retrofit2.http.DELETE; +import retrofit2.http.GET; +import retrofit2.http.PATCH; +import retrofit2.http.POST; +import retrofit2.http.PUT; +import retrofit2.http.Path; +import retrofit2.http.Query; +import rx.Observable; + +/** + * Created by Kosh on 20 Nov 2016, 10:28 AM + */ + +public interface GistService { + + @POST("gists") Observable createGist(@Body CreateGistModel gistBody); + + @POST("gists/{id}") Observable editGist(@Body CreateGistModel gistBody); + + @DELETE("gists/{id}") Observable> deleteGist(@Path("id") String id); + + @GET("gists/public") Observable> getPublicGists(@Query("per_page") int perPage, @Query("page") int page); + + @GET("gists") Observable> getMyGists(@Query("per_page") int perPage, @Query("page") int page); + + @GET("users/{username}/gists") + Observable> getUserGists(@NonNull @Path("username") String username, + @Query("per_page") int perPage, @Query("page") int page); + + @GET("gists/{id}") + Observable getGist(@Path("id") String id); + + @GET("gists/{id}/comments") + Observable> getGistComments(@NonNull @Path("id") String id, @Query("page") int page); + + @GET("gists/{gist_id}/comments/{id}") + Observable getGistComment(@Path("gist_id") String gistId, @Path("id") String id); + + @POST("gists/{gist_id}/comments") + Observable createGistComment(@Path("gist_id") String gistId, @Body CommentRequestModel body); + + @PATCH("gists/{gist_id}/comments/{id}") + Observable editGistComment(@Path("gist_id") String gistId, @Path("id") long id, @Body CommentRequestModel body); + + @DELETE("gists/{gist_id}/comments/{id}") + Observable> deleteGistComment(@Path("gist_id") String gistId, @Path("id") long id); + + @GET("gists/{gist_id}/star") + Observable> checkGistStar(@Path("gist_id") @NonNull String gistId); + + @PUT("gists/{gist_id}/star") + Observable> starGist(@Path("gist_id") @NonNull String gistId); + + @DELETE("gists/{gist_id}/star") + Observable> unStarGist(@Path("gist_id") @NonNull String gistId); + + @POST("gists/{gist_id}/forks") + Observable> forkGist(@Path("gist_id") @NonNull String gistId); + +} diff --git a/app/src/main/java/com/fastaccess/data/service/IssueService.java b/app/src/main/java/com/fastaccess/data/service/IssueService.java new file mode 100644 index 00000000..988d659e --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/service/IssueService.java @@ -0,0 +1,78 @@ +package com.fastaccess.data.service; + + +import com.fastaccess.data.dao.CommentRequestModel; +import com.fastaccess.data.dao.CommentsModel; +import com.fastaccess.data.dao.IssueEventModel; +import com.fastaccess.data.dao.IssueModel; +import com.fastaccess.data.dao.IssueRequestModel; +import com.fastaccess.data.dao.Pageable; + +import retrofit2.Response; +import retrofit2.http.Body; +import retrofit2.http.DELETE; +import retrofit2.http.GET; +import retrofit2.http.Headers; +import retrofit2.http.PATCH; +import retrofit2.http.POST; +import retrofit2.http.PUT; +import retrofit2.http.Path; +import retrofit2.http.Query; +import rx.Observable; + +public interface IssueService { + + @GET("repos/{owner}/{repo}/issues") + Observable> getRepositoryIssues(@Path("owner") String owner, @Path("repo") String repo, + @Query("state") String state, @Query("page") int page); + + @GET("repos/{owner}/{repo}/issues/{number}") +// @Headers("Accept: application/vnd.github.VERSION.html") + Observable getIssue(@Path("owner") String owner, @Path("repo") String repo, + @Path("number") int number); + + @GET("repos/{owner}/{repo}/issues/{issue_number}/events") + Observable> getTimeline(@Path("owner") String owner, @Path("repo") String repo, + @Path("issue_number") int issue_number, + @Query("page") int page); + + @POST("repos/{owner}/{repo}/issues") + Observable createIssue(@Path("owner") String owner, @Path("repo") String repo, + @Body IssueRequestModel issue); + + @PATCH("repos/{owner}/{repo}/issues/{number}") + Observable editIssue(@Path("owner") String owner, @Path("repo") String repo, + @Path("number") int number, + @Body IssueRequestModel issue); + + @Headers("Content-Length: 0") + @PUT("repos/{owner}/{repo}/issues/{number}/lock") + Observable> lockIssue(@Path("owner") String owner, @Path("repo") String repo, @Path("number") int number); + + @DELETE("repos/{owner}/{repo}/issues/{number}/lock") + Observable> unlockIssue(@Path("owner") String owner, @Path("repo") String repo, @Path("number") int number); + + + @GET("repos/{owner}/{repo}/issues/{number}/comments") +// @Headers("Accept: application/vnd.github.VERSION.html") + Observable> getIssueComments(@Path("owner") String owner, + @Path("repo") String repo, + @Path("number") int number, + @Query("page") int page); + + @GET("repos/{owner}/{repo}/issues/{number}/comments/{id}") + Observable getIssueComment(@Path("owner") String owner, @Path("repo") String repo, + @Path("number") int number, @Path("id") long id); + + @POST("repos/{owner}/{repo}/issues/{number}/comments") + Observable createIssueComment(@Path("owner") String owner, @Path("repo") String repo, + @Path("number") int number, @Body CommentRequestModel body); + + @PATCH("repos/{owner}/{repo}/issues/comments/{id}") + Observable editIssueComment(@Path("owner") String owner, @Path("repo") String repo, @Path("id") long id, + @Body CommentRequestModel body); + + @DELETE("repos/{owner}/{repo}/issues/comments/{id}") + Observable> deleteIssueComment(@Path("owner") String owner, @Path("repo") String repo, @Path("id") long id); + +} \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/data/service/PullRequestService.java b/app/src/main/java/com/fastaccess/data/service/PullRequestService.java new file mode 100644 index 00000000..b3eb0f7c --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/service/PullRequestService.java @@ -0,0 +1,49 @@ +package com.fastaccess.data.service; + +import com.fastaccess.data.dao.CommitModel; +import com.fastaccess.data.dao.FilesListModel; +import com.fastaccess.data.dao.MergeRequestModel; +import com.fastaccess.data.dao.MergeResponseModel; +import com.fastaccess.data.dao.Pageable; +import com.fastaccess.data.dao.PullRequestModel; + +import retrofit2.Response; +import retrofit2.http.Body; +import retrofit2.http.GET; +import retrofit2.http.PUT; +import retrofit2.http.Path; +import retrofit2.http.Query; +import rx.Observable; + +/** + * Created by Kosh on 15 Dec 2016, 10:21 PM + */ + +public interface PullRequestService { + + @GET("repos/{owner}/{repo}/pulls") + Observable> getPullRequests(@Path("owner") String owner, @Path("repo") String repo, + @Query("state") String state, @Query("page") int page); + + @GET("repos/{owner}/{repo}/pulls/{number}") + Observable getPullRequest(@Path("owner") String owner, @Path("repo") String repo, @Path("number") long number); + + @PUT("repos/{owner}/{repo}/pulls/{number}/merge") + Observable mergePullRequest(@Path("owner") String owner, @Path("repo") String repo, + @Path("number") long number, @Body MergeRequestModel body); + + + @GET("repos/{owner}/{repo}/pulls/{number}/commits") + Observable> getPullRequestCommits(@Path("owner") String owner, @Path("repo") String repo, + @Path("number") long number, + @Query("page") int page); + + @GET("repos/{owner}/{repo}/pulls/{number}/files") + Observable> getPullRequestFiles(@Path("owner") String owner, @Path("repo") String repo, + @Path("number") long number, + @Query("page") int page); + + @GET("repos/{owner}/{repo}/pulls/{number}/merge") + Observable> hasPullRequestBeenMerged(@Path("owner") String owner, @Path("repo") String repo, + @Path("number") long number); +} diff --git a/app/src/main/java/com/fastaccess/data/service/RepoService.java b/app/src/main/java/com/fastaccess/data/service/RepoService.java new file mode 100644 index 00000000..0fe54e44 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/service/RepoService.java @@ -0,0 +1,104 @@ +package com.fastaccess.data.service; + +import android.support.annotation.NonNull; + +import com.fastaccess.data.dao.CommentRequestModel; +import com.fastaccess.data.dao.CommentsModel; +import com.fastaccess.data.dao.CommitModel; +import com.fastaccess.data.dao.MarkdownModel; +import com.fastaccess.data.dao.Pageable; +import com.fastaccess.data.dao.ReleasesModel; +import com.fastaccess.data.dao.RepoFilesModel; +import com.fastaccess.data.dao.RepoModel; +import com.fastaccess.data.dao.UserModel; + +import retrofit2.Response; +import retrofit2.http.Body; +import retrofit2.http.DELETE; +import retrofit2.http.GET; +import retrofit2.http.Headers; +import retrofit2.http.PATCH; +import retrofit2.http.POST; +import retrofit2.http.PUT; +import retrofit2.http.Path; +import retrofit2.http.Query; +import retrofit2.http.Url; +import rx.Observable; + +/** + * Created by Kosh on 10 Dec 2016, 3:16 PM + */ +public interface RepoService { + + + @GET @Headers("Accept: application/vnd.github.VERSION.raw") + Observable getFileAsStream(@Url String url); + + @POST("markdown") + Observable convertReadmeToHtml(@Body MarkdownModel model); + + @GET("repos/{login}/{repoId}") @Headers({"Accept: application/vnd.github.drax-preview+json"}) + Observable getRepo(@Path("login") String login, @Path("repoId") String repoId); + + @GET @Headers("Accept: application/vnd.github.html") + Observable getReadmeHtml(@NonNull @Url String url); + + @GET("user/starred/{owner}/{repo}") + Observable> checkStarring(@NonNull @Path("owner") String login, @NonNull @Path("repo") String repoId); + + @PUT("user/starred/{owner}/{repo}") + Observable> starRepo(@NonNull @Path("owner") String login, @NonNull @Path("repo") String repoId); + + @DELETE("user/starred/{owner}/{repo}") + Observable> unstarRepo(@NonNull @Path("owner") String login, @NonNull @Path("repo") String repoId); + + @POST("/repos/{owner}/{repo}/forks") + Observable forkRepo(@NonNull @Path("owner") String login, @NonNull @Path("repo") String repoId); + + @GET("repos/{owner}/{repo}/subscribers") + Observable> getRepoWatchers(@Path("owner") String owner, @Path("repo") String repo, @Query("page") int page); + + @GET("users/{username}/subscriptions") + Observable> getWatchedRepos(@Path("username") String username, @Query("page") int page); + + @GET("user/subscriptions/{owner}/{repo}") + Observable> isWatchingRepo(@Path("owner") String owner, @Path("repo") String repo); + + @PUT("user/subscriptions/{owner}/{repo}") + Observable> watchRepo(@Path("owner") String owner, @Path("repo") String repo); + + @DELETE("user/subscriptions/{owner}/{repo}") + Observable> unwatchRepo(@Path("owner") String owner, @Path("repo") String repo); + + @GET("repos/{owner}/{repo}/commits") + Observable> getCommits(@Path("owner") String owner, @Path("repo") String repo, @Query("page") int page); + + @GET("repos/{owner}/{repo}/releases") + Observable> getReleases(@Path("owner") String owner, @Path("repo") String repo, @Query("page") int page); + + @GET("repos/{owner}/{repo}/contributors") + Observable> getContributors(@Path("owner") String owner, @Path("repo") String repo, @Query("page") int page); + + @GET("repos/{owner}/{repo}/commits/{sha}") + Observable getCommit(@Path("owner") String owner, @Path("repo") String repo, @Path("sha") String sha); + + @GET("repos/{owner}/{repo}/commits/{sha}/comments") + Observable> getCommitComments(@NonNull @Path("owner") String owner, @NonNull @Path("repo") String repo, + @NonNull @Path("sha") String ref, @Query("page") int page); + + @POST("repos/{owner}/{repo}/commits/{sha}/comments") + Observable postCommitComment(@NonNull @Path("owner") String owner, @NonNull @Path("repo") String repo, + @NonNull @Path("sha") String ref, @Body CommentRequestModel model); + + @PATCH("repos/{owner}/{repo}/comments/{id}") + Observable editCommitComment(@Path("owner") String owner, @Path("repo") String repo, @Path("id") long id, + @Body CommentRequestModel body); + + @DELETE("repos/{owner}/{repo}/comments/{id}") + Observable> deleteComment(@Path("owner") String owner, @Path("repo") String repo, @Path("id") long id); + + @GET("repos/{owner}/{repo}/contents/{getPath}") + Observable> getRepoFiles(@NonNull @Path("owner") String owner, @NonNull @Path("repo") String repo, + @NonNull @Path("getPath") String path); + +} diff --git a/app/src/main/java/com/fastaccess/data/service/SearchService.java b/app/src/main/java/com/fastaccess/data/service/SearchService.java new file mode 100644 index 00000000..3f69890e --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/service/SearchService.java @@ -0,0 +1,30 @@ +package com.fastaccess.data.service; + +import com.fastaccess.data.dao.IssueModel; +import com.fastaccess.data.dao.Pageable; +import com.fastaccess.data.dao.RepoModel; +import com.fastaccess.data.dao.SearchCodeModel; +import com.fastaccess.data.dao.UserModel; + +import retrofit2.http.GET; +import retrofit2.http.Query; +import rx.Observable; + +/** + * Created by Kosh on 08 Dec 2016, 9:07 PM + */ + +public interface SearchService { + + @GET("search/repositories") + Observable> searchRepositories(@Query("q") String query, @Query("page") long page); + + @GET("search/code") + Observable> searchCode(@Query("q") String query, @Query("page") long page); + + @GET("search/issues") + Observable> searchIssues(@Query("q") String query, @Query("page") long page); + + @GET("search/users") + Observable> searchUsers(@Query("q") String query, @Query("page") long page); +} diff --git a/app/src/main/java/com/fastaccess/data/service/UserRestService.java b/app/src/main/java/com/fastaccess/data/service/UserRestService.java new file mode 100644 index 00000000..cb74d8a4 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/service/UserRestService.java @@ -0,0 +1,63 @@ +package com.fastaccess.data.service; + +import android.support.annotation.NonNull; + +import com.fastaccess.data.dao.AccessTokenModel; +import com.fastaccess.data.dao.EventsModel; +import com.fastaccess.data.dao.LoginModel; +import com.fastaccess.data.dao.Pageable; +import com.fastaccess.data.dao.RepoModel; +import com.fastaccess.data.dao.UserModel; + +import retrofit2.Response; +import retrofit2.http.DELETE; +import retrofit2.http.Field; +import retrofit2.http.FormUrlEncoded; +import retrofit2.http.GET; +import retrofit2.http.POST; +import retrofit2.http.PUT; +import retrofit2.http.Path; +import retrofit2.http.Query; +import rx.Observable; + +/** + * Created by Kosh on 08 Feb 2017, 8:54 PM + */ + +public interface UserRestService { + + @FormUrlEncoded @POST("access_token") + Observable getAccessToken(@NonNull @Field("code") String code, + @NonNull @Field("client_id") String clientId, + @NonNull @Field("client_secret") String clientSecret, + @NonNull @Field("state") String state, + @NonNull @Field("redirect_uri") String redirectUrl); + + @GET("user") Observable getUser(); + + @GET("users/{username}") Observable getUser(@Path("username") @NonNull String username); + + @GET("users/{username}/received_events") + Observable> getReceivedEvents(@NonNull @Path("username") String userName, @Query("page") int page); + + @GET("users/{username}/repos?sort=pushed&direction=desc") Observable> + getRepos(@Path("username") @NonNull String username, @Query("page") int page); + + @GET("users/{username}/starred") Observable> + getStarred(@Path("username") @NonNull String username, @Query("page") int page); + + @GET("users/{username}/following") + Observable> getFollowing(@Path("username") @NonNull String username, @Query("page") int page); + + @GET("users/{username}/followers") + Observable> getFollowers(@Path("username") @NonNull String username, @Query("page") int page); + + @GET("user/following/{username}") + Observable> getFollowStatus(@Path("username") @NonNull String username); + + @PUT("/user/following/{username}") + Observable> followUser(@Path("username") @NonNull String username); + + @DELETE("/user/following/{username}") + Observable> unfollowUser(@Path("username") @NonNull String username); +} diff --git a/app/src/main/java/com/fastaccess/helper/ActivityHelper.java b/app/src/main/java/com/fastaccess/helper/ActivityHelper.java new file mode 100644 index 00000000..a4461ff2 --- /dev/null +++ b/app/src/main/java/com/fastaccess/helper/ActivityHelper.java @@ -0,0 +1,118 @@ +package com.fastaccess.helper; + +import android.app.Activity; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.Intent; +import android.graphics.Rect; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.customtabs.CustomTabsIntent; +import android.support.v4.app.ActivityOptionsCompat; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.ShareCompat; +import android.support.v4.content.ContextCompat; +import android.support.v4.util.Pair; +import android.view.View; + +import com.fastaccess.R; + +import java.util.List; + +/** + * Created by Kosh on 12/12/15 10:51 PM + */ +public class ActivityHelper { + + @Nullable public static Activity getActivity(@Nullable Context cont) { + if (cont == null) return null; + else if (cont instanceof Activity) return (Activity) cont; + else if (cont instanceof ContextWrapper) return getActivity(((ContextWrapper) cont).getBaseContext()); + return null; + } + + public static void startCustomTab(@NonNull Activity context) { + CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(); + builder.setToolbarColor(ContextCompat.getColor(context, R.color.primary)); + CustomTabsIntent tabsIntent = builder.build(); + tabsIntent.launchUrl(context, Uri.parse("https://github.com/k0shk0sh/FastAccess"));//TODO + } + + public static void startCustomTab(@NonNull Activity context, @NonNull Uri url) { + CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(); + builder.setToolbarColor(ContextCompat.getColor(context, R.color.primary)); + builder.setShowTitle(false); + CustomTabsIntent tabsIntent = builder.build(); + tabsIntent.launchUrl(context, url); + } + + public static void startCustomTab(@NonNull Activity context, @NonNull String url) { + startCustomTab(context, Uri.parse(url)); + } + + public static void forceOpenInBrowser(@NonNull Context context, @NonNull String url) { + try { + Uri uri = Uri.parse("googlechrome://navigate?url=" + url); + Intent i = new Intent(Intent.ACTION_VIEW, uri); + i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(i); + } catch (ActivityNotFoundException e) { + Uri uri = Uri.parse(url); + Intent i = new Intent(Intent.ACTION_VIEW, uri); + i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(i); + } + } + + @SafeVarargs public static void start(Activity activity, Class cl, Pair... sharedElements) { + Intent intent = new Intent(activity, cl); + ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, sharedElements); + activity.startActivity(intent, options.toBundle()); + } + + public static void start(Activity activity, Intent intent, View sharedElement) { + ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, + sharedElement, ViewHelper.getTransitionName(sharedElement)); + activity.startActivity(intent, options.toBundle()); + } + + public static void startReveal(Activity activity, Intent intent, View sharedElement) { + Rect rect = ViewHelper.getLayoutPosition(sharedElement); + ActivityOptionsCompat options = ActivityOptionsCompat.makeClipRevealAnimation(sharedElement, rect.centerX(), rect.centerY(), sharedElement + .getWidth(), sharedElement.getHeight()); + activity.startActivity(intent, options.toBundle()); + } + + @SafeVarargs public static void start(Activity activity, Intent intent, Pair... sharedElements) { + ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, sharedElements); + activity.startActivity(intent, options.toBundle()); + + } + + public static void shareUrl(@NonNull Context context, @NonNull String url) { + Activity activity = getActivity(context); + if (activity == null) throw new IllegalArgumentException("Context give is not an instance of activity " + context.getClass().getName()); + ShareCompat.IntentBuilder.from(activity) + .setChooserTitle(context.getString(R.string.share)) + .setType("text/*") + .setText(url) + .startChooser(); + + } + + @SuppressWarnings("RestrictedApi") + @Nullable public static Fragment getVisibleFragment(@NonNull FragmentManager manager) { + List fragments = manager.getFragments(); + if (fragments != null && !fragments.isEmpty()) { + for (Fragment fragment : fragments) { + if (fragment != null && fragment.isVisible()) { + return fragment; + } + } + } + return null; + } +} diff --git a/app/src/main/java/com/fastaccess/helper/AnimHelper.java b/app/src/main/java/com/fastaccess/helper/AnimHelper.java new file mode 100644 index 00000000..e566f966 --- /dev/null +++ b/app/src/main/java/com/fastaccess/helper/AnimHelper.java @@ -0,0 +1,158 @@ +package com.fastaccess.helper; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.graphics.Rect; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.UiThread; +import android.support.v4.view.ViewCompat; +import android.view.View; +import android.view.ViewAnimationUtils; +import android.view.ViewPropertyAnimator; +import android.view.ViewTreeObserver; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.Interpolator; +import android.view.animation.LinearInterpolator; + +import java.util.Arrays; +import java.util.List; + + +/** + * Created by Kosh on 27 May 2016, 9:04 PM + */ + +public class AnimHelper { + + interface AnimationCallback { + void onAnimationEnd(); + + void onAnimationStart(); + } + + private static final Interpolator interpolator = new LinearInterpolator(); + + @UiThread public static void animateVisibility(@Nullable final View view, final boolean show) { + animateVisibility(view, show, null); + } + + @SuppressWarnings("WeakerAccess") @UiThread + public static void animateVisibility(@Nullable final View view, final boolean show, @Nullable final AnimationCallback callback) { + if (view == null) { + return; + } + if (!ViewCompat.isAttachedToWindow(view)) { + view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + @Override public boolean onPreDraw() { + view.getViewTreeObserver().removeOnPreDrawListener(this); + animateSafeVisibility(show, view, callback); + return true; + } + }); + } else { + animateSafeVisibility(show, view, callback); + } + } + + @UiThread private static void animateSafeVisibility(final boolean show, @NonNull final View view, @Nullable final AnimationCallback callback) { + view.clearAnimation(); + if (view.getAnimation() != null) view.getAnimation().cancel(); + ViewPropertyAnimator animator = view.animate().setDuration(200).alpha(show ? 1F : 0F).setInterpolator(new AccelerateInterpolator()) + .setListener(new AnimatorListenerAdapter() { + @Override public void onAnimationStart(Animator animation) { + super.onAnimationStart(animation); + if (callback != null) callback.onAnimationStart(); + if (show) { + view.setScaleX(1); + view.setScaleY(1); + view.setVisibility(View.VISIBLE); + } + } + + @Override public void onAnimationEnd(@NonNull Animator animation) { + super.onAnimationEnd(animation); + if (!show) { + view.setVisibility(View.GONE); + view.setScaleX(0); + view.setScaleY(0); + } + if (callback != null) callback.onAnimationEnd(); + animation.removeListener(this); + view.clearAnimation(); + } + }); + animator.scaleX(show ? 1 : 0).scaleY(show ? 1 : 0); + } + + @UiThread @NonNull private static List getBeats(@NonNull View view) { + ObjectAnimator[] animator = new ObjectAnimator[]{ + ObjectAnimator.ofFloat(view, "scaleY", 1, 1.1f, 1), + ObjectAnimator.ofFloat(view, "scaleX", 1, 1.1f, 1) + }; + return Arrays.asList(animator); + } + + @UiThread public static void startBeatsAnimation(@NonNull View view) { + view.clearAnimation(); + if (view.getAnimation() != null) { + view.getAnimation().cancel(); + } + List animators = getBeats(view); + for (ObjectAnimator anim : animators) { + anim.setDuration(300).start(); + anim.setInterpolator(interpolator); + } + } + + @UiThread public static void circularReveal(final View mRevealView, final View from, final boolean show) { + if (ViewCompat.isAttachedToWindow(mRevealView)) { + if (show) { + if (mRevealView.isShown()) return; + } else { + if (!mRevealView.isShown()) { + return; + } + } + reveal(mRevealView, show, from); + } else { + mRevealView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + @Override public boolean onPreDraw() { + mRevealView.getViewTreeObserver().removeOnPreDrawListener(this); + if (show) { + if (mRevealView.isShown()) return true; + } else { + if (!mRevealView.isShown()) { + return true; + } + } + reveal(mRevealView, show, from); + return true; + } + }); + } + } + + @UiThread private static void reveal(final View mRevealView, final boolean show, View from) { + Rect rect = ViewHelper.getLayoutPosition(from); + int x = (int) rect.exactCenterX(); + int y = (int) rect.exactCenterY(); + Animator animator = ViewAnimationUtils.createCircularReveal(mRevealView, x, y, 0, Math.max(rect.width(), rect.height())); + animator.setDuration(400L); + animator.setInterpolator(new AccelerateDecelerateInterpolator()); + mRevealView.setVisibility(View.VISIBLE); + if (!show) { + animator.addListener(new AnimatorListenerAdapter() { + @Override public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + mRevealView.setVisibility(View.GONE); + animation.removeListener(this); + } + }); + animator.start(); + } + } + +} diff --git a/app/src/main/java/com/fastaccess/helper/AppHelper.java b/app/src/main/java/com/fastaccess/helper/AppHelper.java new file mode 100644 index 00000000..632bc184 --- /dev/null +++ b/app/src/main/java/com/fastaccess/helper/AppHelper.java @@ -0,0 +1,60 @@ +package com.fastaccess.helper; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.Network; +import android.net.NetworkInfo; +import android.os.Build; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.view.View; +import android.view.inputmethod.InputMethodManager; + +/** + * Created by kosh20111 on 18 Oct 2016, 9:29 PM + */ + +public class AppHelper { + + @SuppressWarnings("deprecation") public static boolean isOnline(@NonNull Context context) { + boolean haveConnectedWifi = false; + boolean haveConnectedMobile = false; + try { + ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + if (isM()) { + Network networks = cm.getActiveNetwork(); + NetworkInfo netInfo = cm.getNetworkInfo(networks); + haveConnectedWifi = netInfo.getType() == ConnectivityManager.TYPE_WIFI && netInfo.getState().equals(NetworkInfo.State.CONNECTED); + haveConnectedMobile = netInfo.getType() == ConnectivityManager.TYPE_MOBILE && netInfo.getState().equals(NetworkInfo.State.CONNECTED); + } else { + NetworkInfo[] netInfo = cm.getAllNetworkInfo(); + for (NetworkInfo ni : netInfo) { + if (ni.getTypeName().equalsIgnoreCase("WIFI")) { + if (ni.isConnected()) + haveConnectedWifi = true; + } + if (ni.getTypeName().equalsIgnoreCase("MOBILE")) { + if (ni.isConnected()) + haveConnectedMobile = true; + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return haveConnectedWifi || haveConnectedMobile; + } + + @SuppressWarnings("WeakerAccess") public static boolean isM() {return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;} + + public static void hideKeyboard(@NonNull View view) { + InputMethodManager inputManager = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + inputManager.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + + @Nullable public static Fragment getFragmentByTag(@NonNull FragmentManager fragmentManager, @NonNull String tag) { + return fragmentManager.findFragmentByTag(tag); + } +} diff --git a/app/src/main/java/com/fastaccess/helper/BundleConstant.java b/app/src/main/java/com/fastaccess/helper/BundleConstant.java new file mode 100644 index 00000000..23656670 --- /dev/null +++ b/app/src/main/java/com/fastaccess/helper/BundleConstant.java @@ -0,0 +1,42 @@ +package com.fastaccess.helper; + +import android.support.annotation.StringDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import static com.fastaccess.helper.BundleConstant.ExtraTYpe.EDIT_GIST_COMMENT_EXTRA; +import static com.fastaccess.helper.BundleConstant.ExtraTYpe.FOR_RESULT_EXTRA; +import static com.fastaccess.helper.BundleConstant.ExtraTYpe.NEW_GIST_COMMENT_EXTRA; + +/** + * Created by Kosh on 12 Nov 2016, 3:55 PM + */ + +public class BundleConstant { + public static final String ITEM = "item"; + public static final String ID = "id"; + public static final String EXTRA = "extra"; + public static final String EXTRA_TWO = "extra_id"; + public static final String EXTRA_THREE = "extra2_id"; + public static final String EXTRA_FOUR = "extra3_id"; + public static final String EXTRA_TYPE = "extra_type"; + public static final int REQUEST_CODE = 2016; + + + @StringDef({ + NEW_GIST_COMMENT_EXTRA, + EDIT_GIST_COMMENT_EXTRA, + FOR_RESULT_EXTRA + }) + + @Retention(RetentionPolicy.SOURCE) public @interface ExtraTYpe { + String FOR_RESULT_EXTRA = "for_result_extra"; + String EDIT_GIST_COMMENT_EXTRA = "edit_comment_extra"; + String NEW_GIST_COMMENT_EXTRA = "new_gist_comment_extra"; + String EDIT_ISSUE_COMMENT_EXTRA = "edit_issue_comment_extra"; + String NEW_ISSUE_COMMENT_EXTRA = "new_issue_comment_extra"; + String EDIT_COMMIT_COMMENT_EXTRA = "edit_commit_comment_extra"; + String NEW_COMMIT_COMMENT_EXTRA = "new_commit_comment_extra"; + } +} diff --git a/app/src/main/java/com/fastaccess/helper/Bundler.java b/app/src/main/java/com/fastaccess/helper/Bundler.java new file mode 100644 index 00000000..9cbeddfe --- /dev/null +++ b/app/src/main/java/com/fastaccess/helper/Bundler.java @@ -0,0 +1,195 @@ +package com.fastaccess.helper; + +import android.os.Bundle; +import android.os.IBinder; +import android.os.Parcelable; +import android.support.annotation.NonNull; +import android.util.SparseArray; + +import java.io.Serializable; +import java.util.ArrayList; + +/** + * Created by Kosh on 23 May 2016, 3:37 PM + */ + +public class Bundler { + + private Bundle bundle; + + private Bundler() { + bundle = new Bundle(); + } + + public static Bundler start() { + return new Bundler(); + } + + public Bundler put(@NonNull String key, boolean value) { + bundle.putBoolean(key, value); + return this; + } + + public Bundler put(@NonNull String key, boolean[] value) { + bundle.putBooleanArray(key, value); + return this; + } + + public Bundler put(@NonNull String key, IBinder value) { + // Uncommment this line if your minimum sdk version is API level 18 + //start.putBinder(key, value); + return this; + } + + public Bundler put(@NonNull String key, int value) { + bundle.putInt(key, value); + return this; + } + + public Bundler put(@NonNull String key, int[] value) { + bundle.putIntArray(key, value); + return this; + } + + public Bundler putIntegerArrayList(@NonNull String key, ArrayList value) { + bundle.putIntegerArrayList(key, value); + return this; + } + + public Bundler put(@NonNull String key, Bundle value) { + bundle.putBundle(key, value); + return this; + } + + public Bundler put(@NonNull String key, byte value) { + bundle.putByte(key, value); + return this; + } + + public Bundler put(@NonNull String key, byte[] value) { + bundle.putByteArray(key, value); + return this; + } + + public Bundler put(@NonNull String key, String value) { + bundle.putString(key, value); + return this; + } + + public Bundler put(@NonNull String key, String[] value) { + bundle.putStringArray(key, value); + return this; + } + + public Bundler putStringArrayList(@NonNull String key, ArrayList value) { + bundle.putStringArrayList(key, value); + return this; + } + + public Bundler put(@NonNull String key, long value) { + bundle.putLong(key, value); + return this; + } + + public Bundler put(@NonNull String key, long[] value) { + bundle.putLongArray(key, value); + return this; + } + + public Bundler put(@NonNull String key, float value) { + bundle.putFloat(key, value); + return this; + } + + public Bundler put(@NonNull String key, float[] value) { + bundle.putFloatArray(key, value); + return this; + } + + public Bundler put(@NonNull String key, char value) { + bundle.putChar(key, value); + return this; + } + + public Bundler put(@NonNull String key, char[] value) { + bundle.putCharArray(key, value); + return this; + } + + public Bundler put(@NonNull String key, CharSequence value) { + bundle.putCharSequence(key, value); + return this; + } + + public Bundler put(@NonNull String key, CharSequence[] value) { + bundle.putCharSequenceArray(key, value); + return this; + } + + public Bundler putCharSequenceArrayList(@NonNull String key, ArrayList value) { + bundle.putCharSequenceArrayList(key, value); + return this; + } + + public Bundler put(@NonNull String key, double value) { + bundle.putDouble(key, value); + return this; + } + + public Bundler put(@NonNull String key, double[] value) { + bundle.putDoubleArray(key, value); + return this; + } + + public Bundler put(@NonNull String key, Parcelable value) { + bundle.putParcelable(key, value); + return this; + } + + public Bundler put(@NonNull String key, Parcelable[] value) { + bundle.putParcelableArray(key, value); + return this; + } + + public Bundler putParcelableArrayList(@NonNull String key, ArrayList value) { + bundle.putParcelableArrayList(key, value); + return this; + } + + public Bundler putSparseParcelableArray(@NonNull String key, SparseArray value) { + bundle.putSparseParcelableArray(key, value); + return this; + } + + public Bundler put(@NonNull String key, short value) { + bundle.putShort(key, value); + return this; + } + + public Bundler put(@NonNull String key, short[] value) { + bundle.putShortArray(key, value); + return this; + } + + public Bundler put(@NonNull String key, Serializable value) { + bundle.putSerializable(key, value); + return this; + } + + public Bundler putAll(Bundle map) { + bundle.putAll(map); + return this; + } + + /** + * Get the underlying start. + */ + public Bundle get() { + return bundle; + } + + public Bundle end() { + return get(); + } + +} diff --git a/app/src/main/java/com/fastaccess/helper/FileHelper.java b/app/src/main/java/com/fastaccess/helper/FileHelper.java new file mode 100644 index 00000000..d8f6e4c4 --- /dev/null +++ b/app/src/main/java/com/fastaccess/helper/FileHelper.java @@ -0,0 +1,17 @@ +package com.fastaccess.helper; + +import android.os.Environment; +import android.webkit.MimeTypeMap; + +/** + * Created by kosh20111 on 10/7/2015. CopyRights @ Innov8tif + */ +public class FileHelper { + public static String getExtension(String file) { + return MimeTypeMap.getFileExtensionFromUrl(file); + } + + public static String getDownloadDirectory() { + return Environment.DIRECTORY_DOWNLOADS; + } +} diff --git a/app/src/main/java/com/fastaccess/helper/InputHelper.java b/app/src/main/java/com/fastaccess/helper/InputHelper.java new file mode 100644 index 00000000..45e308d9 --- /dev/null +++ b/app/src/main/java/com/fastaccess/helper/InputHelper.java @@ -0,0 +1,68 @@ +package com.fastaccess.helper; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.TextInputLayout; +import android.text.TextUtils; +import android.widget.EditText; +import android.widget.TextView; + +/** + * Created by kosh20111 on 3/11/2015. CopyRights @ Innov8tif + *

+ * Input Helper to validate stuff related to input fields. + */ +public class InputHelper { + + + private static boolean isWhiteSpaces(String s) { + return s != null && s.matches("\\s+"); + } + + public static boolean isEmpty(String text) { + return text == null || TextUtils.isEmpty(text) || isWhiteSpaces(text); + } + + public static boolean isEmpty(Object text) { + return text == null || TextUtils.isEmpty(text.toString()) || isWhiteSpaces(text.toString()); + } + + public static boolean isEmpty(EditText text) { + return text == null || isEmpty(text.getText().toString()); + } + + public static boolean isEmpty(TextView text) { + return text == null || isEmpty(text.getText().toString()); + } + + public static boolean isEmpty(TextInputLayout txt) { + return txt == null || isEmpty(txt.getEditText()); + } + + public static String toString(EditText editText) { + return editText.getText().toString(); + } + + public static String toString(TextView editText) { + return editText.getText().toString(); + } + + public static String toString(TextInputLayout textInputLayout) { + return toString(textInputLayout.getEditText()); + } + + public static String toNA(@Nullable String value) { + return isEmpty(value) ? "N/A" : value; + } + + @NonNull public static String toString(@NonNull Object object) { + return !isEmpty(object) ? object.toString() : ""; + } + + public static long toLong(TextView textView) { + if (!isEmpty(textView)) { + return Long.valueOf(toString(textView)); + } + return 0; + } +} diff --git a/app/src/main/java/com/fastaccess/helper/Logger.java b/app/src/main/java/com/fastaccess/helper/Logger.java new file mode 100644 index 00000000..a3da7390 --- /dev/null +++ b/app/src/main/java/com/fastaccess/helper/Logger.java @@ -0,0 +1,78 @@ +package com.fastaccess.helper; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Log; + +import com.fastaccess.BuildConfig; + +import java.util.Arrays; +import java.util.List; + +/** + * Created by Kosh on 04/12/15 11:52 PM. copyrights @ Innov8tif + */ +public class Logger { + + private final static String TAG = Logger.class.getSimpleName(); + + private static final boolean DEBUG = BuildConfig.DEBUG; + + private static void e(@NonNull String tag, @Nullable Object text) { + if (!DEBUG) return; + Log.e(tag, text != null ? text.toString() : "LOGGER IS NULL");//avoid null + } + + private static void d(@NonNull String tag, @Nullable Object text) { + if (!DEBUG) return; + Log.d(tag, text != null ? text.toString() : "LOGGER IS NULL");//avoid null + } + + private static void i(@NonNull String tag, @Nullable Object text) { + if (!DEBUG) return; + Log.i(tag, text != null ? text.toString() : "LOGGER IS NULL");//avoid null + } + + public static void e(@Nullable Object text) {e(getCurrentClassName() + " || " + getCurrentMethodName(), text);} + + public static void d(@Nullable Object text) { + d(getCurrentClassName() + " || " + getCurrentMethodName(), text);//avoid null + } + + public static void i(@Nullable Object text) { + i(getCurrentClassName() + " || " + getCurrentMethodName(), text);//avoid null + } + + public static void e(Object... objects) { + if (objects != null && objects.length > 0) { + e(getCurrentClassName() + " || " + getCurrentMethodName(), Arrays.toString(objects)); + } else { + e(getCurrentClassName() + " || " + getCurrentMethodName(), getCurrentMethodName()); + } + } + + public static void e(List objects) { + if (objects != null) { + e(getCurrentClassName() + " || " + getCurrentMethodName(), Arrays.toString(objects.toArray())); + } else { + e(TAG, null); + } + } + + private static String getCurrentMethodName() { + try { + return Thread.currentThread().getStackTrace()[4].getMethodName() + "()"; + } catch (Exception ignored) {} + return TAG; + } + + private static String getCurrentClassName() { + try { + String className = Thread.currentThread().getStackTrace()[4].getClassName(); + String[] temp = className.split("[\\.]"); + className = temp[temp.length - 1]; + return className; + } catch (Exception ignored) {} + return TAG; + } +} diff --git a/app/src/main/java/com/fastaccess/helper/ParseDateFormat.java b/app/src/main/java/com/fastaccess/helper/ParseDateFormat.java new file mode 100644 index 00000000..657ffe0c --- /dev/null +++ b/app/src/main/java/com/fastaccess/helper/ParseDateFormat.java @@ -0,0 +1,44 @@ +package com.fastaccess.helper; + +import android.support.annotation.Nullable; +import android.text.format.DateUtils; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +public class ParseDateFormat { + + private static final ParseDateFormat INSTANCE = new ParseDateFormat(); + + private static ParseDateFormat getInstance() { + return INSTANCE; + } + + private final Object lock = new Object(); + private final DateFormat dateFormat; + private final TimeZone timeZone; + + private ParseDateFormat() { + dateFormat = new SimpleDateFormat("HH:mm:ss", Locale.US); + dateFormat.setTimeZone(TimeZone.getDefault()); + timeZone = TimeZone.getDefault(); + } + + public String format(Date date) { + synchronized (lock) { + return dateFormat.format(date); + } + } + + public static CharSequence getTimeAgo(@Nullable Date parsedDate) { + if (parsedDate != null) { + long toLocalTime = parsedDate.getTime() + getInstance().timeZone.getRawOffset() + getInstance().timeZone.getDSTSavings(); + return DateUtils.getRelativeTimeSpanString(toLocalTime, System.currentTimeMillis + (), DateUtils.SECOND_IN_MILLIS); + } + return "N/A"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/helper/PrefGetter.java b/app/src/main/java/com/fastaccess/helper/PrefGetter.java new file mode 100644 index 00000000..8fd6882f --- /dev/null +++ b/app/src/main/java/com/fastaccess/helper/PrefGetter.java @@ -0,0 +1,19 @@ +package com.fastaccess.helper; + +import android.support.annotation.NonNull; + +/** + * Created by Kosh on 10 Nov 2016, 3:43 PM + */ + +public class PrefGetter { + private static final String TOKEN = "token"; + + public static void setToken(@NonNull String token) { + PrefHelper.set(TOKEN, token); + } + + public static String getToken() { + return PrefHelper.getString(TOKEN); + } +} diff --git a/app/src/main/java/com/fastaccess/helper/PrefHelper.java b/app/src/main/java/com/fastaccess/helper/PrefHelper.java new file mode 100644 index 00000000..355d51e6 --- /dev/null +++ b/app/src/main/java/com/fastaccess/helper/PrefHelper.java @@ -0,0 +1,80 @@ +package com.fastaccess.helper; + +import android.annotation.SuppressLint; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.fastaccess.App; + +import java.util.Map; + +/** + * Created by kosh20111 on 19 Feb 2017, 2:01 AM + */ +class PrefHelper { + + /** + * @param key + * ( the Key to used to retrieve this data later ) + * @param value + * ( any kind of primitive values ) + *

+ * non can be null!!! + */ + @SuppressLint("ApplySharedPref") public static void set(@NonNull String key, @NonNull Object value) { + if (InputHelper.isEmpty(key)) { + throw new NullPointerException("Key must not be null! (key = " + key + "), (value = " + value + ")"); + } + SharedPreferences.Editor edit = PreferenceManager.getDefaultSharedPreferences(App.getInstance()).edit(); + if (value instanceof String) { + edit.putString(key, (String) value); + } else if (value instanceof Integer) { + edit.putInt(key, (int) value); + } else if (value instanceof Long) { + edit.putLong(key, (long) value); + } else if (value instanceof Boolean) { + edit.putBoolean(key, (boolean) value); + } else if (value instanceof Float) { + edit.putFloat(key, (float) value); + } + edit.commit();//apply on UI + } + + @Nullable public static String getString(@NonNull String key) { + return PreferenceManager.getDefaultSharedPreferences(App.getInstance()).getString(key, null); + } + + public static boolean getBoolean(@NonNull String key) { + return PreferenceManager.getDefaultSharedPreferences(App.getInstance()).getBoolean(key, false); + } + + public static int getInt(@NonNull String key) { + return PreferenceManager.getDefaultSharedPreferences(App.getInstance()).getInt(key, 0); + } + + public static long getLong(@NonNull String key) { + return PreferenceManager.getDefaultSharedPreferences(App.getInstance()).getLong(key, 0); + } + + public static float getFloat(@NonNull String key) { + return PreferenceManager.getDefaultSharedPreferences(App.getInstance()).getFloat(key, 0); + } + + public static void clearKey(@NonNull String key) { + PreferenceManager.getDefaultSharedPreferences(App.getInstance()).edit().remove(key).apply(); + } + + public static boolean isExist(@NonNull String key) { + return PreferenceManager.getDefaultSharedPreferences(App.getInstance()).contains(key); + } + + public static void clearPrefs() { + PreferenceManager.getDefaultSharedPreferences(App.getInstance()).edit().clear().apply(); + } + + public static Map getAll() { + return PreferenceManager.getDefaultSharedPreferences(App.getInstance()).getAll(); + } +} diff --git a/app/src/main/java/com/fastaccess/helper/RxHelper.java b/app/src/main/java/com/fastaccess/helper/RxHelper.java new file mode 100644 index 00000000..cfc6c3b4 --- /dev/null +++ b/app/src/main/java/com/fastaccess/helper/RxHelper.java @@ -0,0 +1,25 @@ +package com.fastaccess.helper; + +import android.support.annotation.NonNull; + +import rx.Observable; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; + +/** + * Created by Kosh on 11 Nov 2016, 11:53 AM + */ + +public class RxHelper { + public static Observable getObserver(@NonNull Observable observable) { + return observable + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()); + } + + public static Observable getObserverComputation(@NonNull Observable observable) { + return observable + .subscribeOn(Schedulers.computation()) + .observeOn(AndroidSchedulers.mainThread()); + } +} diff --git a/app/src/main/java/com/fastaccess/helper/TypeFaceHelper.java b/app/src/main/java/com/fastaccess/helper/TypeFaceHelper.java new file mode 100644 index 00000000..6cc41371 --- /dev/null +++ b/app/src/main/java/com/fastaccess/helper/TypeFaceHelper.java @@ -0,0 +1,25 @@ +package com.fastaccess.helper; + +import android.content.Context; +import android.graphics.Typeface; +import android.widget.TextView; + +/** + * Created by Kosh on 17/12/15 10:25 PM + */ +public class TypeFaceHelper { + + private static Typeface typeFace; + + public static void generateTypeface(Context context) { + typeFace = Typeface.createFromAsset(context.getAssets(), "fonts/app_font.ttf"); + } + + public static void applyTypeface(TextView textView) { + textView.setTypeface(typeFace); + } + + public static Typeface getTypeface() { + return typeFace; + } +} diff --git a/app/src/main/java/com/fastaccess/helper/ViewHelper.java b/app/src/main/java/com/fastaccess/helper/ViewHelper.java new file mode 100644 index 00000000..29171b66 --- /dev/null +++ b/app/src/main/java/com/fastaccess/helper/ViewHelper.java @@ -0,0 +1,136 @@ +package com.fastaccess.helper; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.RippleDrawable; +import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.StateListDrawable; +import android.graphics.drawable.shapes.RoundRectShape; +import android.support.annotation.ColorInt; +import android.support.annotation.IdRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.NavigationView; +import android.util.TypedValue; +import android.view.View; +import android.view.inputmethod.InputMethodManager; +import android.widget.TextView; + +import com.fastaccess.R; + +import java.util.Arrays; + + +/** + * Created by kosh20111 on 10/7/2015 10:42 PM + */ +public class ViewHelper { + + public static int getPrimaryDarkColor(Context context) { + TypedValue typedValue = new TypedValue(); + TypedArray a = context.obtainStyledAttributes(typedValue.data, new int[]{R.attr.colorPrimaryDark}); + int color = a.getColor(0, 0); + a.recycle(); + return color; + } + + public static int toPx(Context context, int dp) { + return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, dp, context.getResources().getDisplayMetrics()); + } + + public static Drawable tintDrawable(@NonNull Drawable drawable, @ColorInt int color) { + drawable.mutate().setColorFilter(color, PorterDuff.Mode.SRC_ATOP); + return drawable; + } + + public static Drawable getDrawableSelector(int normalColor, int pressedColor) { + return new RippleDrawable(ColorStateList.valueOf(pressedColor), getRippleMask(normalColor), getRippleMask(normalColor)); + } + + private static Drawable getRippleMask(int color) { + float[] outerRadii = new float[8]; + Arrays.fill(outerRadii, 3); + RoundRectShape r = new RoundRectShape(outerRadii, null, null); + ShapeDrawable shapeDrawable = new ShapeDrawable(r); + shapeDrawable.getPaint().setColor(color); + return shapeDrawable; + } + + private static StateListDrawable getStateListDrawable(int normalColor, int pressedColor) { + StateListDrawable states = new StateListDrawable(); + states.addState(new int[]{android.R.attr.state_pressed}, new ColorDrawable(pressedColor)); + states.addState(new int[]{android.R.attr.state_focused}, new ColorDrawable(pressedColor)); + states.addState(new int[]{android.R.attr.state_activated}, new ColorDrawable(pressedColor)); + states.addState(new int[]{android.R.attr.state_selected}, new ColorDrawable(pressedColor)); + states.addState(new int[]{}, new ColorDrawable(normalColor)); + return states; + } + + public static ColorStateList textSelector(int normalColor, int pressedColor) { + return new ColorStateList( + new int[][]{ + new int[]{android.R.attr.state_pressed}, + new int[]{android.R.attr.state_focused}, + new int[]{android.R.attr.state_activated}, + new int[]{android.R.attr.state_selected}, + new int[]{} + }, + new int[]{ + pressedColor, + pressedColor, + pressedColor, + pressedColor, + normalColor + } + ); + } + + private static boolean isTablet(Resources resources) { + return (resources.getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE; + } + + public static boolean isTablet(Context context) { + return isTablet(context.getResources()); + } + + private static void setTextViewMenuCounter(@NonNull NavigationView navigationView, @IdRes int itemId, int count) { + TextView view = (TextView) navigationView.getMenu().findItem(itemId).getActionView(); + view.setText(String.format("%s", count)); + } + + public static boolean isLandscape(Resources resources) { + return resources.getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; + } + + @SuppressWarnings("WeakerAccess") public static Rect getLayoutPosition(@NonNull View view) { + Rect myViewRect = new Rect(); + view.getGlobalVisibleRect(myViewRect); + return myViewRect; + } + + @SuppressWarnings("WeakerAccess") @Nullable public static String getTransitionName(@NonNull View view) { + return !InputHelper.isEmpty(view.getTransitionName()) ? view.getTransitionName() : null; + } + + @SuppressWarnings("WeakerAccess") public static void showKeyboard(@NonNull View v, @NonNull Context activity) { + InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(v, 0); + } + + public static void showKeyboard(@NonNull View v) { + showKeyboard(v, v.getContext()); + } + + public static void hideKeyboard(@NonNull View view) { + InputMethodManager inputManager = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + inputManager.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + +} diff --git a/app/src/main/java/com/fastaccess/provider/markdown/MarkDownProvider.java b/app/src/main/java/com/fastaccess/provider/markdown/MarkDownProvider.java new file mode 100644 index 00000000..68aa9c15 --- /dev/null +++ b/app/src/main/java/com/fastaccess/provider/markdown/MarkDownProvider.java @@ -0,0 +1,260 @@ +package com.fastaccess.provider.markdown; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.Html; +import android.webkit.MimeTypeMap; +import android.widget.EditText; +import android.widget.TextView; + +import com.annimon.stream.IntStream; +import com.commonsware.cwac.anddown.AndDown; +import com.fastaccess.App; +import com.fastaccess.helper.InputHelper; + +/** + * Created by Kosh on 24 Nov 2016, 7:43 PM + */ + +public class MarkDownProvider { + private static final String[] IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".gif", ".svg"}; + + private static final String[] MARKDOWN_EXTENSIONS = { + ".md", ".mkdn", ".mdwn", ".mdown", ".markdown", ".mkd", ".mkdown", ".ron", ".rst" + }; + + private static final String[] ARCHIVE_EXTENSIONS = { + ".zip", ".7z", ".rar", ".tar.gz", ".tgz", ".tar.Z", ".tar.bz2", ".tbz2", ".tar.lzma", ".tlz", ".apk", ".jar", ".dmg" + }; + + private MarkDownProvider() {} + + public static void setMdText(@NonNull TextView textView, @NonNull String value) { + String text = App.getInstance().getAndDown() + .markdownToHtml(value, AndDown.HOEDOWN_EXT_AUTOLINK | + AndDown.HOEDOWN_EXT_TABLES | + AndDown.HOEDOWN_EXT_QUOTE | + AndDown.HOEDOWN_EXT_FENCED_CODE | + AndDown.HOEDOWN_EXT_HIGHLIGHT | + AndDown.HOEDOWN_EXT_NO_INTRA_EMPHASIS | + AndDown.HOEDOWN_EXT_SPACE_HEADERS | + AndDown.HOEDOWN_EXT_MATH | + AndDown.HOEDOWN_EXT_SUPERSCRIPT | + AndDown.HOEDOWN_EXT_DISABLE_INDENTED_CODE, 0); + //noinspection deprecation + textView.setText(Html.fromHtml(text)); + } + +// public static RichText convertTextToMarkDown(@NonNull TextView textView, @NonNull String text) { +// RichText richText = RichText.fromMarkdown(text) +// .clickable(true) +// .fix(holder -> { +// if (holder.isGif()) { +// holder.setAutoFix(true); +// holder.setAutoPlay(true); +// } +// +// }) +// .urlClick(url -> { +// ActivityHelper.startCustomTab((Activity) textView.getContext(), Uri.parse(url)); +// return true; +// }); +// richText.into(textView); +// return richText; +// } + + public static void addList(@NonNull EditText editText, @NonNull String list) { + String tag = list + " "; + String source = editText.getText().toString(); + int selectionStart = editText.getSelectionStart(); + int selectionEnd = editText.getSelectionEnd(); + String substring = source.substring(0, selectionStart); + int line = substring.lastIndexOf(10); + if (line != -1) { + selectionStart = line + 1; + } else { + selectionStart = 0; + } + substring = source.substring(selectionStart, selectionEnd); + String[] split = substring.split("\n"); + StringBuilder stringBuffer = new StringBuilder(); + if (split.length > 0) + for (String s : split) { + if (s.length() == 0 && stringBuffer.length() != 0) { + stringBuffer.append("\n"); + continue; + } + if (!s.trim().startsWith(tag)) { + if (stringBuffer.length() > 0) stringBuffer.append("\n"); + stringBuffer.append(tag).append(s); + } else { + if (stringBuffer.length() > 0) stringBuffer.append("\n"); + stringBuffer.append(s); + } + } + + if (stringBuffer.length() == 0) { + stringBuffer.append(tag); + } + editText.getText().replace(selectionStart, selectionEnd, stringBuffer.toString()); + editText.setSelection(stringBuffer.length() + selectionStart); + + } + + public static void addHeader(@NonNull EditText editText, int level) { + String source = editText.getText().toString(); + int selectionStart = editText.getSelectionStart(); + int selectionEnd = editText.getSelectionEnd(); + StringBuilder result = new StringBuilder(); + String substring = source.substring(selectionStart, selectionEnd); + if (!hasNewLine(source, selectionStart)) + result.append("\n"); + IntStream.range(0, level).forEach(integer -> result.append("#")); + result.append(" ").append(substring); + editText.getText().replace(selectionStart, selectionEnd, result.toString()); + editText.setSelection(selectionStart + result.length()); + + } + + public static void addItalic(@NonNull EditText editText) { + String source = editText.getText().toString(); + int selectionStart = editText.getSelectionStart(); + int selectionEnd = editText.getSelectionEnd(); + String substring = source.substring(selectionStart, selectionEnd); + String result = "_" + substring + "_ "; + editText.getText().replace(selectionStart, selectionEnd, result); + editText.setSelection(result.length() + selectionStart - 2); + + } + + public static void addBold(@NonNull EditText editText) { + String source = editText.getText().toString(); + int selectionStart = editText.getSelectionStart(); + int selectionEnd = editText.getSelectionEnd(); + String substring = source.substring(selectionStart, selectionEnd); + String result = "__" + substring + "__ "; + editText.getText().replace(selectionStart, selectionEnd, result); + editText.setSelection(result.length() + selectionStart - 3); + + } + + public static void addCode(@NonNull EditText editText) { + try { + String source = editText.getText().toString(); + int selectionStart = editText.getSelectionStart(); + int selectionEnd = editText.getSelectionEnd(); + String substring = source.substring(selectionStart, selectionEnd); + String result; + if (hasNewLine(source, selectionStart)) + result = "```\n" + substring + "\n```\n"; + else + result = "\n```\n" + substring + "\n```\n"; + + editText.getText().replace(selectionStart, selectionEnd, result); + editText.setSelection(result.length() + selectionStart - 5); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static void addStrikeThrough(@NonNull EditText editText) { + String source = editText.getText().toString(); + int selectionStart = editText.getSelectionStart(); + int selectionEnd = editText.getSelectionEnd(); + String substring = source.substring(selectionStart, selectionEnd); + String result = "~~" + substring + "~~ "; + editText.getText().replace(selectionStart, selectionEnd, result); + editText.setSelection(result.length() + selectionStart - 3); + + } + + public static void addQuote(@NonNull EditText editText) { + String source = editText.getText().toString(); + int selectionStart = editText.getSelectionStart(); + int selectionEnd = editText.getSelectionEnd(); + String substring = source.substring(selectionStart, selectionEnd); + String result; + if (hasNewLine(source, selectionStart)) { + result = "> " + substring; + } else { + result = "\n> " + substring; + + } + editText.getText().replace(selectionStart, selectionEnd, result); + editText.setSelection(result.length() + selectionStart); + + } + + public static void addDivider(@NonNull EditText editText) { + String source = editText.getText().toString(); + int selectionStart = editText.getSelectionStart(); + String result; + if (hasNewLine(source, selectionStart)) { + result = "-------\n"; + } else { + result = "\n-------\n"; + } + editText.getText().replace(selectionStart, selectionStart, result); + editText.setSelection(result.length() + selectionStart); + + } + + public static void addPhoto(@NonNull EditText editText) { + int selectionStart = editText.getSelectionStart(); + String result = "![]()\n"; + int length = selectionStart + result.length(); + editText.getText().insert(selectionStart, result); + editText.setSelection(length - 2); + } + + public static void addLink(@NonNull EditText editText) { + int selectionStart = editText.getSelectionStart(); + String result = "[]()\n"; + int length = selectionStart + result.length(); + editText.getText().insert(selectionStart, result); + editText.setSelection(length - 2); + } + + private static boolean hasNewLine(@NonNull String source, int selectionStart) { + try { + if (source.isEmpty()) return true; + source = source.substring(0, selectionStart); + return source.charAt(source.length() - 1) == 10; + } catch (StringIndexOutOfBoundsException e) { + return false; + } + } + + public static boolean isImage(@Nullable String name) { + if (InputHelper.isEmpty(name)) return false; + name = name.toLowerCase(); + for (String value : IMAGE_EXTENSIONS) { + String extension = MimeTypeMap.getFileExtensionFromUrl(name); + if ((extension != null && value.replace(".", "").equals(extension)) || name.endsWith(value)) return true; + } + return false; + } + + public static boolean isMarkdown(@Nullable String name) { + if (InputHelper.isEmpty(name)) return false; + name = name.toLowerCase(); + for (String value : MARKDOWN_EXTENSIONS) { + String extension = MimeTypeMap.getFileExtensionFromUrl(name); + if ((extension != null && value.replace(".", "").equals(extension)) || name.equalsIgnoreCase("README") || + name.endsWith(value)) return true; + } + return false; + } + + public static boolean isArchive(@Nullable String name) { + if (InputHelper.isEmpty(name)) return false; + name = name.toLowerCase(); + for (String value : ARCHIVE_EXTENSIONS) { + String extension = MimeTypeMap.getFileExtensionFromUrl(name); + if ((extension != null && value.replace(".", "").equals(extension)) || name.endsWith(value)) return true; + } + + return false; + } +} diff --git a/app/src/main/java/com/fastaccess/provider/rest/RestProvider.java b/app/src/main/java/com/fastaccess/provider/rest/RestProvider.java new file mode 100644 index 00000000..e7882b94 --- /dev/null +++ b/app/src/main/java/com/fastaccess/provider/rest/RestProvider.java @@ -0,0 +1,176 @@ +package com.fastaccess.provider.rest; + +import android.app.DownloadManager; +import android.content.Context; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.fastaccess.App; +import com.fastaccess.BuildConfig; +import com.fastaccess.R; +import com.fastaccess.data.dao.GitHubErrorResponse; +import com.fastaccess.data.service.GistService; +import com.fastaccess.data.service.IssueService; +import com.fastaccess.data.service.PullRequestService; +import com.fastaccess.data.service.RepoService; +import com.fastaccess.data.service.SearchService; +import com.fastaccess.data.service.UserRestService; +import com.fastaccess.helper.FileHelper; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.helper.Logger; +import com.fastaccess.helper.PrefGetter; +import com.fastaccess.provider.rest.converters.GithubResponseConverter; +import com.fastaccess.provider.rest.interceptors.PaginationInterceptor; +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import java.lang.reflect.Modifier; + +import okhttp3.Cache; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.ResponseBody; +import okhttp3.logging.HttpLoggingInterceptor; +import retrofit2.Retrofit; +import retrofit2.adapter.rxjava.HttpException; +import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; +import retrofit2.converter.gson.GsonConverterFactory; + +/** + * Created by Kosh on 08 Feb 2017, 8:37 PM + */ + +public class RestProvider { + + private static Cache cache; + public static final int PAGE_SIZE = 30; + + private final static Gson gson = new GsonBuilder() + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .excludeFieldsWithModifiers(Modifier.FINAL, Modifier.TRANSIENT, Modifier.STATIC) + .setDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'") + .setPrettyPrinting() + .create(); + + private static Cache provideCache() { + if (cache == null) { + int cacheSize = 20 * 1024 * 1024; + cache = new Cache(App.getInstance().getCacheDir(), cacheSize); + } + return cache; + } + + private static OkHttpClient provideOkHttpClient(boolean forLogin) { + OkHttpClient.Builder client = new OkHttpClient.Builder(); + if (BuildConfig.DEBUG) { + client.addInterceptor(new HttpLoggingInterceptor() + .setLevel(HttpLoggingInterceptor.Level.BODY)); + } + client.addInterceptor(new PaginationInterceptor()) + .addInterceptor(chain -> { + Request original = chain.request(); + Request.Builder requestBuilder = original.newBuilder(); + if (!InputHelper.isEmpty(PrefGetter.getToken())) { + requestBuilder.header("Authorization", "token " + PrefGetter.getToken()); + } + if (!forLogin) { + requestBuilder.addHeader("Accept", "application/vnd.github.v3+json") + .addHeader("Content-type", "application/vnd.github.v3+json"); + } else { + requestBuilder.addHeader("Accept", "application/json") + .addHeader("Content-type", "application/json"); + } + requestBuilder.method(original.method(), original.body()); + Request request = requestBuilder.build(); + return chain.proceed(request); + }); +// client.cache(provideCache());//disable cache, since we are going offline. + return client.build(); + } + + private static Retrofit provideRetrofit() { + return new Retrofit.Builder() + .baseUrl(BuildConfig.REST_URL) + .client(provideOkHttpClient(false)) + .addConverterFactory(new GithubResponseConverter(gson)) + .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) + .build(); + } + + @SuppressWarnings("WeakerAccess") public static long downloadFile(@NonNull Context context, @NonNull String url, @Nullable String fileName) { + DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); + Uri uri = Uri.parse(url); + DownloadManager.Request request = new DownloadManager.Request(uri); + Logger.e(FileHelper.getDownloadDirectory(), fileName); + if (!InputHelper.isEmpty(fileName)) { + request.setDestinationInExternalPublicDir(FileHelper.getDownloadDirectory(), fileName); + request.setDescription(String.format("%s %s", context.getString(R.string.downloading), fileName)); + } else { + request.setDescription(String.format("%s %s", context.getString(R.string.downloading), url)); + } + request.setTitle(context.getString(R.string.downloading_file)); + request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE | DownloadManager.Request.NETWORK_WIFI); + request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); + return downloadManager.enqueue(request); + } + + public static long downloadFile(@NonNull Context context, @NonNull String url) { + return downloadFile(context, url, null); + } + + public static int getErrorCode(Throwable throwable) { + if (throwable instanceof HttpException) { + return ((HttpException) throwable).code(); + + } + return -1; + } + + @NonNull public static UserRestService getLoginRestService() { + return new Retrofit.Builder() + .client(provideOkHttpClient(true)) + .baseUrl("https://github.com/login/oauth/") + .addConverterFactory(GsonConverterFactory.create(gson)) + .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) + .build() + .create(UserRestService.class); + } + + @NonNull public static UserRestService getUserService() { + return provideRetrofit().create(UserRestService.class); + } + + @NonNull public static GistService getGistService() { + return provideRetrofit().create(GistService.class); + } + + @NonNull public static RepoService getRepoService() { + return provideRetrofit().create(RepoService.class); + } + + @NonNull public static IssueService getIssueService() { + return provideRetrofit().create(IssueService.class); + } + + @NonNull public static PullRequestService getPullRequestSerice() { + return provideRetrofit().create(PullRequestService.class); + } + + @NonNull public static SearchService getSearchService() { + return provideRetrofit().create(SearchService.class); + } + + @Nullable public static GitHubErrorResponse getErrorResponse(@NonNull Throwable throwable) { + if (throwable instanceof HttpException) { + ResponseBody body = ((HttpException) throwable).response().errorBody(); + if (body != null) { + try { + return gson.fromJson(body.toString(), GitHubErrorResponse.class); + } catch (Exception ignored) {} + } + } + return null; + } +} diff --git a/app/src/main/java/com/fastaccess/provider/rest/converters/GithubResponseConverter.java b/app/src/main/java/com/fastaccess/provider/rest/converters/GithubResponseConverter.java new file mode 100644 index 00000000..19f89464 --- /dev/null +++ b/app/src/main/java/com/fastaccess/provider/rest/converters/GithubResponseConverter.java @@ -0,0 +1,43 @@ +package com.fastaccess.provider.rest.converters; + +import com.google.gson.Gson; + +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; + +import lombok.AllArgsConstructor; +import okhttp3.RequestBody; +import okhttp3.ResponseBody; +import retrofit2.Converter; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; + +/** + * call that supports String & Gson and always uses json as its request body + */ +@AllArgsConstructor +public class GithubResponseConverter extends Converter.Factory { + private Gson gson; + + @Override public Converter responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { + if (type == String.class) { + return new StringResponseConverter(); + } + return GsonConverterFactory.create(gson).responseBodyConverter(type, annotations, retrofit); + } + + @Override + public Converter requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit + retrofit) { + return GsonConverterFactory.create(gson).requestBodyConverter(type, parameterAnnotations, methodAnnotations, retrofit); + } + + private static class StringResponseConverter implements Converter { + @Override public String convert(ResponseBody value) throws IOException { + return value.string(); + } + } + + +} diff --git a/app/src/main/java/com/fastaccess/provider/rest/interceptors/PaginationInterceptor.java b/app/src/main/java/com/fastaccess/provider/rest/interceptors/PaginationInterceptor.java new file mode 100644 index 00000000..2a3fe327 --- /dev/null +++ b/app/src/main/java/com/fastaccess/provider/rest/interceptors/PaginationInterceptor.java @@ -0,0 +1,54 @@ +package com.fastaccess.provider.rest.interceptors; + +import android.net.Uri; + +import com.fastaccess.helper.InputHelper; + +import java.io.IOException; + +import okhttp3.Interceptor; +import okhttp3.Response; +import okhttp3.ResponseBody; + +public class PaginationInterceptor implements Interceptor { + + @Override public Response intercept(Chain chain) throws IOException { + Response response = chain.proceed(chain.request()); + if (response.isSuccessful()) { + if (response.peekBody(1).string().equals("[")) { + String json = "{"; + String link = response.header("link"); + if (link != null) { + String[] links = link.split(","); + for (String link1 : links) { + String[] pageLink = link1.split(";"); + String page = Uri.parse(pageLink[0].replaceAll("[<>]", "")).getQueryParameter("page"); + String rel = pageLink[1].replaceAll("\"", "").replace("rel=", ""); + if (page != null) json += String.format("\"%s\":\"%s\",", rel.trim(), page); + } + } + json += String.format("\"items\":%s}", response.body().string()); + return response.newBuilder().body(ResponseBody.create(response.body().contentType(), json)).build(); + } else { + String link = response.header("link"); + if (link != null) { + String pagination = ""; + String[] links = link.split(","); + for (String link1 : links) { + String[] pageLink = link1.split(";"); + String page = Uri.parse(pageLink[0].replaceAll("[<>]", "")).getQueryParameter("page"); + String rel = pageLink[1].replaceAll("\"", "").replace("rel=", ""); + if (page != null) pagination += String.format("\"%s\":\"%s\",", rel.trim(), page); + } + if (!InputHelper.isEmpty(pagination)) {//hacking for search pagination. + String body = response.body().string(); + return response.newBuilder().body(ResponseBody.create(response.body().contentType(), + "{" + pagination + body.substring(1, body.length()))).build(); + } + } + } + } + return response; + } + +} diff --git a/app/src/main/java/com/fastaccess/provider/rest/loadmore/OnLoadMore.java b/app/src/main/java/com/fastaccess/provider/rest/loadmore/OnLoadMore.java new file mode 100644 index 00000000..27a3c649 --- /dev/null +++ b/app/src/main/java/com/fastaccess/provider/rest/loadmore/OnLoadMore.java @@ -0,0 +1,38 @@ +package com.fastaccess.provider.rest.loadmore; + +import android.support.annotation.Nullable; + +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.widgets.recyclerview.scroll.InfiniteScroll; + +public class OnLoadMore

extends InfiniteScroll { + + private BaseMvp.PaginationListener

presenter; + @Nullable private P parameter; + + public OnLoadMore(BaseMvp.PaginationListener

presenter) { + this(presenter, null); + } + + public OnLoadMore(BaseMvp.PaginationListener

presenter, @Nullable P parameter) { + super(); + this.presenter = presenter; + this.parameter = parameter; + } + + @Override protected void onLoadMore(int page, int previousTotal) { + super.onLoadMore(page, previousTotal); + if (presenter != null) { + presenter.setPreviousTotal(previousTotal); + presenter.onCallApi(page + 1, parameter); + } + } + + public void setParameter(@Nullable P parameter) { + this.parameter = parameter; + } + + @Nullable public P getParameter() { + return parameter; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/provider/scheme/SchemeParser.java b/app/src/main/java/com/fastaccess/provider/scheme/SchemeParser.java new file mode 100644 index 00000000..4f48aefd --- /dev/null +++ b/app/src/main/java/com/fastaccess/provider/scheme/SchemeParser.java @@ -0,0 +1,196 @@ +package com.fastaccess.provider.scheme; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.annimon.stream.Optional; +import com.annimon.stream.Stream; +import com.fastaccess.helper.ActivityHelper; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.helper.Logger; +import com.fastaccess.ui.modules.code.CodeViewerView; +import com.fastaccess.ui.modules.gists.gist.GistView; +import com.fastaccess.ui.modules.repos.RepoPagerView; +import com.fastaccess.ui.modules.repos.code.commit.details.CommitPagerView; +import com.fastaccess.ui.modules.repos.issues.issue.details.IssuePagerView; +import com.fastaccess.ui.modules.repos.pull_requests.pull_request.details.PullRequestPagerView; +import com.fastaccess.ui.modules.user.UserPagerView; + +import java.util.List; + +import static android.content.Intent.ACTION_VIEW; +import static android.content.Intent.CATEGORY_BROWSABLE; + +/** + * Created by Kosh on 09 Dec 2016, 4:44 PM + */ + +public class SchemeParser { + + + private static final String HOST_DEFAULT = "github.com"; + private static final String HOST_GISTS = "gist.github.com"; + private static final String PROTOCOL_HTTPS = "https"; + + public static void launchUri(@NonNull Context context, @NonNull Intent data) { + Intent intent = convert(context, data); + if (intent != null) { + context.startActivity(intent); + } else { + Activity activity = ActivityHelper.getActivity(context); + if (activity == null) { + context.startActivity(new Intent(ACTION_VIEW, data.getData()).addCategory(CATEGORY_BROWSABLE)); + } else { + ActivityHelper.startCustomTab(activity, data.getData()); + } + } + } + + public static void launchUri(@NonNull Context context, @NonNull Uri data) { + Intent intent = convert(context, data); + if (intent != null) { + context.startActivity(intent); + } else { + Activity activity = ActivityHelper.getActivity(context); + if (activity == null) { + context.startActivity(new Intent(ACTION_VIEW, data).addCategory(CATEGORY_BROWSABLE)); + } else { + ActivityHelper.startCustomTab(activity, data); + } + } + } + + @Nullable private static Intent convert(@NonNull Context context, final Intent intent) { + if (intent == null) return null; + if (!ACTION_VIEW.equals(intent.getAction())) return null; + Uri data = intent.getData(); + return convert(context, data); + } + + @Nullable private static Intent convert(@NonNull Context context, Uri data) { + if (data == null) return null; + if (InputHelper.isEmpty(data.getHost()) || InputHelper.isEmpty(data.getScheme())) { + String host = data.getHost(); + if (InputHelper.isEmpty(host)) host = HOST_DEFAULT; + String scheme = data.getScheme(); + if (InputHelper.isEmpty(scheme)) scheme = PROTOCOL_HTTPS; + String prefix = scheme + "://" + host; + String path = data.getPath(); + if (!InputHelper.isEmpty(path)) + if (path.charAt(0) == '/') data = Uri.parse(prefix + path); + else data = Uri.parse(prefix + '/' + path); + else data = Uri.parse(prefix); + } + + return getIntentForURI(context, data); + } + + @Nullable private static Intent getIntentForURI(@NonNull Context context, @NonNull Uri data) { + if (HOST_GISTS.equals(data.getHost())) { + String gist = getGistId(data); + if (gist != null) { + return GistView.createIntent(context, gist); + } + } else if (HOST_DEFAULT.equals(data.getHost())) { + Intent userIntent = getUser(context, data); + Intent pullRequestIntent = getPullRequestIntent(context, data); + Intent issueIntent = getIssueIntent(context, data); + Intent repoIntent = getRepo(context, data); + Intent commit = getCommit(context, data); + Intent blob = getBlob(context, data); + Optional intentOptional = returnNonNull(userIntent, pullRequestIntent, commit, issueIntent, repoIntent, blob); + Optional empty = Optional.empty(); + if (intentOptional != null && intentOptional.isPresent() && intentOptional != empty) + return intentOptional.get(); + } + return null; + } + + @Nullable private static Intent getPullRequestIntent(@NonNull Context context, @NonNull Uri uri) { + List segments = uri.getPathSegments(); + if (segments == null || segments.size() < 4) return null; + if (!"pull".equals(segments.get(2))) return null; + String owner = segments.get(0); + String repo = segments.get(1); + String number = segments.get(3); + if (InputHelper.isEmpty(number)) + return null; + int issueNumber; + try { + issueNumber = Integer.parseInt(number); + } catch (NumberFormatException nfe) { + return null; + } + if (issueNumber < 1) return null; + return PullRequestPagerView.createIntent(context, repo, owner, issueNumber); + } + + @Nullable private static Intent getIssueIntent(@NonNull Context context, @NonNull Uri uri) { + List segments = uri.getPathSegments(); + if (segments == null || segments.size() < 4) return null; + if (!"issues".equals(segments.get(2))) return null; + String owner = segments.get(0); + String repo = segments.get(1); + String number = segments.get(3); + if (InputHelper.isEmpty(number)) + return null; + int issueNumber; + try { + issueNumber = Integer.parseInt(number); + } catch (NumberFormatException nfe) { + return null; + } + if (issueNumber < 1) return null; + return IssuePagerView.createIntent(context, repo, owner, issueNumber); + } + + @Nullable private static Intent getRepo(@NonNull Context context, @NonNull Uri uri) { + List segments = uri.getPathSegments(); + if (segments == null || segments.size() < 2 || segments.size() > 2) return null; + String owner = segments.get(0); + String repoName = segments.get(1); + return RepoPagerView.createIntent(context, repoName, owner); + } + + @Nullable private static Intent getCommit(@NonNull Context context, @NonNull Uri uri) { + List segments = uri.getPathSegments(); + if (segments == null || segments.size() < 4 || !"commit".equals(segments.get(2))) return null; + String login = segments.get(0); + String repoId = segments.get(1); + String sha = segments.get(3); + return CommitPagerView.createIntent(context, repoId, login, sha); + } + + @Nullable private static String getGistId(@NonNull Uri uri) { + List segments = uri.getPathSegments(); + return segments != null && !segments.isEmpty() ? segments.get(0) : null; + } + + @Nullable private static Intent getUser(@NonNull Context context, @NonNull Uri uri) { + List segments = uri.getPathSegments(); + if (segments != null && !segments.isEmpty() && segments.size() == 1) { + return UserPagerView.createIntent(context, segments.get(0)); + } + return null; + } + + @Nullable private static Intent getBlob(@NonNull Context context, @NonNull Uri uri) { + List segments = uri.getPathSegments(); + if (segments == null || segments.size() < 4) return null; + String segmentTwo = segments.get(2); + if (segmentTwo.equals("blob") || segmentTwo.equals("tree")) { + String fullUrl = uri.toString(); + return CodeViewerView.createIntent(context, fullUrl); + } + Logger.e(uri); + return null; + } + + @SafeVarargs private static Optional returnNonNull(T... t) { + return Stream.of(t).filter(value -> value != null).findFirst(); + } +} diff --git a/app/src/main/java/com/fastaccess/provider/uil/UILProvider.java b/app/src/main/java/com/fastaccess/provider/uil/UILProvider.java new file mode 100644 index 00000000..6add17f1 --- /dev/null +++ b/app/src/main/java/com/fastaccess/provider/uil/UILProvider.java @@ -0,0 +1,47 @@ +package com.fastaccess.provider.uil; + +import android.app.AlarmManager; +import android.content.Context; +import android.graphics.Bitmap; +import android.support.annotation.NonNull; + +import com.fastaccess.R; +import com.nostra13.universalimageloader.cache.disc.impl.LimitedAgeDiskCache; +import com.nostra13.universalimageloader.core.DisplayImageOptions; +import com.nostra13.universalimageloader.core.ImageLoader; +import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; +import com.nostra13.universalimageloader.core.assist.ImageScaleType; + +/** + * Created by Kosh on 26 Nov 2016, 10:48 AM + */ + +public class UILProvider { + + private UILProvider() {} + + public static void initUIL(@NonNull Context context) { + ImageLoader imageLoader = ImageLoader.getInstance(); + if (!imageLoader.isInited()) { + ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context) + .writeDebugLogs() + .defaultDisplayImageOptions(getOptions()) + .denyCacheImageMultipleSizesInMemory() + .diskCache(new LimitedAgeDiskCache(context.getCacheDir(), AlarmManager.INTERVAL_DAY)) + .build(); + imageLoader.init(config); + } + } + + private static DisplayImageOptions getOptions() { + return new DisplayImageOptions.Builder() + .delayBeforeLoading(0) + .resetViewBeforeLoading(true) + .cacheOnDisk(true) + .cacheInMemory(true) + .showImageForEmptyUri(R.drawable.ic_github_black) + .imageScaleType(ImageScaleType.EXACTLY) + .bitmapConfig(Bitmap.Config.RGB_565) + .build(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/adapter/CommentsAdapter.java b/app/src/main/java/com/fastaccess/ui/adapter/CommentsAdapter.java new file mode 100644 index 00000000..fa992412 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/CommentsAdapter.java @@ -0,0 +1,30 @@ +package com.fastaccess.ui.adapter; + +import android.support.annotation.NonNull; +import android.view.ViewGroup; + +import com.fastaccess.data.dao.CommentsModel; +import com.fastaccess.ui.adapter.viewholder.CommentsViewHolder; +import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.ArrayList; + +/** + * Created by Kosh on 11 Nov 2016, 2:07 PM + */ + +public class CommentsAdapter extends BaseRecyclerAdapter> { + + public CommentsAdapter(@NonNull ArrayList eventsModels) { + super(eventsModels); + } + + @Override protected CommentsViewHolder viewHolder(ViewGroup parent, int viewType) { + return CommentsViewHolder.newInstance(parent, this); + } + + @Override protected void onBindView(CommentsViewHolder holder, int position) { + holder.bind(getItem(position)); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/adapter/CommitFilesAdapter.java b/app/src/main/java/com/fastaccess/ui/adapter/CommitFilesAdapter.java new file mode 100644 index 00000000..43fc1ffb --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/CommitFilesAdapter.java @@ -0,0 +1,40 @@ +package com.fastaccess.ui.adapter; + +import android.support.annotation.NonNull; +import android.view.ViewGroup; + +import com.fastaccess.data.dao.CommitFileModel; +import com.fastaccess.ui.adapter.viewholder.CommitFilesViewHolder; +import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.ArrayList; + +/** + * Created by Kosh on 11 Nov 2016, 2:07 PM + */ + +public class CommitFilesAdapter extends BaseRecyclerAdapter> { + + public interface OnTogglePatch { + void onToggle(int position, boolean isCollapsed); + + boolean isCollapsed(int position); + } + + @NonNull private OnTogglePatch onTogglePatch; + + public CommitFilesAdapter(@NonNull ArrayList eventsModels, @NonNull OnTogglePatch onTogglePatch) { + super(eventsModels); + this.onTogglePatch = onTogglePatch; + } + + @Override protected CommitFilesViewHolder viewHolder(ViewGroup parent, int viewType) { + return CommitFilesViewHolder.newInstance(parent, this, onTogglePatch); + } + + @Override protected void onBindView(CommitFilesViewHolder holder, int position) { + holder.bind(getItem(position)); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/adapter/CommitsAdapter.java b/app/src/main/java/com/fastaccess/ui/adapter/CommitsAdapter.java new file mode 100644 index 00000000..9d2acac1 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/CommitsAdapter.java @@ -0,0 +1,30 @@ +package com.fastaccess.ui.adapter; + +import android.support.annotation.NonNull; +import android.view.ViewGroup; + +import com.fastaccess.data.dao.CommitModel; +import com.fastaccess.ui.adapter.viewholder.CommitsViewHolder; +import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.List; + +/** + * Created by Kosh on 11 Nov 2016, 2:07 PM + */ + +public class CommitsAdapter extends BaseRecyclerAdapter> { + + public CommitsAdapter(@NonNull List data) { + super(data); + } + + @Override protected CommitsViewHolder viewHolder(ViewGroup parent, int viewType) { + return CommitsViewHolder.newInstance(parent, this); + } + + @Override protected void onBindView(CommitsViewHolder holder, int position) { + holder.bind(getItem(position)); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/adapter/FeedsAdapter.java b/app/src/main/java/com/fastaccess/ui/adapter/FeedsAdapter.java new file mode 100644 index 00000000..474d5a82 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/FeedsAdapter.java @@ -0,0 +1,30 @@ +package com.fastaccess.ui.adapter; + +import android.support.annotation.NonNull; +import android.view.ViewGroup; + +import com.fastaccess.data.dao.EventsModel; +import com.fastaccess.ui.adapter.viewholder.FeedsViewHolder; +import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.ArrayList; + +/** + * Created by Kosh on 11 Nov 2016, 2:07 PM + */ + +public class FeedsAdapter extends BaseRecyclerAdapter> { + + public FeedsAdapter(@NonNull ArrayList eventsModels) { + super(eventsModels); + } + + @Override protected FeedsViewHolder viewHolder(ViewGroup parent, int viewType) { + return new FeedsViewHolder(FeedsViewHolder.getView(parent), this); + } + + @Override protected void onBindView(FeedsViewHolder holder, int position) { + holder.bind(getItem(position)); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/adapter/FragmentsPagerAdapter.java b/app/src/main/java/com/fastaccess/ui/adapter/FragmentsPagerAdapter.java new file mode 100644 index 00000000..58e4bcaa --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/FragmentsPagerAdapter.java @@ -0,0 +1,35 @@ +package com.fastaccess.ui.adapter; + +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentStatePagerAdapter; + +import com.fastaccess.data.dao.FragmentPagerAdapterModel; + +import java.util.List; + +/** + * Created by Kosh on 03 Dec 2016, 9:25 AM + */ + +public class FragmentsPagerAdapter extends FragmentStatePagerAdapter { + + private List fragments; + + public FragmentsPagerAdapter(FragmentManager fm, List fragments) { + super(fm); + this.fragments = fragments; + } + + @Override public Fragment getItem(int position) { + return fragments.get(position).getFragment(); + } + + @Override public int getCount() { + return fragments.size(); + } + + @Override public CharSequence getPageTitle(int position) { + return fragments.get(position).getTitle(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/adapter/GistFilesAdapter.java b/app/src/main/java/com/fastaccess/ui/adapter/GistFilesAdapter.java new file mode 100644 index 00000000..c6871899 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/GistFilesAdapter.java @@ -0,0 +1,32 @@ +package com.fastaccess.ui.adapter; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.ViewGroup; + +import com.fastaccess.data.dao.FilesListModel; +import com.fastaccess.ui.adapter.viewholder.GistFilesViewHolder; +import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.List; + +/** + * Created by Kosh on 11 Nov 2016, 2:07 PM + */ + +public class GistFilesAdapter extends BaseRecyclerAdapter> { + + public GistFilesAdapter(@NonNull List data, @Nullable BaseViewHolder.OnItemClickListener listener) { + super(data, listener); + } + + @Override protected GistFilesViewHolder viewHolder(ViewGroup parent, int viewType) { + return GistFilesViewHolder.newInstance(parent, this); + } + + @Override protected void onBindView(GistFilesViewHolder holder, int position) { + holder.bind(getItem(position)); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/adapter/GistsAdapter.java b/app/src/main/java/com/fastaccess/ui/adapter/GistsAdapter.java new file mode 100644 index 00000000..035beb2d --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/GistsAdapter.java @@ -0,0 +1,37 @@ +package com.fastaccess.ui.adapter; + +import android.support.annotation.NonNull; +import android.view.ViewGroup; + +import com.fastaccess.data.dao.GistsModel; +import com.fastaccess.ui.adapter.viewholder.GistsViewHolder; +import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.ArrayList; + +/** + * Created by Kosh on 11 Nov 2016, 2:07 PM + */ + +public class GistsAdapter extends BaseRecyclerAdapter> { + + private boolean isForProfile; + + public GistsAdapter(@NonNull ArrayList gistModels) { + this(gistModels, false); + } + + public GistsAdapter(@NonNull ArrayList gistsModels, boolean isForProfile) { + super(gistsModels); + this.isForProfile = isForProfile; + } + + @Override protected GistsViewHolder viewHolder(ViewGroup parent, int viewType) { + return new GistsViewHolder(GistsViewHolder.getView(parent), this); + } + + @Override protected void onBindView(GistsViewHolder holder, int position) { + holder.bind(getItem(position), isForProfile); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/adapter/IssueTimelineAdapter.java b/app/src/main/java/com/fastaccess/ui/adapter/IssueTimelineAdapter.java new file mode 100644 index 00000000..be0a9ac5 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/IssueTimelineAdapter.java @@ -0,0 +1,48 @@ +package com.fastaccess.ui.adapter; + +import android.support.annotation.NonNull; +import android.support.v7.widget.StaggeredGridLayoutManager; +import android.view.ViewGroup; + +import com.fastaccess.data.dao.IssueEventAdapterModel; +import com.fastaccess.ui.adapter.viewholder.IssueDetailsViewHolder; +import com.fastaccess.ui.adapter.viewholder.IssueTimelineViewHolder; +import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.List; + +/** + * Created by Kosh on 13 Dec 2016, 1:44 AM + */ + +public class IssueTimelineAdapter extends BaseRecyclerAdapter> { + + public IssueTimelineAdapter(@NonNull List data) { + super(data); + } + + @Override protected BaseViewHolder viewHolder(ViewGroup parent, int viewType) { + if (viewType == IssueEventAdapterModel.HEADER) { + return IssueDetailsViewHolder.newInstance(parent, this); + } + return IssueTimelineViewHolder.newInstance(parent, this); + } + + @Override protected void onBindView(BaseViewHolder holder, int position) { + IssueEventAdapterModel model = getItem(position); + if (model.getType() == IssueEventAdapterModel.HEADER) { + ((IssueDetailsViewHolder) holder).bind(model); + StaggeredGridLayoutManager.LayoutParams layoutParams = (StaggeredGridLayoutManager.LayoutParams) holder.itemView.getLayoutParams(); + layoutParams.setFullSpan(true); + } else { + ((IssueTimelineViewHolder) holder).bind(model); + } + } + + @Override public int getItemViewType(int position) { + return getData().get(position).getType(); + } +} + diff --git a/app/src/main/java/com/fastaccess/ui/adapter/IssuesAdapter.java b/app/src/main/java/com/fastaccess/ui/adapter/IssuesAdapter.java new file mode 100644 index 00000000..3beb6f86 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/IssuesAdapter.java @@ -0,0 +1,37 @@ +package com.fastaccess.ui.adapter; + +import android.support.annotation.NonNull; +import android.view.ViewGroup; + +import com.fastaccess.data.dao.IssueModel; +import com.fastaccess.ui.adapter.viewholder.IssuesViewHolder; +import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.List; + +/** + * Created by Kosh on 11 Nov 2016, 2:07 PM + */ + +public class IssuesAdapter extends BaseRecyclerAdapter> { + + private boolean withAvatar; + + public IssuesAdapter(@NonNull List data) { + this(data, false); + } + + public IssuesAdapter(@NonNull List data, boolean withAvatar) { + super(data); + this.withAvatar = withAvatar; + } + + @Override protected IssuesViewHolder viewHolder(ViewGroup parent, int viewType) { + return IssuesViewHolder.newInstance(parent, this); + } + + @Override protected void onBindView(IssuesViewHolder holder, int position) { + holder.bind(getItem(position), withAvatar); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/adapter/PullRequestAdapter.java b/app/src/main/java/com/fastaccess/ui/adapter/PullRequestAdapter.java new file mode 100644 index 00000000..c6020c67 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/PullRequestAdapter.java @@ -0,0 +1,38 @@ +package com.fastaccess.ui.adapter; + +import android.support.annotation.NonNull; +import android.view.ViewGroup; + +import com.fastaccess.data.dao.PullRequestModel; +import com.fastaccess.ui.adapter.viewholder.PullRequestViewHolder; +import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.List; + +/** + * Created by Kosh on 11 Nov 2016, 2:07 PM + */ + +public class PullRequestAdapter extends BaseRecyclerAdapter> { + + private boolean withAvatar; + + public PullRequestAdapter(@NonNull List data) { + this(data, false); + } + + public PullRequestAdapter(@NonNull List data, boolean withAvatar) { + super(data); + this.withAvatar = withAvatar; + } + + @Override protected PullRequestViewHolder viewHolder(ViewGroup parent, int viewType) { + return PullRequestViewHolder.newInstance(parent, this); + } + + @Override protected void onBindView(PullRequestViewHolder holder, int position) { + holder.bind(getItem(position), withAvatar); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/adapter/PullRequestTimelineAdapter.java b/app/src/main/java/com/fastaccess/ui/adapter/PullRequestTimelineAdapter.java new file mode 100644 index 00000000..07d4a40d --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/PullRequestTimelineAdapter.java @@ -0,0 +1,48 @@ +package com.fastaccess.ui.adapter; + +import android.support.annotation.NonNull; +import android.support.v7.widget.StaggeredGridLayoutManager; +import android.view.ViewGroup; + +import com.fastaccess.data.dao.PullRequestAdapterModel; +import com.fastaccess.ui.adapter.viewholder.PullRequestDetailsViewHolder; +import com.fastaccess.ui.adapter.viewholder.PullRequestTimelineViewHolder; +import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.List; + +/** + * Created by Kosh on 13 Dec 2016, 1:44 AM + */ + +public class PullRequestTimelineAdapter extends BaseRecyclerAdapter> { + + public PullRequestTimelineAdapter(@NonNull List data) { + super(data); + } + + @Override protected BaseViewHolder viewHolder(ViewGroup parent, int viewType) { + if (viewType == PullRequestAdapterModel.HEADER) { + return PullRequestDetailsViewHolder.newInstance(parent, this); + } + return PullRequestTimelineViewHolder.newInstance(parent, this); + } + + @Override protected void onBindView(BaseViewHolder holder, int position) { + PullRequestAdapterModel model = getItem(position); + if (model.getType() == PullRequestAdapterModel.HEADER) { + ((PullRequestDetailsViewHolder) holder).bind(model); + StaggeredGridLayoutManager.LayoutParams layoutParams = (StaggeredGridLayoutManager.LayoutParams) holder.itemView.getLayoutParams(); + layoutParams.setFullSpan(true); + } else { + ((PullRequestTimelineViewHolder) holder).bind(model); + } + } + + @Override public int getItemViewType(int position) { + return getData().get(position).getType(); + } +} + diff --git a/app/src/main/java/com/fastaccess/ui/adapter/ReleasesAdapter.java b/app/src/main/java/com/fastaccess/ui/adapter/ReleasesAdapter.java new file mode 100644 index 00000000..491339b4 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/ReleasesAdapter.java @@ -0,0 +1,30 @@ +package com.fastaccess.ui.adapter; + +import android.support.annotation.NonNull; +import android.view.ViewGroup; + +import com.fastaccess.data.dao.ReleasesModel; +import com.fastaccess.ui.adapter.viewholder.ReleasesViewHolder; +import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.List; + +/** + * Created by Kosh on 11 Nov 2016, 2:07 PM + */ + +public class ReleasesAdapter extends BaseRecyclerAdapter> { + + public ReleasesAdapter(@NonNull List data) { + super(data); + } + + @Override protected ReleasesViewHolder viewHolder(ViewGroup parent, int viewType) { + return ReleasesViewHolder.newInstance(parent, this); + } + + @Override protected void onBindView(ReleasesViewHolder holder, int position) { + holder.bind(getItem(position)); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/adapter/RepoFilePathsAdapter.java b/app/src/main/java/com/fastaccess/ui/adapter/RepoFilePathsAdapter.java new file mode 100644 index 00000000..c987c17a --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/RepoFilePathsAdapter.java @@ -0,0 +1,32 @@ +package com.fastaccess.ui.adapter; + +import android.support.annotation.NonNull; +import android.view.ViewGroup; + +import com.fastaccess.data.dao.RepoFilesModel; +import com.fastaccess.ui.adapter.viewholder.RepoFilePathsViewHolder; +import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.ArrayList; + +/** + * Created by Kosh on 11 Nov 2016, 2:07 PM + */ + +public class RepoFilePathsAdapter extends BaseRecyclerAdapter> { + + + public RepoFilePathsAdapter(@NonNull ArrayList eventsModels) { + super(eventsModels); + } + + @Override protected RepoFilePathsViewHolder viewHolder(ViewGroup parent, int viewType) { + return RepoFilePathsViewHolder.newInstance(parent, this); + } + + @Override protected void onBindView(RepoFilePathsViewHolder holder, int position) { + holder.bind(getItem(position)); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/adapter/RepoFilesAdapter.java b/app/src/main/java/com/fastaccess/ui/adapter/RepoFilesAdapter.java new file mode 100644 index 00000000..6c430842 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/RepoFilesAdapter.java @@ -0,0 +1,32 @@ +package com.fastaccess.ui.adapter; + +import android.support.annotation.NonNull; +import android.view.ViewGroup; + +import com.fastaccess.data.dao.RepoFilesModel; +import com.fastaccess.ui.adapter.viewholder.RepoFilesViewHolder; +import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.ArrayList; + +/** + * Created by Kosh on 11 Nov 2016, 2:07 PM + */ + +public class RepoFilesAdapter extends BaseRecyclerAdapter> { + + + public RepoFilesAdapter(@NonNull ArrayList eventsModels) { + super(eventsModels); + } + + @Override protected RepoFilesViewHolder viewHolder(ViewGroup parent, int viewType) { + return RepoFilesViewHolder.newInstance(parent, this); + } + + @Override protected void onBindView(RepoFilesViewHolder holder, int position) { + holder.bind(getItem(position)); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/adapter/ReposAdapter.java b/app/src/main/java/com/fastaccess/ui/adapter/ReposAdapter.java new file mode 100644 index 00000000..43d25af7 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/ReposAdapter.java @@ -0,0 +1,38 @@ +package com.fastaccess.ui.adapter; + +import android.support.annotation.NonNull; +import android.view.ViewGroup; + +import com.fastaccess.data.dao.RepoModel; +import com.fastaccess.ui.adapter.viewholder.ReposViewHolder; +import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.List; + +/** + * Created by Kosh on 11 Nov 2016, 2:07 PM + */ + +public class ReposAdapter extends BaseRecyclerAdapter> { + private boolean isStarred; + private boolean withImage; + + public ReposAdapter(@NonNull List data, boolean isStarred) { + this(data, isStarred, false); + } + + public ReposAdapter(@NonNull List data, boolean isStarred, boolean withImage) { + super(data); + this.isStarred = isStarred; + this.withImage = withImage; + } + + @Override protected ReposViewHolder viewHolder(ViewGroup parent, int viewType) { + return ReposViewHolder.newInstance(parent, this); + } + + @Override protected void onBindView(ReposViewHolder holder, int position) { + holder.bind(getItem(position), isStarred, withImage); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/adapter/SearchCodeAdapter.java b/app/src/main/java/com/fastaccess/ui/adapter/SearchCodeAdapter.java new file mode 100644 index 00000000..ef9419c7 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/SearchCodeAdapter.java @@ -0,0 +1,29 @@ +package com.fastaccess.ui.adapter; + +import android.support.annotation.NonNull; +import android.view.ViewGroup; + +import com.fastaccess.data.dao.SearchCodeModel; +import com.fastaccess.ui.adapter.viewholder.SearchCodeViewHolder; +import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.List; + +/** + * Created by Kosh on 11 Nov 2016, 2:07 PM + */ + +public class SearchCodeAdapter extends BaseRecyclerAdapter> { + public SearchCodeAdapter(@NonNull List data) { + super(data); + } + + @Override protected SearchCodeViewHolder viewHolder(ViewGroup parent, int viewType) { + return SearchCodeViewHolder.newInstance(parent, this); + } + + @Override protected void onBindView(SearchCodeViewHolder holder, int position) { + holder.bind(getItem(position)); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/adapter/SimpleListAdapter.java b/app/src/main/java/com/fastaccess/ui/adapter/SimpleListAdapter.java new file mode 100644 index 00000000..066f39ae --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/SimpleListAdapter.java @@ -0,0 +1,27 @@ +package com.fastaccess.ui.adapter; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.ViewGroup; + +import com.fastaccess.R; +import com.fastaccess.ui.adapter.viewholder.SimpleViewHolder; +import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.List; + +public class SimpleListAdapter extends BaseRecyclerAdapter, + SimpleViewHolder.OnItemClickListener> { + public SimpleListAdapter(@NonNull List data, @Nullable SimpleViewHolder.OnItemClickListener listener) { + super(data, listener); + } + + @Override protected SimpleViewHolder viewHolder(ViewGroup parent, int viewType) { + return new SimpleViewHolder<>(BaseViewHolder.getView(parent, R.layout.simple_row_item), this); + } + + @Override protected void onBindView(SimpleViewHolder holder, int position) { + holder.bind(getItem(position)); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/ui/adapter/UsersAdapter.java b/app/src/main/java/com/fastaccess/ui/adapter/UsersAdapter.java new file mode 100644 index 00000000..e84a8e7a --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/UsersAdapter.java @@ -0,0 +1,37 @@ +package com.fastaccess.ui.adapter; + +import android.support.annotation.NonNull; +import android.view.ViewGroup; + +import com.fastaccess.data.dao.UserModel; +import com.fastaccess.ui.adapter.viewholder.UsersViewHolder; +import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.ArrayList; + +/** + * Created by Kosh on 11 Nov 2016, 2:07 PM + */ + +public class UsersAdapter extends BaseRecyclerAdapter> { + + private boolean isContributor; + + public UsersAdapter(@NonNull ArrayList list) { + this(list, false); + } + + public UsersAdapter(@NonNull ArrayList list, boolean isContributor) { + super(list); + this.isContributor = isContributor; + } + + @Override protected UsersViewHolder viewHolder(ViewGroup parent, int viewType) { + return new UsersViewHolder(UsersViewHolder.getView(parent), this); + } + + @Override protected void onBindView(UsersViewHolder holder, int position) { + holder.bind(getItem(position), isContributor); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/adapter/viewholder/CommentsViewHolder.java b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/CommentsViewHolder.java new file mode 100644 index 00000000..1431859c --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/CommentsViewHolder.java @@ -0,0 +1,60 @@ +package com.fastaccess.ui.adapter.viewholder; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; +import android.view.ViewGroup; + +import com.fastaccess.R; +import com.fastaccess.data.dao.CommentsModel; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.helper.ParseDateFormat; +import com.fastaccess.provider.markdown.MarkDownProvider; +import com.fastaccess.ui.widgets.AvatarLayout; +import com.fastaccess.ui.widgets.FontTextView; +import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import butterknife.BindView; + +/** + * Created by Kosh on 11 Nov 2016, 2:08 PM + */ + +public class CommentsViewHolder extends BaseViewHolder { + + @BindView(R.id.avatarView) AvatarLayout avatar; + @BindView(R.id.date) FontTextView date; + @BindView(R.id.name) FontTextView name; + @BindView(R.id.comment) FontTextView comment; + + @Override public void onClick(View v) { + super.onClick(v); + } + + private CommentsViewHolder(@NonNull View itemView, @Nullable BaseRecyclerAdapter adapter) { + super(itemView, adapter); +// comment.setOnLongClickListener(this); +// comment.setOnClickListener(this); + } + + public static CommentsViewHolder newInstance(@NonNull ViewGroup viewGroup, @Nullable BaseRecyclerAdapter adapter) { + return new CommentsViewHolder(getView(viewGroup, R.layout.comments_row_item), adapter); + } + + @Override public void bind(@NonNull CommentsModel commentsModel) { + if (commentsModel.getUser() != null) { + avatar.setUrl(commentsModel.getUser().getAvatarUrl(), commentsModel.getUser().getLogin()); + } else { + avatar.setUrl(null, null); + } + if (!InputHelper.isEmpty(commentsModel.getBody())) { + comment.setNestedScrollingEnabled(false); + if (!InputHelper.isEmpty(commentsModel.getBody())) { + MarkDownProvider.setMdText(comment, commentsModel.getBody()); + } + } + name.setText(commentsModel.getUser() != null ? commentsModel.getUser().getLogin() : "Anonymous"); + date.setText(ParseDateFormat.getTimeAgo(commentsModel.getCreatedAt())); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/adapter/viewholder/CommitFilesViewHolder.java b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/CommitFilesViewHolder.java new file mode 100644 index 00000000..b168bbce --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/CommitFilesViewHolder.java @@ -0,0 +1,124 @@ +package com.fastaccess.ui.adapter.viewholder; + +import android.graphics.Color; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.SpannableString; +import android.text.SpannableStringBuilder; +import android.text.TextUtils; +import android.view.View; +import android.view.ViewGroup; + +import com.fastaccess.R; +import com.fastaccess.data.dao.CommitFileModel; +import com.fastaccess.ui.adapter.CommitFilesAdapter; +import com.fastaccess.ui.widgets.DiffLineSpan; +import com.fastaccess.ui.widgets.FontTextView; +import com.fastaccess.ui.widgets.SpannableBuilder; +import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import butterknife.BindString; +import butterknife.BindView; + +/** + * Created by Kosh on 15 Feb 2017, 10:29 PM + */ + +public class CommitFilesViewHolder extends BaseViewHolder { + + + @BindView(R.id.name) FontTextView name; + @BindView(R.id.patch) FontTextView patch; + @BindView(R.id.changes) FontTextView changes; + @BindView(R.id.addition) FontTextView addition; + @BindView(R.id.deletion) FontTextView deletion; + @BindView(R.id.status) FontTextView status; + @BindView(R.id.toggle) View toggle; + @BindString(R.string.changes) String changesText; + @BindString(R.string.addition) String additionText; + @BindString(R.string.delete) String deletionText; + @BindString(R.string.status) String statusText; + private String pathText; + private CommitFilesAdapter.OnTogglePatch onTogglePatch; + + @Override public void onClick(View v) { + int position = getAdapterPosition(); + onTogglePatch.onToggle(position, !onTogglePatch.isCollapsed(position)); + } + + private void onToggle(boolean expanded) { + if (!expanded) { + patch.setText("....."); + toggle.setRotation(0.0f); + } else { + setPatchText(pathText); + toggle.setRotation(180f); + } + } + + private CommitFilesViewHolder(@NonNull View itemView, @Nullable BaseRecyclerAdapter adapter, + @NonNull CommitFilesAdapter.OnTogglePatch onTogglePatch) { + super(itemView, adapter); + this.onTogglePatch = onTogglePatch; + } + + public static CommitFilesViewHolder newInstance(ViewGroup viewGroup, BaseRecyclerAdapter adapter, + @NonNull CommitFilesAdapter.OnTogglePatch onTogglePatch) { + return new CommitFilesViewHolder(getView(viewGroup, R.layout.commit_file_row_item), adapter, onTogglePatch); + } + + @Override public void bind(@NonNull CommitFileModel commit) { + this.pathText = commit.getPatch(); + name.setText(commit.getFilename()); + changes.setText(SpannableBuilder.builder() + .append(changesText) + .append("\n") + .bold(String.valueOf(commit.getChanges()))); + addition.setText(SpannableBuilder.builder() + .append(additionText) + .append("\n") + .bold(String.valueOf(commit.getAdditions()))); + deletion.setText(SpannableBuilder.builder() + .append(deletionText) + .append("\n") + .bold(String.valueOf(commit.getDeletions()))); + status.setText(SpannableBuilder.builder() + .append(statusText) + .append("\n") + .bold(String.valueOf(commit.getStatus()))); + onToggle(onTogglePatch.isCollapsed(getAdapterPosition())); + } + + private void setPatchText(@NonNull String text) { + if (!TextUtils.isEmpty(text)) { + String[] split = text.split("\\r?\\n|\\r"); + if (split.length > 0) { + SpannableStringBuilder builder = new SpannableStringBuilder(); + int lines = split.length; + for (int i = 0; i < lines; i++) { + String token = split[i]; + if (i < (lines - 1)) { + token = token.concat("\n"); + } + char firstChar = token.charAt(0); + int color = Color.TRANSPARENT; + if (firstChar == '+') { + color = Color.parseColor("#CCFFCC"); + } else if (firstChar == '-') { + color = Color.parseColor("#FFDDDD"); + } else if (token.startsWith("@@")) { + color = Color.parseColor("#EEEEEE"); + } + SpannableString spannableDiff = new SpannableString(token); + if (color != Color.TRANSPARENT) { + DiffLineSpan span = new DiffLineSpan(color, patch.getPaddingLeft()); + spannableDiff.setSpan(span, 0, token.length(), SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE); + } + builder.append(spannableDiff); + } + patch.setText(builder); + } + } + } +} diff --git a/app/src/main/java/com/fastaccess/ui/adapter/viewholder/CommitsViewHolder.java b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/CommitsViewHolder.java new file mode 100644 index 00000000..c311a3d4 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/CommitsViewHolder.java @@ -0,0 +1,52 @@ +package com.fastaccess.ui.adapter.viewholder; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; +import android.view.ViewGroup; + +import com.fastaccess.R; +import com.fastaccess.data.dao.CommitModel; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.helper.ParseDateFormat; +import com.fastaccess.ui.widgets.AvatarLayout; +import com.fastaccess.ui.widgets.FontTextView; +import com.fastaccess.ui.widgets.SpannableBuilder; +import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.Date; + +import butterknife.BindView; + +/** + * Created by Kosh on 11 Nov 2016, 2:08 PM + */ + +public class CommitsViewHolder extends BaseViewHolder { + + @BindView(R.id.title) FontTextView title; + @BindView(R.id.avatarLayout) AvatarLayout avatarLayout; + @BindView(R.id.details) FontTextView details; + + private CommitsViewHolder(@NonNull View itemView, @Nullable BaseRecyclerAdapter adapter) { + super(itemView, adapter); + } + + public static CommitsViewHolder newInstance(ViewGroup viewGroup, BaseRecyclerAdapter adapter) { + return new CommitsViewHolder(getView(viewGroup, R.layout.issue_row_item), adapter); + } + + @Override public void bind(@NonNull CommitModel commit) { + title.setText(commit.getGitCommit().getMessage()); + String login = commit.getAuthor() != null ? commit.getAuthor().getLogin() : commit.getGitCommit().getAuthor().getName(); + String avatar = commit.getAuthor() != null ? commit.getAuthor().getAvatarUrl() : null; + Date date = commit.getGitCommit().getAuthor().getDate(); + details.setText(SpannableBuilder.builder() + .bold(InputHelper.toNA(login)) + .append(" ") + .append(ParseDateFormat.getTimeAgo(date))); + avatarLayout.setUrl(avatar, login); + avatarLayout.setVisibility(View.VISIBLE); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/adapter/viewholder/FeedsViewHolder.java b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/FeedsViewHolder.java new file mode 100644 index 00000000..d9bec309 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/FeedsViewHolder.java @@ -0,0 +1,51 @@ +package com.fastaccess.ui.adapter.viewholder; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; +import android.view.ViewGroup; + +import com.fastaccess.R; +import com.fastaccess.data.dao.EventsModel; +import com.fastaccess.data.dao.types.EventsType; +import com.fastaccess.helper.ParseDateFormat; +import com.fastaccess.ui.widgets.AvatarLayout; +import com.fastaccess.ui.widgets.FontTextView; +import com.fastaccess.ui.widgets.SpannableBuilder; +import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import butterknife.BindView; + +/** + * Created by Kosh on 11 Nov 2016, 2:08 PM + */ + +public class FeedsViewHolder extends BaseViewHolder { + + @BindView(R.id.avatarLayout) AvatarLayout avatar; + @BindView(R.id.title) FontTextView title; + @BindView(R.id.date) FontTextView date; + + public FeedsViewHolder(@NonNull View itemView, @Nullable BaseRecyclerAdapter adapter) { + super(itemView, adapter); + } + + public static View getView(@NonNull ViewGroup viewGroup) { + return getView(viewGroup, R.layout.feeds_row_item); + } + + @Override public void bind(@NonNull EventsModel eventsModel) { + if (eventsModel.getActor() != null) { + avatar.setUrl(eventsModel.getActor().getAvatarUrl(), eventsModel.getActor().getLogin()); + } else { + avatar.setUrl(null, null); + } + SpannableBuilder spannableBuilder = SpannableBuilder.builder(); + spannableBuilder.append(eventsModel.getActor() != null ? eventsModel.getActor().getLogin() : "n/a").append(" "); + spannableBuilder.bold(eventsModel.getType().getType()).append(" "); + spannableBuilder.append(eventsModel.getRepo() != null ? eventsModel.getRepo().getName() : "n/a"); + title.setText(spannableBuilder); + date.setText(ParseDateFormat.getTimeAgo(eventsModel.getCreatedAt())); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/adapter/viewholder/GistFilesViewHolder.java b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/GistFilesViewHolder.java new file mode 100644 index 00000000..a81dfc58 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/GistFilesViewHolder.java @@ -0,0 +1,42 @@ +package com.fastaccess.ui.adapter.viewholder; + +import android.support.annotation.NonNull; +import android.text.format.Formatter; +import android.view.View; +import android.view.ViewGroup; + +import com.fastaccess.R; +import com.fastaccess.data.dao.FilesListModel; +import com.fastaccess.ui.adapter.GistFilesAdapter; +import com.fastaccess.ui.widgets.FontTextView; +import com.fastaccess.ui.widgets.SpannableBuilder; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import butterknife.BindView; + +/** + * Created by Kosh on 12 Nov 2016, 3:44 PM + */ + +public class GistFilesViewHolder extends BaseViewHolder { + + + @BindView(R.id.fileName) FontTextView fileName; + @BindView(R.id.language) FontTextView language; + @BindView(R.id.size) FontTextView size; + + private GistFilesViewHolder(@NonNull View itemView, GistFilesAdapter adapter) { + super(itemView, adapter); + + } + + public static GistFilesViewHolder newInstance(@NonNull ViewGroup parent, GistFilesAdapter adapter) { + return new GistFilesViewHolder(getView(parent, R.layout.gist_files_row_item), adapter); + } + + @Override public void bind(@NonNull FilesListModel filesListModel) { + fileName.setText(filesListModel.getFilename()); + language.setText(SpannableBuilder.builder().bold(filesListModel.getType())); + size.setText(Formatter.formatFileSize(size.getContext(), filesListModel.getSize())); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/adapter/viewholder/GistsViewHolder.java b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/GistsViewHolder.java new file mode 100644 index 00000000..2e0cee64 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/GistsViewHolder.java @@ -0,0 +1,51 @@ +package com.fastaccess.ui.adapter.viewholder; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; +import android.view.ViewGroup; + +import com.fastaccess.R; +import com.fastaccess.data.dao.GistsModel; +import com.fastaccess.helper.ParseDateFormat; +import com.fastaccess.ui.widgets.AvatarLayout; +import com.fastaccess.ui.widgets.FontTextView; +import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import butterknife.BindView; + +/** + * Created by Kosh on 11 Nov 2016, 2:08 PM + */ + +public class GistsViewHolder extends BaseViewHolder { + + @BindView(R.id.avatarLayout) AvatarLayout avatar; + @BindView(R.id.title) FontTextView title; + @BindView(R.id.date) FontTextView date; + + public GistsViewHolder(@NonNull View itemView, @Nullable BaseRecyclerAdapter adapter) { + super(itemView, adapter); + title.setMaxLines(2); + } + + public static View getView(@NonNull ViewGroup viewGroup) { + return getView(viewGroup, R.layout.feeds_row_item); + } + + public void bind(@NonNull GistsModel item, boolean isFromProfile) { + if (!isFromProfile) { + avatar.setVisibility(View.VISIBLE); + String url = item.getOwner() != null ? item.getOwner().getAvatarUrl() : item.getUser() != null ? item.getUser().getAvatarUrl() : null; + String login = item.getOwner() != null ? item.getOwner().getLogin() : item.getUser() != null ? item.getUser().getLogin() : null; + avatar.setUrl(url, login); + } else { + avatar.setVisibility(View.GONE); + } + title.setText(item.getDisplayTitle(isFromProfile)); + date.setText(ParseDateFormat.getTimeAgo(item.getCreatedAt())); + } + + @Override public void bind(@NonNull GistsModel item) {} +} diff --git a/app/src/main/java/com/fastaccess/ui/adapter/viewholder/IssueDetailsViewHolder.java b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/IssueDetailsViewHolder.java new file mode 100644 index 00000000..13a6dbf6 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/IssueDetailsViewHolder.java @@ -0,0 +1,50 @@ +package com.fastaccess.ui.adapter.viewholder; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; +import android.view.ViewGroup; + +import com.fastaccess.R; +import com.fastaccess.data.dao.IssueEventAdapterModel; +import com.fastaccess.data.dao.IssueModel; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.helper.ParseDateFormat; +import com.fastaccess.provider.markdown.MarkDownProvider; +import com.fastaccess.ui.widgets.AvatarLayout; +import com.fastaccess.ui.widgets.FontTextView; +import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import butterknife.BindView; + +/** + * Created by Kosh on 13 Dec 2016, 1:03 AM + */ + +public class IssueDetailsViewHolder extends BaseViewHolder { + + @BindView(R.id.avatarView) AvatarLayout avatarView; + @BindView(R.id.name) FontTextView name; + @BindView(R.id.date) FontTextView date; + @BindView(R.id.description) FontTextView description; + + private IssueDetailsViewHolder(@NonNull View itemView, @Nullable BaseRecyclerAdapter adapter) { + super(itemView, adapter); + } + + public static IssueDetailsViewHolder newInstance(ViewGroup viewGroup, BaseRecyclerAdapter adapter) { + return new IssueDetailsViewHolder(getView(viewGroup, R.layout.issue_detail_header_row_item), adapter); + } + + @Override public void bind(@NonNull IssueEventAdapterModel model) { + IssueModel issueModel = model.getIssueModel(); + avatarView.setUrl(issueModel.getUser().getAvatarUrl(), issueModel.getUser().getLogin()); + name.setText(issueModel.getUser().getLogin()); + date.setText(ParseDateFormat.getTimeAgo(issueModel.getCreatedAt())); + description.setNestedScrollingEnabled(false); + if (!InputHelper.isEmpty(issueModel.getBody())) { + MarkDownProvider.setMdText(description, issueModel.getBody()); + } + } +} diff --git a/app/src/main/java/com/fastaccess/ui/adapter/viewholder/IssueTimelineViewHolder.java b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/IssueTimelineViewHolder.java new file mode 100644 index 00000000..31e782c2 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/IssueTimelineViewHolder.java @@ -0,0 +1,93 @@ +package com.fastaccess.ui.adapter.viewholder; + +import android.graphics.Color; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; +import android.view.ViewGroup; + +import com.fastaccess.R; +import com.fastaccess.data.dao.IssueEventAdapterModel; +import com.fastaccess.data.dao.IssueEventModel; +import com.fastaccess.data.dao.LabelModel; +import com.fastaccess.data.dao.types.IssueEventType; +import com.fastaccess.helper.ParseDateFormat; +import com.fastaccess.ui.widgets.AvatarLayout; +import com.fastaccess.ui.widgets.FontTextView; +import com.fastaccess.ui.widgets.ForegroundImageView; +import com.fastaccess.ui.widgets.SpannableBuilder; +import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import butterknife.BindString; +import butterknife.BindView; + +/** + * Created by Kosh on 13 Dec 2016, 1:42 AM + */ + +public class IssueTimelineViewHolder extends BaseViewHolder { + + @BindView(R.id.stateImage) ForegroundImageView stateImage; + @BindView(R.id.avatarLayout) AvatarLayout avatarLayout; + @BindView(R.id.stateText) FontTextView stateText; + @BindString(R.string.to) String to; + + private IssueTimelineViewHolder(@NonNull View itemView, @Nullable BaseRecyclerAdapter adapter) { + super(itemView, adapter); + } + + public static IssueTimelineViewHolder newInstance(ViewGroup viewGroup, BaseRecyclerAdapter adapter) { + return new IssueTimelineViewHolder(getView(viewGroup, R.layout.issue_timeline_row_item), adapter); + } + + @Override public void bind(@NonNull IssueEventAdapterModel model) { + IssueEventModel issueEventModel = model.getIssueEvent(); + IssueEventType event = issueEventModel.getEvent(); + SpannableBuilder spannableBuilder = SpannableBuilder.builder() + .bold(issueEventModel.getActor().getLogin()); + if (event != null) { + stateImage.setContentDescription(event.name()); + spannableBuilder + .append(" ") + .append(event.name()); + stateImage.setImageResource(event.getIconResId()); + if (event == IssueEventType.labeled || event == IssueEventType.unlabeled) { + LabelModel labelModel = issueEventModel.getLabel(); + spannableBuilder + .append(" ") + .background(labelModel.getName(), Color.parseColor("#" + labelModel.getColor())); + } else if (event == IssueEventType.assigned || event == IssueEventType.unassigned) { + spannableBuilder + .append(" ") + .bold(issueEventModel.getAssigner().getLogin()); + } else if (event == IssueEventType.milestoned || event == IssueEventType.demilestoned) { + spannableBuilder + .append(" ") + .append(to) + .append(" ") + .bold(issueEventModel.getMilestone().getTitle()); + } else if (event == IssueEventType.renamed) { + spannableBuilder + .append(" ") + .bold(issueEventModel.getRename().getFromValue()) + .append(" ") + .append(to) + .append(" ") + .bold(issueEventModel.getRename().getToValue()); + } else if (event == IssueEventType.referenced || event == IssueEventType.merged) { + spannableBuilder + .append(" ") + .url("This"); + } + } else { + stateImage.setImageResource(R.drawable.ic_label); + } + avatarLayout.setUrl(issueEventModel.getActor().getAvatarUrl(), issueEventModel.getActor().getLogin()); + stateText.setText(spannableBuilder + .append(" ") + .append(ParseDateFormat.getTimeAgo(issueEventModel.getCreatedAt()))); + } + + +} diff --git a/app/src/main/java/com/fastaccess/ui/adapter/viewholder/IssuesViewHolder.java b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/IssuesViewHolder.java new file mode 100644 index 00000000..bf79afbd --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/IssuesViewHolder.java @@ -0,0 +1,51 @@ +package com.fastaccess.ui.adapter.viewholder; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; +import android.view.ViewGroup; + +import com.fastaccess.R; +import com.fastaccess.data.dao.IssueModel; +import com.fastaccess.helper.ParseDateFormat; +import com.fastaccess.ui.widgets.AvatarLayout; +import com.fastaccess.ui.widgets.FontTextView; +import com.fastaccess.ui.widgets.SpannableBuilder; +import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import butterknife.BindString; +import butterknife.BindView; + +/** + * Created by Kosh on 11 Nov 2016, 2:08 PM + */ + +public class IssuesViewHolder extends BaseViewHolder { + + @BindView(R.id.title) FontTextView title; + @BindView(R.id.avatarLayout) AvatarLayout avatarLayout; + @BindView(R.id.details) FontTextView details; + @BindString(R.string.by) String by; + + private IssuesViewHolder(@NonNull View itemView, @Nullable BaseRecyclerAdapter adapter) { + super(itemView, adapter); + } + + public static IssuesViewHolder newInstance(ViewGroup viewGroup, BaseRecyclerAdapter adapter) { + return new IssuesViewHolder(getView(viewGroup, R.layout.issue_row_item), adapter); + } + + public void bind(@NonNull IssueModel issueModel, boolean withAvatar) { + title.setText(issueModel.getTitle()); + details.setText(SpannableBuilder.builder().append(itemView.getResources().getString(issueModel.getState().getStatus())) + .append(" ").append(by).append(" ").append(issueModel.getUser().getLogin()).append(" ") + .append(ParseDateFormat.getTimeAgo(issueModel.getCreatedAt()))); + if (withAvatar) { + avatarLayout.setUrl(issueModel.getUser().getAvatarUrl(), issueModel.getUser().getLogin()); + avatarLayout.setVisibility(View.VISIBLE); + } + } + + @Override public void bind(@NonNull IssueModel issueModel) {} +} diff --git a/app/src/main/java/com/fastaccess/ui/adapter/viewholder/PullRequestDetailsViewHolder.java b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/PullRequestDetailsViewHolder.java new file mode 100644 index 00000000..d4866835 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/PullRequestDetailsViewHolder.java @@ -0,0 +1,50 @@ +package com.fastaccess.ui.adapter.viewholder; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; +import android.view.ViewGroup; + +import com.fastaccess.R; +import com.fastaccess.data.dao.PullRequestAdapterModel; +import com.fastaccess.data.dao.PullRequestModel; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.helper.ParseDateFormat; +import com.fastaccess.provider.markdown.MarkDownProvider; +import com.fastaccess.ui.widgets.AvatarLayout; +import com.fastaccess.ui.widgets.FontTextView; +import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import butterknife.BindView; + +/** + * Created by Kosh on 13 Dec 2016, 1:03 AM + */ + +public class PullRequestDetailsViewHolder extends BaseViewHolder { + + @BindView(R.id.avatarView) AvatarLayout avatarView; + @BindView(R.id.name) FontTextView name; + @BindView(R.id.date) FontTextView date; + @BindView(R.id.description) FontTextView description; + + private PullRequestDetailsViewHolder(@NonNull View itemView, @Nullable BaseRecyclerAdapter adapter) { + super(itemView, adapter); + } + + public static PullRequestDetailsViewHolder newInstance(ViewGroup viewGroup, BaseRecyclerAdapter adapter) { + return new PullRequestDetailsViewHolder(getView(viewGroup, R.layout.issue_detail_header_row_item), adapter); + } + + @Override public void bind(@NonNull PullRequestAdapterModel model) { + PullRequestModel issueModel = model.getPullRequest(); + avatarView.setUrl(issueModel.getUser().getAvatarUrl(), issueModel.getUser().getLogin()); + name.setText(issueModel.getUser().getLogin()); + date.setText(ParseDateFormat.getTimeAgo(issueModel.getCreatedAt())); + description.setNestedScrollingEnabled(false); + if (!InputHelper.isEmpty(issueModel.getBody())) { + MarkDownProvider.setMdText(description, issueModel.getBody()); + } + } +} diff --git a/app/src/main/java/com/fastaccess/ui/adapter/viewholder/PullRequestTimelineViewHolder.java b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/PullRequestTimelineViewHolder.java new file mode 100644 index 00000000..1f231f30 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/PullRequestTimelineViewHolder.java @@ -0,0 +1,93 @@ +package com.fastaccess.ui.adapter.viewholder; + +import android.graphics.Color; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; +import android.view.ViewGroup; + +import com.fastaccess.R; +import com.fastaccess.data.dao.IssueEventModel; +import com.fastaccess.data.dao.LabelModel; +import com.fastaccess.data.dao.PullRequestAdapterModel; +import com.fastaccess.data.dao.types.IssueEventType; +import com.fastaccess.helper.ParseDateFormat; +import com.fastaccess.ui.widgets.AvatarLayout; +import com.fastaccess.ui.widgets.FontTextView; +import com.fastaccess.ui.widgets.ForegroundImageView; +import com.fastaccess.ui.widgets.SpannableBuilder; +import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import butterknife.BindString; +import butterknife.BindView; + +/** + * Created by Kosh on 13 Dec 2016, 1:42 AM + */ + +public class PullRequestTimelineViewHolder extends BaseViewHolder { + + @BindView(R.id.stateImage) ForegroundImageView stateImage; + @BindView(R.id.avatarLayout) AvatarLayout avatarLayout; + @BindView(R.id.stateText) FontTextView stateText; + @BindString(R.string.to) String to; + + private PullRequestTimelineViewHolder(@NonNull View itemView, @Nullable BaseRecyclerAdapter adapter) { + super(itemView, adapter); + } + + public static PullRequestTimelineViewHolder newInstance(ViewGroup viewGroup, BaseRecyclerAdapter adapter) { + return new PullRequestTimelineViewHolder(getView(viewGroup, R.layout.issue_timeline_row_item), adapter); + } + + @Override public void bind(@NonNull PullRequestAdapterModel model) { + IssueEventModel issueEventModel = model.getIssueEvent(); + IssueEventType event = issueEventModel.getEvent(); + SpannableBuilder spannableBuilder = SpannableBuilder.builder() + .bold(issueEventModel.getActor().getLogin()); + if (event != null) { + stateImage.setContentDescription(event.name()); + spannableBuilder + .append(" ") + .append(event.name()); + stateImage.setImageResource(event.getIconResId()); + if (event == IssueEventType.labeled || event == IssueEventType.unlabeled) { + LabelModel labelModel = issueEventModel.getLabel(); + spannableBuilder + .append(" ") + .background(labelModel.getName(), Color.parseColor("#" + labelModel.getColor())); + } else if (event == IssueEventType.assigned || event == IssueEventType.unassigned) { + spannableBuilder + .append(" ") + .bold(issueEventModel.getAssigner().getLogin()); + } else if (event == IssueEventType.milestoned || event == IssueEventType.demilestoned) { + spannableBuilder + .append(" ") + .append(to) + .append(" ") + .bold(issueEventModel.getMilestone().getTitle()); + } else if (event == IssueEventType.renamed) { + spannableBuilder + .append(" ") + .bold(issueEventModel.getRename().getFromValue()) + .append(" ") + .append(to) + .append(" ") + .bold(issueEventModel.getRename().getToValue()); + } else if (event == IssueEventType.referenced || event == IssueEventType.merged) { + spannableBuilder + .append(" ") + .url("This"); + } + } else { + stateImage.setImageResource(R.drawable.ic_label); + } + avatarLayout.setUrl(issueEventModel.getActor().getAvatarUrl(), issueEventModel.getActor().getLogin()); + stateText.setText(spannableBuilder + .append(" ") + .append(ParseDateFormat.getTimeAgo(issueEventModel.getCreatedAt()))); + } + + +} diff --git a/app/src/main/java/com/fastaccess/ui/adapter/viewholder/PullRequestViewHolder.java b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/PullRequestViewHolder.java new file mode 100644 index 00000000..a25e8ce2 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/PullRequestViewHolder.java @@ -0,0 +1,47 @@ +package com.fastaccess.ui.adapter.viewholder; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; +import android.view.ViewGroup; + +import com.fastaccess.R; +import com.fastaccess.data.dao.PullRequestModel; +import com.fastaccess.ui.widgets.AvatarLayout; +import com.fastaccess.ui.widgets.FontTextView; +import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import butterknife.BindString; +import butterknife.BindView; + +/** + * Created by Kosh on 11 Nov 2016, 2:08 PM + */ + +public class PullRequestViewHolder extends BaseViewHolder { + + @BindView(R.id.title) FontTextView title; + @BindView(R.id.avatarLayout) AvatarLayout avatarLayout; + @BindView(R.id.details) FontTextView details; + @BindString(R.string.by) String by; + + private PullRequestViewHolder(@NonNull View itemView, @Nullable BaseRecyclerAdapter adapter) { + super(itemView, adapter); + } + + public static PullRequestViewHolder newInstance(ViewGroup viewGroup, BaseRecyclerAdapter adapter) { + return new PullRequestViewHolder(getView(viewGroup, R.layout.issue_row_item), adapter); + } + + public void bind(@NonNull PullRequestModel pullRequest, boolean withAvatar) { + title.setText(pullRequest.getTitle()); + details.setText(PullRequestModel.getMergeBy(pullRequest, details.getContext())); + if (withAvatar) { + avatarLayout.setUrl(pullRequest.getUser().getAvatarUrl(), pullRequest.getUser().getLogin()); + avatarLayout.setVisibility(View.VISIBLE); + } + } + + @Override public void bind(@NonNull PullRequestModel issueModel) {} +} diff --git a/app/src/main/java/com/fastaccess/ui/adapter/viewholder/ReleasesViewHolder.java b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/ReleasesViewHolder.java new file mode 100644 index 00000000..2e63041f --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/ReleasesViewHolder.java @@ -0,0 +1,57 @@ +package com.fastaccess.ui.adapter.viewholder; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; +import android.view.ViewGroup; + +import com.fastaccess.R; +import com.fastaccess.data.dao.ReleasesModel; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.helper.ParseDateFormat; +import com.fastaccess.ui.widgets.AvatarLayout; +import com.fastaccess.ui.widgets.FontTextView; +import com.fastaccess.ui.widgets.ForegroundImageView; +import com.fastaccess.ui.widgets.SpannableBuilder; +import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import butterknife.BindString; +import butterknife.BindView; + +/** + * Created by Kosh on 11 Nov 2016, 2:08 PM + */ + +public class ReleasesViewHolder extends BaseViewHolder { + + + @BindView(R.id.avatarLayout) AvatarLayout avatarLayout; + @BindView(R.id.title) FontTextView title; + @BindView(R.id.details) FontTextView details; + @BindView(R.id.download) ForegroundImageView download; + @BindString(R.string.released) String released; + @BindString(R.string.drafted) String drafted; + + private ReleasesViewHolder(@NonNull View itemView, @Nullable BaseRecyclerAdapter adapter) { + super(itemView, adapter); + download.setOnClickListener(this); + download.setOnLongClickListener(this); + } + + public static ReleasesViewHolder newInstance(ViewGroup viewGroup, BaseRecyclerAdapter adapter) { + return new ReleasesViewHolder(getView(viewGroup, R.layout.releases_row_item), adapter); + } + + @Override public void bind(@NonNull ReleasesModel item) { + title.setText(SpannableBuilder.builder().bold(!InputHelper.isEmpty(item.getName()) ? item.getName() : item.getTagName())); + details.setText(SpannableBuilder.builder() + .append(item.getAuthor().getLogin()) + .append(" ") + .append(item.isDraft() ? drafted : released) + .append(" ") + .append(ParseDateFormat.getTimeAgo(item.getCreatedAt()))); + avatarLayout.setUrl(item.getAuthor().getAvatarUrl(), item.getAuthor().getLogin()); + avatarLayout.setVisibility(View.VISIBLE); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/adapter/viewholder/RepoFilePathsViewHolder.java b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/RepoFilePathsViewHolder.java new file mode 100644 index 00000000..fe88d532 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/RepoFilePathsViewHolder.java @@ -0,0 +1,36 @@ +package com.fastaccess.ui.adapter.viewholder; + +import android.support.annotation.NonNull; +import android.view.View; +import android.view.ViewGroup; + +import com.fastaccess.R; +import com.fastaccess.data.dao.RepoFilesModel; +import com.fastaccess.helper.Logger; +import com.fastaccess.ui.widgets.FontTextView; +import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import butterknife.BindView; + +/** + * Created by Kosh on 18 Feb 2017, 2:53 AM + */ + +public class RepoFilePathsViewHolder extends BaseViewHolder { + + @BindView(R.id.pathName) FontTextView pathName; + + private RepoFilePathsViewHolder(@NonNull View itemView, @NonNull BaseRecyclerAdapter baseAdapter) { + super(itemView, baseAdapter); + } + + public static RepoFilePathsViewHolder newInstance(ViewGroup viewGroup, BaseRecyclerAdapter adapter) { + return new RepoFilePathsViewHolder(getView(viewGroup, R.layout.file_path_row_item), adapter); + } + + @Override public void bind(@NonNull RepoFilesModel filesModel) { + Logger.e(filesModel.getPath()); + pathName.setText(filesModel.getName()); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/adapter/viewholder/RepoFilesViewHolder.java b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/RepoFilesViewHolder.java new file mode 100644 index 00000000..49024880 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/RepoFilesViewHolder.java @@ -0,0 +1,51 @@ +package com.fastaccess.ui.adapter.viewholder; + +import android.support.annotation.NonNull; +import android.text.format.Formatter; +import android.view.View; +import android.view.ViewGroup; + +import com.fastaccess.R; +import com.fastaccess.data.dao.RepoFilesModel; +import com.fastaccess.data.dao.types.FilesType; +import com.fastaccess.ui.widgets.FontTextView; +import com.fastaccess.ui.widgets.ForegroundImageView; +import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import butterknife.BindString; +import butterknife.BindView; + +/** + * Created by Kosh on 15 Feb 2017, 10:29 PM + */ + +public class RepoFilesViewHolder extends BaseViewHolder { + + @BindView(R.id.contentTypeImage) ForegroundImageView contentTypeImage; + @BindView(R.id.title) FontTextView title; + @BindView(R.id.size) FontTextView size; + @BindView(R.id.menu) ForegroundImageView menu; + @BindString(R.string.file) String file; + + private RepoFilesViewHolder(@NonNull View itemView, @NonNull BaseRecyclerAdapter adapter) { + super(itemView, adapter); + menu.setOnClickListener(this); + } + + public static RepoFilesViewHolder newInstance(ViewGroup viewGroup, BaseRecyclerAdapter adapter) { + return new RepoFilesViewHolder(getView(viewGroup, R.layout.repo_files_row_item), adapter); + } + + @Override public void bind(@NonNull RepoFilesModel filesModel) { + contentTypeImage.setImageResource(filesModel.getType().getIcon()); + contentTypeImage.setContentDescription(String.format("%s %s", filesModel.getName(), file)); + title.setText(filesModel.getName()); + if (filesModel.getType() == FilesType.file) { + size.setText(Formatter.formatFileSize(size.getContext(), filesModel.getSize())); + size.setVisibility(View.VISIBLE); + } else { + size.setVisibility(View.GONE); + } + } +} diff --git a/app/src/main/java/com/fastaccess/ui/adapter/viewholder/ReposViewHolder.java b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/ReposViewHolder.java new file mode 100644 index 00000000..a931f56d --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/ReposViewHolder.java @@ -0,0 +1,62 @@ +package com.fastaccess.ui.adapter.viewholder; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; +import android.view.ViewGroup; + +import com.fastaccess.R; +import com.fastaccess.data.dao.RepoModel; +import com.fastaccess.helper.ParseDateFormat; +import com.fastaccess.ui.widgets.AvatarLayout; +import com.fastaccess.ui.widgets.FontTextView; +import com.fastaccess.ui.widgets.SpannableBuilder; +import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.text.NumberFormat; + +import butterknife.BindString; +import butterknife.BindView; + +/** + * Created by Kosh on 11 Nov 2016, 2:08 PM + */ + +public class ReposViewHolder extends BaseViewHolder { + + @BindView(R.id.title) FontTextView title; + @BindView(R.id.date) FontTextView date; + @BindView(R.id.stars) FontTextView stars; + @BindView(R.id.forks) FontTextView forks; + @BindView(R.id.avatarLayout) AvatarLayout avatarLayout; + @BindString(R.string.forked) String forked; + + private ReposViewHolder(@NonNull View itemView, @Nullable BaseRecyclerAdapter adapter) { + super(itemView, adapter); + } + + public static ReposViewHolder newInstance(ViewGroup viewGroup, BaseRecyclerAdapter adapter) { + return new ReposViewHolder(getView(viewGroup, R.layout.repos_row_item), adapter); + } + + public void bind(@NonNull RepoModel repo, boolean isStarred, boolean withImage) { + if (repo.isFork()) { + title.setText(SpannableBuilder.builder().bold(forked).append(" ").append(repo.getName())); + } else { + title.setText(!isStarred ? repo.getName() : repo.getFullName()); + } + if (withImage) { + String avatar = repo.getOwner() != null ? repo.getOwner().getAvatarUrl() : null; + String login = repo.getOwner() != null ? repo.getOwner().getLogin() : null; + avatarLayout.setVisibility(View.VISIBLE); + avatarLayout.setUrl(avatar, login); + } + NumberFormat numberFormat = NumberFormat.getNumberInstance(); + stars.setText(numberFormat.format(repo.getStargazersCount())); + forks.setText(numberFormat.format(repo.getForks())); + date.setText(ParseDateFormat.getTimeAgo(repo.getUpdatedAt())); + } + + @Override public void bind(@NonNull RepoModel repo) {} +} diff --git a/app/src/main/java/com/fastaccess/ui/adapter/viewholder/SearchCodeViewHolder.java b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/SearchCodeViewHolder.java new file mode 100644 index 00000000..06ea8dd3 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/SearchCodeViewHolder.java @@ -0,0 +1,39 @@ +package com.fastaccess.ui.adapter.viewholder; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; +import android.view.ViewGroup; + +import com.fastaccess.R; +import com.fastaccess.data.dao.SearchCodeModel; +import com.fastaccess.ui.widgets.AvatarLayout; +import com.fastaccess.ui.widgets.FontTextView; +import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import butterknife.BindView; + +/** + * Created by Kosh on 11 Nov 2016, 2:08 PM + */ + +public class SearchCodeViewHolder extends BaseViewHolder { + + @BindView(R.id.title) FontTextView title; + @BindView(R.id.avatarLayout) AvatarLayout avatarLayout; + @BindView(R.id.details) FontTextView details; + + private SearchCodeViewHolder(@NonNull View itemView, @Nullable BaseRecyclerAdapter adapter) { + super(itemView, adapter); + } + + public static SearchCodeViewHolder newInstance(ViewGroup viewGroup, BaseRecyclerAdapter adapter) { + return new SearchCodeViewHolder(getView(viewGroup, R.layout.issue_row_item), adapter); + } + + @Override public void bind(@NonNull SearchCodeModel codeMode) { + title.setText(codeMode.getRepository() != null ? codeMode.getRepository().getFullName() : "N/A"); + details.setText(codeMode.getName()); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/adapter/viewholder/SimpleViewHolder.java b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/SimpleViewHolder.java new file mode 100644 index 00000000..d91b25e2 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/SimpleViewHolder.java @@ -0,0 +1,29 @@ +package com.fastaccess.ui.adapter.viewholder; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.ui.widgets.FontTextView; +import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import butterknife.BindView; + +/** + * Created by Kosh on 31 Dec 2016, 3:12 PM + */ + +public class SimpleViewHolder extends BaseViewHolder { + + @BindView(R.id.title) FontTextView title; + + public SimpleViewHolder(@NonNull View itemView, @Nullable BaseRecyclerAdapter adapter) { + super(itemView, adapter); + } + + @Override public void bind(@NonNull O o) { + title.setText(o.toString()); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/adapter/viewholder/UsersViewHolder.java b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/UsersViewHolder.java new file mode 100644 index 00000000..db128d7c --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/UsersViewHolder.java @@ -0,0 +1,49 @@ +package com.fastaccess.ui.adapter.viewholder; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; +import android.view.ViewGroup; + +import com.fastaccess.R; +import com.fastaccess.data.dao.UserModel; +import com.fastaccess.ui.widgets.AvatarLayout; +import com.fastaccess.ui.widgets.FontTextView; +import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import butterknife.BindView; + +/** + * Created by Kosh on 11 Nov 2016, 2:08 PM + */ + +public class UsersViewHolder extends BaseViewHolder { + + @BindView(R.id.avatarLayout) AvatarLayout avatar; + @BindView(R.id.title) FontTextView title; + @BindView(R.id.date) FontTextView date; + + public UsersViewHolder(@NonNull View itemView, @Nullable BaseRecyclerAdapter adapter) { + super(itemView, adapter); + } + + public static View getView(@NonNull ViewGroup viewGroup) { + return getView(viewGroup, R.layout.feeds_row_item); + } + + @Override public void onClick(View v) { + avatar.findViewById(R.id.avatar).callOnClick(); + } + + @Override public void bind(@NonNull UserModel user) {} + + public void bind(@NonNull UserModel user, boolean isContributor) { + avatar.setUrl(user.getAvatarUrl(), user.getLogin()); + title.setText(user.getLogin()); + date.setVisibility(!isContributor ? View.GONE : View.VISIBLE); + if (isContributor) { + date.setText(String.format("%s (%s)", date.getResources().getString(R.string.commits), user.getContributions())); + } + } +} diff --git a/app/src/main/java/com/fastaccess/ui/base/BaseActivity.java b/app/src/main/java/com/fastaccess/ui/base/BaseActivity.java new file mode 100644 index 00000000..6b51a796 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/base/BaseActivity.java @@ -0,0 +1,195 @@ +package com.fastaccess.ui.base; + +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.DrawableRes; +import android.support.annotation.LayoutRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.v4.app.Fragment; +import android.support.v7.widget.Toolbar; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.Toast; + +import com.fastaccess.BuildConfig; +import com.fastaccess.R; +import com.fastaccess.data.dao.LoginModel; +import com.fastaccess.helper.AppHelper; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.helper.PrefGetter; +import com.fastaccess.helper.ViewHelper; +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; +import com.fastaccess.ui.modules.login.LoginView; +import com.fastaccess.ui.widgets.dialog.MessageDialogView; +import com.fastaccess.ui.widgets.dialog.ProgressDialogFragment; + +import net.grandcentrix.thirtyinch.TiActivity; + +import butterknife.BindView; +import butterknife.ButterKnife; +import icepick.Icepick; +import icepick.State; + +/** + * Created by Kosh on 24 May 2016, 8:48 PM + */ + +public abstract class BaseActivity> extends TiActivity implements + BaseMvp.FAView { + + private Toast toast; + + @State boolean isProgressShowing; + @Nullable @BindView(R.id.toolbar) Toolbar toolbar; + @Nullable @BindView(R.id.toolbarShadow) View shadowView; + + @LayoutRes protected abstract int layout(); + + protected abstract boolean isTransparent(); + + protected abstract boolean canBack(); + + protected abstract boolean isSecured(); + + @Override protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + Icepick.saveInstanceState(this, outState); + } + + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (layout() != 0) { + setContentView(layout()); + ButterKnife.bind(this); + } + + Icepick.setDebug(BuildConfig.DEBUG); + if (savedInstanceState != null && !savedInstanceState.isEmpty()) { + Icepick.restoreInstanceState(this, savedInstanceState); + } + setupToolbarAndStatusBar(toolbar); + if (!isSecured()) { + if (!isLoggedIn()) { + startActivity(new Intent(this, LoginView.class)); + finish(); + } + } + } + + @Override public boolean onCreateOptionsMenu(Menu menu) { + return super.onCreateOptionsMenu(menu); + } + + @Override public boolean onOptionsItemSelected(MenuItem item) { + if (canBack()) { + if (item.getItemId() == android.R.id.home) { + onBackPressed(); + } + } + return super.onOptionsItemSelected(item); + } + + @Override protected void onDestroy() { + super.onDestroy(); + } + + @Override public void onDialogDismissed() { + + }//pass + + @Override public void onMessageDialogActionClicked(boolean isOk, @Nullable Bundle bundle) { + + }//pass + + @Override public void showMessage(@StringRes int titleRes, @StringRes int msgRes) { + showMessage(getString(titleRes), getString(msgRes)); + } + + @Override public void showMessage(@NonNull String titleRes, @NonNull String msgRes) { + hideProgress(); + if (!isFinishing()) { + try { + getSupportFragmentManager().executePendingTransactions(); + Fragment fragment = AppHelper.getFragmentByTag(getSupportFragmentManager(), "BaseActivity"); + if (fragment == null) { + MessageDialogView.newInstance(titleRes, msgRes).show(getSupportFragmentManager(), "BaseActivity"); + } else { + MessageDialogView messageDialogView = (MessageDialogView) fragment; + messageDialogView.setArguments(MessageDialogView.getBundle(titleRes, msgRes, false, null)); + messageDialogView.initMessage(); + } + return; + } catch (Exception ignored) {} + } + if (toast != null) toast.cancel(); + toast = Toast.makeText(this, msgRes, Toast.LENGTH_LONG); + toast.show(); + } + + @Override public void showErrorMessage(@NonNull String msgRes) { + showMessage(getString(R.string.error), msgRes); + } + + @Override public boolean isLoggedIn() { + return !InputHelper.isEmpty(PrefGetter.getToken()) && LoginModel.getUser() != null; + } + + @Override public void showProgress(@StringRes int resId) { + String msg = getString(R.string.in_progress); + if (resId != 0) { + msg = getString(resId); + } + if (!isProgressShowing) { + ProgressDialogFragment fragment = (ProgressDialogFragment) AppHelper.getFragmentByTag(getSupportFragmentManager(), + ProgressDialogFragment.TAG); + if (fragment == null) { + isProgressShowing = true; + fragment = ProgressDialogFragment.newInstance(msg, false); + fragment.show(getSupportFragmentManager(), ProgressDialogFragment.TAG); + } + } + } + + @Override public void hideProgress() { + ProgressDialogFragment fragment = (ProgressDialogFragment) AppHelper.getFragmentByTag(getSupportFragmentManager(), + ProgressDialogFragment.TAG); + if (fragment != null) { + isProgressShowing = false; + fragment.dismiss(); + } + } + + private void setupToolbarAndStatusBar(@Nullable Toolbar toolbar) { + changeStatusBarColor(isTransparent()); + if (toolbar != null) { + setSupportActionBar(toolbar); + if (canBack()) { + if (getSupportActionBar() != null) { + getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_back); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + } + } + } + } + + protected void setToolbarIcon(@DrawableRes int res) { + if (getSupportActionBar() != null) { + getSupportActionBar().setHomeAsUpIndicator(res); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + } + } + + protected void hideShowShadow(boolean show) { + if (shadowView != null) shadowView.setVisibility(show ? View.VISIBLE : View.GONE); + } + + protected void changeStatusBarColor(boolean isTransparent) { + if (!isTransparent) { + getWindow().setStatusBarColor(ViewHelper.getPrimaryDarkColor(this)); + } + } +} diff --git a/app/src/main/java/com/fastaccess/ui/base/BaseBottomSheetDialog.java b/app/src/main/java/com/fastaccess/ui/base/BaseBottomSheetDialog.java new file mode 100644 index 00000000..9f387a6b --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/base/BaseBottomSheetDialog.java @@ -0,0 +1,119 @@ +package com.fastaccess.ui.base; + +import android.app.Dialog; +import android.os.Bundle; +import android.support.annotation.LayoutRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.BottomSheetBehavior; +import android.support.design.widget.BottomSheetDialogFragment; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup; + +import com.fastaccess.helper.ViewHelper; + +import butterknife.ButterKnife; +import butterknife.Unbinder; +import icepick.Icepick; + +/** + * Created by Kosh on 16 Sep 2016, 2:11 PM + */ + +@SuppressWarnings("RestrictedApi") public abstract class BaseBottomSheetDialog extends BottomSheetDialogFragment { + + protected BottomSheetBehavior bottomSheetBehavior; + private BottomSheetBehavior.BottomSheetCallback bottomSheetCallback = new BottomSheetBehavior.BottomSheetCallback() { + @Override public void onStateChanged(@NonNull View bottomSheet, int newState) { + if (newState == BottomSheetBehavior.STATE_HIDDEN) { + isAlreadyHidden = true; + onHidden(); + } + } + + @Override public void onSlide(@NonNull View bottomSheet, float slideOffset) { + if (slideOffset == -1.0) { + isAlreadyHidden = true; + onDismissedByScrolling(); + } + } + }; + protected boolean isAlreadyHidden; + @Nullable private Unbinder unbinder; + + @LayoutRes protected abstract int layoutRes(); + + @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + } + + @Override public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + Icepick.saveInstanceState(this, outState); + } + + @Override public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (savedInstanceState != null && !savedInstanceState.isEmpty()) { + Icepick.restoreInstanceState(this, savedInstanceState); + } + } + + @Override public void setupDialog(Dialog dialog, int style) { + super.setupDialog(dialog, style); + View contentView = View.inflate(getContext(), layoutRes(), null); + dialog.setContentView(contentView); + View parent = ((View) contentView.getParent()); + bottomSheetBehavior = BottomSheetBehavior.from(parent); + if (bottomSheetBehavior != null) { + bottomSheetBehavior.setBottomSheetCallback(bottomSheetCallback); + bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED); + } + unbinder = ButterKnife.bind(this, contentView); + onViewCreated(contentView, dialog.onSaveInstanceState()); + } + + @Override public void onDestroyView() { + super.onDestroyView(); + if (unbinder != null) unbinder.unbind(); + } + + @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { + final Dialog dialog = super.onCreateDialog(savedInstanceState); + dialog.setOnShowListener(dialogInterface -> { + if (ViewHelper.isTablet(getContext())) { + if (dialog.getWindow() != null) { + dialog.getWindow().setLayout( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.MATCH_PARENT); + } + } + onDialogIsShowing(); + }); + dialog.setOnKeyListener((dialog1, keyCode, event) -> { + if (keyCode == KeyEvent.KEYCODE_BACK) { + isAlreadyHidden = true; + onDismissedByScrolling(); + } + return false; + }); + return dialog; + } + + @Override public void onDetach() { + if (!isAlreadyHidden) { + onDismissedByScrolling(); + } + super.onDetach(); + } + + protected void onHidden() { + dismiss(); + }//helper method to notify dialogs + + protected void onDismissedByScrolling() {}//helper method to notify dialogs + + protected void onDialogIsShowing() {}//helper method to notify dialogs + +} \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/ui/base/BaseFragment.java b/app/src/main/java/com/fastaccess/ui/base/BaseFragment.java new file mode 100644 index 00000000..fecb497f --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/base/BaseFragment.java @@ -0,0 +1,115 @@ +package com.fastaccess.ui.base; + +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.LayoutRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; + +import net.grandcentrix.thirtyinch.TiFragment; + +import butterknife.ButterKnife; +import butterknife.Unbinder; +import icepick.Icepick; + +/** + * Created by Kosh on 27 May 2016, 7:54 PM + */ + +public abstract class BaseFragment> extends TiFragment implements BaseMvp.FAView { + + protected BaseMvp.FAView callback; + + @Nullable private Unbinder unbinder; + + @LayoutRes protected abstract int fragmentLayout(); + + protected abstract void onFragmentCreated(@NonNull View view, @Nullable Bundle savedInstanceState); + + @Override public void onAttach(Context context) { + super.onAttach(context); + if (context instanceof BaseMvp.FAView) { + callback = (BaseMvp.FAView) context; + } + } + + @Override public void onDetach() { + super.onDetach(); + callback = null; + } + + @Override public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + Icepick.saveInstanceState(this, outState); + } + + @Override public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (savedInstanceState != null && !savedInstanceState.isEmpty()) { + Icepick.restoreInstanceState(this, savedInstanceState); + } + setHasOptionsMenu(true); + } + + @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + if (fragmentLayout() != 0) { + View view = inflater.inflate(fragmentLayout(), container, false); + unbinder = ButterKnife.bind(this, view); + return view; + } + return super.onCreateView(inflater, container, savedInstanceState); + } + + @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + onFragmentCreated(view, savedInstanceState); + } + + @Override public void onDestroyView() { + super.onDestroyView(); + if (unbinder != null) unbinder.unbind(); + } + + @Override public void showProgress(@StringRes int resId) { + callback.showProgress(resId); + } + + @Override public void hideProgress() { + callback.hideProgress(); + } + + @Override public void showMessage(@StringRes int titleRes, @StringRes int msgRes) { + callback.showMessage(titleRes, msgRes); + } + + @Override public void showMessage(@NonNull String titleRes, @NonNull String msgRes) { + callback.showMessage(titleRes, msgRes); + } + + @Override public void showErrorMessage(@NonNull String msgRes) { + callback.showErrorMessage(msgRes); + } + + @Override public boolean isLoggedIn() { + return callback.isLoggedIn(); + } + + @Override public void onMessageDialogActionClicked(boolean isOk, @Nullable Bundle bundle) { + + } + + @Override public void onDialogDismissed() { + + } + + protected boolean isSafe() { + return getView() != null && getActivity() != null && !getActivity().isFinishing(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/base/mvp/BaseMvp.java b/app/src/main/java/com/fastaccess/ui/base/mvp/BaseMvp.java new file mode 100644 index 00000000..0f22d8ea --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/base/mvp/BaseMvp.java @@ -0,0 +1,57 @@ +package com.fastaccess.ui.base.mvp; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; + +import com.fastaccess.ui.widgets.dialog.MessageDialogView; + +import net.grandcentrix.thirtyinch.TiView; + +import rx.Observable; +import rx.Subscription; +import rx.functions.Action1; + +/** + * Created by Kosh on 25 May 2016, 9:09 PM + */ + +public interface BaseMvp { + interface FAView extends TiView, MessageDialogView.MessageDialogViewActionCallback { + void showProgress(@StringRes int resId); + + void hideProgress(); + + void showMessage(@StringRes int titleRes, @StringRes int msgRes); + + void showMessage(@NonNull String titleRes, @NonNull String msgRes); + + void showErrorMessage(@NonNull String msgRes); + + boolean isLoggedIn(); + } + + interface FAPresenter { + void manageSubscription(@Nullable Subscription subscription); + + boolean isApiCalled(); + + void onSubscribed(); + + T onError(@NonNull Throwable throwable, @NonNull Observable observable); + + void makeRestCall(@NonNull Observable observable, @NonNull Action1 onNext); + } + + interface PaginationListener

{ + int getCurrentPage(); + + int getPreviousTotal(); + + void setCurrentPage(int page); + + void setPreviousTotal(int previousTotal); + + void onCallApi(int page, @Nullable P parameter); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/base/mvp/presenter/BasePresenter.java b/app/src/main/java/com/fastaccess/ui/base/mvp/presenter/BasePresenter.java new file mode 100644 index 00000000..4e0e0bf0 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/base/mvp/presenter/BasePresenter.java @@ -0,0 +1,62 @@ +package com.fastaccess.ui.base.mvp.presenter; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.fastaccess.R; +import com.fastaccess.data.dao.GitHubErrorResponse; +import com.fastaccess.helper.RxHelper; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.ui.base.mvp.BaseMvp; + +import net.grandcentrix.thirtyinch.TiPresenter; +import net.grandcentrix.thirtyinch.rx.RxTiPresenterSubscriptionHandler; + +import rx.Observable; +import rx.Subscription; +import rx.functions.Action1; + +/** + * Created by Kosh on 25 May 2016, 9:12 PM + */ + +public class BasePresenter extends TiPresenter implements BaseMvp.FAPresenter { + private boolean apiCalled; + private RxTiPresenterSubscriptionHandler subscriptionHandler = new RxTiPresenterSubscriptionHandler(this); + + @Override public void manageSubscription(@Nullable Subscription subscription) { + if (subscription != null) { + subscriptionHandler.manageSubscription(subscription); + } + } + + @Override public boolean isApiCalled() { + return apiCalled; + } + + @Override public void onSubscribed() { + sendToView(v -> v.showProgress(R.string.in_progress)); + } + + @Override public T onError(@NonNull Throwable throwable, @NonNull Observable observable) { + throwable.printStackTrace(); + GitHubErrorResponse errorResponse = RestProvider.getErrorResponse(throwable); + if (errorResponse != null && errorResponse.getMessage() != null) { + sendToView(v -> v.showErrorMessage(errorResponse.getMessage())); + } else { + sendToView(v -> v.showErrorMessage(throwable.getMessage())); + } + return null; + } + + @Override public void makeRestCall(@NonNull Observable observable, @NonNull Action1 onNext) { + manageSubscription( + RxHelper.getObserver(observable) + .doOnSubscribe(this::onSubscribed) + .doOnNext(onNext) + .doOnCompleted(() -> apiCalled = true) + .onErrorReturn(throwable -> onError(throwable, observable)) + .subscribe() + ); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/code/CodeViewerView.java b/app/src/main/java/com/fastaccess/ui/modules/code/CodeViewerView.java new file mode 100644 index 00000000..803e7995 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/code/CodeViewerView.java @@ -0,0 +1,97 @@ +package com.fastaccess.ui.modules.code; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.Menu; +import android.view.MenuItem; + +import com.annimon.stream.Objects; +import com.fastaccess.R; +import com.fastaccess.helper.ActivityHelper; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.ui.base.BaseActivity; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; +import com.fastaccess.ui.modules.repos.code.prettifier.ViewerView; + +import net.grandcentrix.thirtyinch.TiPresenter; + +import icepick.State; + +/** + * Created by Kosh on 27 Nov 2016, 3:43 PM + */ + +public class CodeViewerView extends BaseActivity { + + @State String url; + + public static void startActivity(@NonNull Context context, @NonNull String url) { + context.startActivity(createIntent(context, url)); + } + + public static Intent createIntent(@NonNull Context context, @NonNull String url) { + Intent intent = new Intent(context, CodeViewerView.class); + intent.putExtras(Bundler.start() + .put(BundleConstant.EXTRA, url) + .end()); + return intent; + } + + @Override protected int layout() { + return R.layout.activity_fragment_layout; + } + + @Override protected boolean isTransparent() { + return false; + } + + @Override protected boolean canBack() { + return true; + } + + @Override protected boolean isSecured() { + return false; + } + + @NonNull @Override public TiPresenter providePresenter() { + return new BasePresenter(); + } + + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (savedInstanceState == null) { + Intent intent = Objects.requireNonNull(getIntent(), "Intent is null"); + Bundle bundle = Objects.requireNonNull(intent.getExtras()); + //noinspection ConstantConditions + url = Objects.requireNonNull(bundle.getString(BundleConstant.EXTRA), "Url is null"); + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.container, ViewerView.newInstance(url), ViewerView.TAG) + .commit(); + } + } + + @Override public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.download_browser_menu, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.download) { + if (!InputHelper.isEmpty(url)) { + RestProvider.downloadFile(this, url); + } + return true; + } else if (item.getItemId() == R.id.browser) { + ActivityHelper.forceOpenInBrowser(this, url); + return true; + } + return super.onOptionsItemSelected(item); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/editor/EditorMvp.java b/app/src/main/java/com/fastaccess/ui/modules/editor/EditorMvp.java new file mode 100644 index 00000000..229bec77 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/editor/EditorMvp.java @@ -0,0 +1,43 @@ +package com.fastaccess.ui.modules.editor; + +import android.support.annotation.IdRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.widget.EditText; + +import com.fastaccess.data.dao.CommentsModel; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.ui.base.mvp.BaseMvp; + +/** + * Created by Kosh on 27 Nov 2016, 1:31 AM + */ + +interface EditorMvp { + + interface View extends BaseMvp.FAView { + void onSendResultAndFinish(@NonNull CommentsModel commentModel, boolean isNew); + + void onSendMarkDownResult(); + } + + interface Presenter extends BaseMvp.FAPresenter { + + void onActionClicked(@NonNull EditText editText, @IdRes int id); + + void onEditGistComment(long id, @Nullable CharSequence savedText, @NonNull String gistId); + + void onSubmitGistComment(@Nullable CharSequence savedText, @NonNull String gistId); + + void onSubmitIssueComment(CharSequence savedText, @NonNull String itemId, @NonNull String login, int issueNumber); + + void onEditIssueComment(CharSequence savedText, @NonNull String itemId, long id, @NonNull String login, int issueNumber); + + void onSubmitCommitComment(CharSequence savedText, @NonNull String itemId, @NonNull String login, @NonNull String sha); + + void onEditCommitComment(CharSequence savedText, @NonNull String itemId, @NonNull String login, long id); + + void onHandleSubmission(@Nullable CharSequence savedText, @Nullable @BundleConstant.ExtraTYpe String extraType, + @Nullable String itemId, long commentId, @Nullable String login, int issueNumber, @Nullable String sha); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/editor/EditorPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/editor/EditorPresenter.java new file mode 100644 index 00000000..e02d7e81 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/editor/EditorPresenter.java @@ -0,0 +1,179 @@ +package com.fastaccess.ui.modules.editor; + +import android.support.annotation.IdRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.widget.EditText; + +import com.fastaccess.R; +import com.fastaccess.data.dao.CommentRequestModel; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.provider.markdown.MarkDownProvider; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; + +import static com.fastaccess.helper.BundleConstant.ExtraTYpe.EDIT_COMMIT_COMMENT_EXTRA; +import static com.fastaccess.helper.BundleConstant.ExtraTYpe.EDIT_GIST_COMMENT_EXTRA; +import static com.fastaccess.helper.BundleConstant.ExtraTYpe.EDIT_ISSUE_COMMENT_EXTRA; +import static com.fastaccess.helper.BundleConstant.ExtraTYpe.FOR_RESULT_EXTRA; +import static com.fastaccess.helper.BundleConstant.ExtraTYpe.NEW_COMMIT_COMMENT_EXTRA; +import static com.fastaccess.helper.BundleConstant.ExtraTYpe.NEW_GIST_COMMENT_EXTRA; +import static com.fastaccess.helper.BundleConstant.ExtraTYpe.NEW_ISSUE_COMMENT_EXTRA; + +/** + * Created by Kosh on 27 Nov 2016, 1:31 AM + */ + +class EditorPresenter extends BasePresenter implements EditorMvp.Presenter { + + @Override public void onActionClicked(@NonNull EditText editText, @IdRes int id) { + if (editText.getSelectionEnd() == -1 || editText.getSelectionStart() == -1) { + return; + } + switch (id) { + case R.id.headerOne: + MarkDownProvider.addHeader(editText, 1); + break; + case R.id.headerTwo: + MarkDownProvider.addHeader(editText, 2); + break; + case R.id.headerThree: + MarkDownProvider.addHeader(editText, 3); + break; + case R.id.bold: + MarkDownProvider.addBold(editText); + break; + case R.id.italic: + MarkDownProvider.addItalic(editText); + break; + case R.id.strikethrough: + MarkDownProvider.addStrikeThrough(editText); + break; + case R.id.numbered: + MarkDownProvider.addList(editText, "1."); + break; + case R.id.bullet: + MarkDownProvider.addList(editText, "-"); + break; + case R.id.header: + MarkDownProvider.addDivider(editText); + break; + case R.id.code: + MarkDownProvider.addCode(editText); + break; + case R.id.quote: + MarkDownProvider.addQuote(editText); + break; + case R.id.link: + MarkDownProvider.addLink(editText); + break; + case R.id.image: + MarkDownProvider.addPhoto(editText); + break; + } + } + + @Override public void onEditGistComment(long id, @Nullable CharSequence savedText, @NonNull String gistId) { + if (!InputHelper.isEmpty(savedText)) { + CommentRequestModel requestModel = new CommentRequestModel(); + requestModel.setBody(savedText.toString()); + makeRestCall(RestProvider.getGistService().editGistComment(gistId, id, requestModel), + commentsModel -> sendToView(view -> view.onSendResultAndFinish(commentsModel, false))); + } + } + + @Override public void onSubmitGistComment(@Nullable CharSequence savedText, @NonNull String gistId) { + if (!InputHelper.isEmpty(savedText)) { + CommentRequestModel requestModel = new CommentRequestModel(); + requestModel.setBody(savedText.toString()); + makeRestCall(RestProvider.getGistService().createGistComment(gistId, requestModel), + commentsModel -> sendToView(view -> view.onSendResultAndFinish(commentsModel, true))); + } + } + + @Override public void onHandleSubmission(@Nullable CharSequence savedText, @Nullable @BundleConstant.ExtraTYpe String extraType, + @Nullable String itemId, long id, @Nullable String login, int issueNumber, + @Nullable String sha) { + if (extraType == null) { + throw new NullPointerException("extraType is null"); + } + switch (extraType) { + case EDIT_GIST_COMMENT_EXTRA: + if (itemId == null) { + throw new NullPointerException("itemId is null"); + } + onEditGistComment(id, savedText, itemId); + break; + case NEW_GIST_COMMENT_EXTRA: + if (itemId == null) { + throw new NullPointerException("itemId is null"); + } + onSubmitGistComment(savedText, itemId); + break; + case FOR_RESULT_EXTRA: + sendToView(EditorMvp.View::onSendMarkDownResult); + break; + case EDIT_ISSUE_COMMENT_EXTRA: + if (itemId == null || login == null) { + throw new NullPointerException("itemId or login is null"); + } + onEditIssueComment(savedText, itemId, id, login, issueNumber); + break; + case NEW_ISSUE_COMMENT_EXTRA: + if (itemId == null || login == null) { + throw new NullPointerException("itemId or login is null"); + } + onSubmitIssueComment(savedText, itemId, login, issueNumber); + break; + case NEW_COMMIT_COMMENT_EXTRA: + if (itemId == null || login == null || sha == null) { + throw new NullPointerException("itemId or login is null"); + } + onSubmitCommitComment(savedText, itemId, login, sha); + break; + case EDIT_COMMIT_COMMENT_EXTRA: + if (itemId == null || login == null) { + throw new NullPointerException("itemId or login is null"); + } + onEditCommitComment(savedText, itemId, login, id); + break; + } + } + + @Override public void onSubmitIssueComment(CharSequence savedText, @NonNull String itemId, @NonNull String login, int issueNumber) { + if (!InputHelper.isEmpty(savedText)) { + CommentRequestModel requestModel = new CommentRequestModel(); + requestModel.setBody(savedText.toString()); + makeRestCall(RestProvider.getIssueService().createIssueComment(login, itemId, issueNumber, requestModel), + commentsModel -> sendToView(view -> view.onSendResultAndFinish(commentsModel, true))); + } + } + + @Override public void onEditIssueComment(CharSequence savedText, @NonNull String itemId, long id, @NonNull String login, int issueNumber) { + if (!InputHelper.isEmpty(savedText)) { + CommentRequestModel requestModel = new CommentRequestModel(); + requestModel.setBody(savedText.toString()); + makeRestCall(RestProvider.getIssueService().editIssueComment(login, itemId, id, requestModel), + commentsModel -> sendToView(view -> view.onSendResultAndFinish(commentsModel, false))); + } + } + + @Override public void onSubmitCommitComment(CharSequence savedText, @NonNull String itemId, @NonNull String login, @NonNull String sha) { + if (!InputHelper.isEmpty(savedText)) { + CommentRequestModel requestModel = new CommentRequestModel(); + requestModel.setBody(savedText.toString()); + makeRestCall(RestProvider.getRepoService().postCommitComment(login, itemId, sha, requestModel), + commentsModel -> sendToView(view -> view.onSendResultAndFinish(commentsModel, true))); + } + } + + @Override public void onEditCommitComment(CharSequence savedText, @NonNull String itemId, @NonNull String login, long id) { + if (!InputHelper.isEmpty(savedText)) { + CommentRequestModel requestModel = new CommentRequestModel(); + requestModel.setBody(savedText.toString()); + makeRestCall(RestProvider.getRepoService().editCommitComment(login, itemId, id, requestModel), + commentsModel -> sendToView(view -> view.onSendResultAndFinish(commentsModel, true))); + } + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/editor/EditorView.java b/app/src/main/java/com/fastaccess/ui/modules/editor/EditorView.java new file mode 100644 index 00000000..ee8e775b --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/editor/EditorView.java @@ -0,0 +1,141 @@ +package com.fastaccess.ui.modules.editor; + +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.data.dao.CommentsModel; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.helper.ViewHelper; +import com.fastaccess.ui.base.BaseActivity; +import com.fastaccess.ui.widgets.FontButton; +import com.fastaccess.ui.widgets.FontEditText; +import com.fastaccess.ui.widgets.ForegroundImageView; + +import butterknife.BindView; +import butterknife.OnClick; +import butterknife.OnTextChanged; +import icepick.State; + +/** + * Created by Kosh on 27 Nov 2016, 1:32 AM + */ + +public class EditorView extends BaseActivity implements EditorMvp.View { + + private CharSequence savedText; + @BindView(R.id.cancel) FontButton cancel; + @BindView(R.id.ok) FontButton ok; + @BindView(R.id.view) ForegroundImageView viewCode; + @BindView(R.id.editText) FontEditText editText; + + @State @BundleConstant.ExtraTYpe String extraType; + @State String itemId; + @State String login; + @State int issueNumber; + @State long commentId = 0; + @State String sha; + + @Override protected int layout() { + return R.layout.editor_layout; + } + + @Override protected boolean isTransparent() { + return false; + } + + @Override protected boolean canBack() { + return true; + } + + @Override protected boolean isSecured() { + return false; + } + + @NonNull @Override public EditorPresenter providePresenter() { + return new EditorPresenter(); + } + + @OnTextChanged(value = R.id.editText, callback = OnTextChanged.Callback.TEXT_CHANGED) void onEdited(CharSequence charSequence) { + if (viewCode.getTag() != null) { + return; + } + savedText = charSequence; + } + + @OnClick(R.id.view) void onViewMarkDown(View v) { + if (InputHelper.isEmpty(editText)) return; + if (v.getTag() == null) { + v.setTag("whatever"); + ViewHelper.hideKeyboard(editText); + } else { + v.setTag(null); + editText.setText(savedText); + } + } + + @OnClick({R.id.headerOne, R.id.headerTwo, R.id.headerThree, R.id.bold, R.id.italic, + R.id.strikethrough, R.id.bullet, R.id.header, R.id.code, R.id.numbered, + R.id.quote, R.id.link, R.id.image}) void onActions(View v) { + if (viewCode.getTag() != null) return; + getPresenter().onActionClicked(editText, v.getId()); + } + + @OnClick(value = {R.id.ok, R.id.cancel}) void onClick(View view) { + if (view.getId() == R.id.ok) { + getPresenter().onHandleSubmission(savedText, extraType, itemId, commentId, login, issueNumber, sha); + } else { + finish(); + } + } + + @OnClick(R.id.back) void onBack() { + finish(); + } + + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (savedInstanceState == null) { + Intent intent = getIntent(); + if (intent != null && intent.getExtras() != null) { + Bundle bundle = intent.getExtras(); + //noinspection WrongConstant + extraType = bundle.getString(BundleConstant.EXTRA_TYPE); + itemId = bundle.getString(BundleConstant.ID); + login = bundle.getString(BundleConstant.EXTRA_TWO); + if (extraType.equalsIgnoreCase(BundleConstant.ExtraTYpe.EDIT_COMMIT_COMMENT_EXTRA) || + extraType.equalsIgnoreCase(BundleConstant.ExtraTYpe.NEW_COMMIT_COMMENT_EXTRA)) { + sha = bundle.getString(BundleConstant.EXTRA_THREE); + } else { + issueNumber = bundle.getInt(BundleConstant.EXTRA_THREE); + } + commentId = bundle.getLong(BundleConstant.EXTRA_FOUR); + + editText.setText(bundle.getString(BundleConstant.EXTRA)); + } + } + } + + @Override public void onSendResultAndFinish(@NonNull CommentsModel commentModel, boolean isNew) { + hideProgress(); + Intent intent = new Intent(); + intent.putExtras(Bundler.start() + .put(BundleConstant.ITEM, commentModel) + .put(BundleConstant.EXTRA, isNew) + .end()); + setResult(RESULT_OK, intent); + finish(); + } + + @Override public void onSendMarkDownResult() { + Intent intent = new Intent(); + intent.putExtras(Bundler.start().put(BundleConstant.EXTRA, savedText).end()); + setResult(RESULT_OK, intent); + finish(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/feeds/FeedsMvp.java b/app/src/main/java/com/fastaccess/ui/modules/feeds/FeedsMvp.java new file mode 100644 index 00000000..bde47d59 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/feeds/FeedsMvp.java @@ -0,0 +1,40 @@ +package com.fastaccess.ui.modules.feeds; + +import android.support.annotation.NonNull; +import android.support.v4.widget.SwipeRefreshLayout; + +import com.fastaccess.data.dao.EventsModel; +import com.fastaccess.data.dao.SimpleUrlsModel; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.widgets.dialog.ListDialogView; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.ArrayList; + +/** + * Created by Kosh on 11 Nov 2016, 12:35 PM + */ + +interface FeedsMvp { + interface View extends BaseMvp.FAView, SwipeRefreshLayout.OnRefreshListener, + android.view.View.OnClickListener, ListDialogView.onSimpleItemSelection { + + void onNotifyAdapter(); + + void onOpenRepoChooser(@NonNull ArrayList models); + + @NonNull OnLoadMore getLoadMore(); + } + + interface Presenter extends BaseMvp.FAPresenter, + BaseViewHolder.OnItemClickListener, + BaseMvp.PaginationListener { + + void onCallApi(int page); + + @NonNull ArrayList getEvents(); + + void onWorkOffline(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/feeds/FeedsPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/feeds/FeedsPresenter.java new file mode 100644 index 00000000..ef0a9246 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/feeds/FeedsPresenter.java @@ -0,0 +1,121 @@ +package com.fastaccess.ui.modules.feeds; + +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; + +import com.annimon.stream.Collectors; +import com.annimon.stream.Stream; +import com.fastaccess.data.dao.EventsModel; +import com.fastaccess.data.dao.LoginModel; +import com.fastaccess.data.dao.NameParser; +import com.fastaccess.data.dao.SimpleUrlsModel; +import com.fastaccess.data.dao.types.EventsType; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.provider.scheme.SchemeParser; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; +import com.fastaccess.ui.modules.repos.RepoPagerView; + +import java.util.ArrayList; + +import rx.Observable; + +/** + * Created by Kosh on 11 Nov 2016, 12:36 PM + */ + +class FeedsPresenter extends BasePresenter implements FeedsMvp.Presenter { + private ArrayList eventsModels = new ArrayList<>(); + private int page; + private int previousTotal; + private int lastPage = Integer.MAX_VALUE; + + @Override public void onCallApi(int page) { + if (page == 1) { + lastPage = Integer.MAX_VALUE; + sendToView(view -> view.getLoadMore().reset()); + } + if (page > lastPage || lastPage == 0) { + sendToView(FeedsMvp.View::hideProgress); + return; + } + setCurrentPage(page); + makeRestCall(RestProvider.getUserService().getReceivedEvents(LoginModel.getUser().getLogin(), page), + response -> { + lastPage = response.getLast(); + if (getCurrentPage() == 1) { + manageSubscription(EventsModel.save(response.getItems()).subscribe()); + eventsModels.clear(); + + } + eventsModels.addAll(response.getItems()); + sendToView(FeedsMvp.View::onNotifyAdapter); + }); + } + + @Override public int getCurrentPage() { + return page; + } + + @Override public int getPreviousTotal() { + return previousTotal; + } + + @Override public void setCurrentPage(int page) { + this.page = page; + } + + @Override public void setPreviousTotal(int previousTotal) { + this.previousTotal = previousTotal; + } + + @Override public void onCallApi(int page, @Nullable Object parameter) { + onCallApi(page); + } + + @Override public void onSubscribed() { + sendToView(view -> view.showProgress(0)); + } + + @Override public T onError(@NonNull Throwable throwable, @NonNull Observable observable) { + onWorkOffline(); + return super.onError(throwable, observable); + } + + @NonNull @Override public ArrayList getEvents() { + return eventsModels; + } + + @Override public void onWorkOffline() { + if (eventsModels.isEmpty()) { + manageSubscription(EventsModel.getEvents().subscribe(modelList -> { + eventsModels.addAll(modelList); + sendToView(FeedsMvp.View::onNotifyAdapter); + })); + } else { + sendToView(FeedsMvp.View::hideProgress); + } + } + + @Override public void onItemClick(int position, View v, EventsModel item) { + if (item.getType() == EventsType.ForkEvent) { + NameParser parser = new NameParser(item.getPayload().getForkee().getHtmlUrl()); + RepoPagerView.startRepoPager(v.getContext(), parser); + } else { + SchemeParser.launchUri(v.getContext(), Uri.parse(item.getRepo().getName())); + } + } + + @Override public void onItemLongClick(int position, View v, EventsModel item) { + if (item.getType() == EventsType.ForkEvent) { + if (getView() != null) { + getView().onOpenRepoChooser(Stream.of(new SimpleUrlsModel(item.getRepo().getName(), item.getRepo().getUrl()), + new SimpleUrlsModel(item.getPayload().getForkee().getFullName(), item.getPayload().getForkee().getHtmlUrl())) + .collect(Collectors.toCollection(ArrayList::new))); + } + } else { + onItemClick(position, v, item); + } + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/feeds/FeedsView.java b/app/src/main/java/com/fastaccess/ui/modules/feeds/FeedsView.java new file mode 100644 index 00000000..75dc383c --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/feeds/FeedsView.java @@ -0,0 +1,116 @@ +package com.fastaccess.ui.modules.feeds; + +import android.net.Uri; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.v4.widget.SwipeRefreshLayout; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.data.dao.SimpleUrlsModel; +import com.fastaccess.helper.Logger; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.provider.scheme.SchemeParser; +import com.fastaccess.ui.adapter.FeedsAdapter; +import com.fastaccess.ui.base.BaseFragment; +import com.fastaccess.ui.widgets.StateLayout; +import com.fastaccess.ui.widgets.dialog.ListDialogView; +import com.fastaccess.ui.widgets.recyclerview.DynamicRecyclerView; + +import java.util.ArrayList; + +import butterknife.BindView; + +/** + * Created by Kosh on 11 Nov 2016, 12:36 PM + */ + +public class FeedsView extends BaseFragment implements FeedsMvp.View { + + public static final String TAG = FeedsView.class.getSimpleName(); + + @BindView(R.id.recycler) DynamicRecyclerView recycler; + @BindView(R.id.refresh) SwipeRefreshLayout refresh; + @BindView(R.id.stateLayout) StateLayout stateLayout; + private FeedsAdapter adapter; + private OnLoadMore onLoadMore; + + public static FeedsView newInstance() { + return new FeedsView(); + } + + @Override protected int fragmentLayout() { + return R.layout.small_grid_refresh_list; + } + + @Override protected void onFragmentCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + stateLayout.setOnReloadListener(this); + refresh.setOnRefreshListener(this); + recycler.setEmptyView(stateLayout, refresh); + adapter = new FeedsAdapter(getPresenter().getEvents()); + adapter.setListener(getPresenter()); + getLoadMore().setCurrent_page(getPresenter().getCurrentPage(), getPresenter().getPreviousTotal()); + recycler.setAdapter(adapter); + recycler.addOnScrollListener(getLoadMore()); + if (getPresenter().getEvents().isEmpty() && !getPresenter().isApiCalled()) { + onRefresh(); + } + } + + @Override public void onRefresh() { + getPresenter().onCallApi(1); + } + + @Override public void onNotifyAdapter() { + hideProgress(); + adapter.notifyDataSetChanged(); + } + + @Override public void showProgress(@StringRes int resId) { + refresh.setRefreshing(true); + stateLayout.showProgress(); + } + + @Override public void hideProgress() { + refresh.setRefreshing(false); + stateLayout.hideProgress(); + } + + @Override public void showErrorMessage(@NonNull String msgRes) { + stateLayout.showReload(adapter.getItemCount()); + super.showErrorMessage(msgRes); + } + + @Override public void onOpenRepoChooser(@NonNull ArrayList models) { + Logger.e(models); + ListDialogView dialogView = new ListDialogView<>(); + dialogView.initArguments(getString(R.string.repo_chooser), models); + dialogView.show(getChildFragmentManager(), "ListDialogView"); + } + + @NonNull @Override public FeedsPresenter providePresenter() { + return new FeedsPresenter(); + } + + @SuppressWarnings("unchecked") @NonNull @Override public OnLoadMore getLoadMore() { + if (onLoadMore == null) { + onLoadMore = new OnLoadMore(getPresenter()); + } + return onLoadMore; + } + + @Override public void onDestroyView() { + recycler.removeOnScrollListener(getLoadMore()); + super.onDestroyView(); + } + + @Override public void onClick(View view) { + onRefresh(); + } + + @Override public void onItemSelected(SimpleUrlsModel item) { + SchemeParser.launchUri(getContext(), Uri.parse(item.getItem())); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/gists/GistsMvp.java b/app/src/main/java/com/fastaccess/ui/modules/gists/GistsMvp.java new file mode 100644 index 00000000..51bf1bac --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/gists/GistsMvp.java @@ -0,0 +1,33 @@ +package com.fastaccess.ui.modules.gists; + +import android.support.annotation.NonNull; +import android.support.v4.widget.SwipeRefreshLayout; + +import com.fastaccess.data.dao.GistsModel; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.ArrayList; + +/** + * Created by Kosh on 11 Nov 2016, 12:35 PM + */ + +interface GistsMvp { + interface View extends BaseMvp.FAView, SwipeRefreshLayout.OnRefreshListener, android.view.View.OnClickListener { + + void onNotifyAdapter(); + + @NonNull OnLoadMore getLoadMore(); + } + + interface Presenter extends BaseMvp.FAPresenter, + BaseViewHolder.OnItemClickListener, + BaseMvp.PaginationListener { + + @NonNull ArrayList getGists(); + + void onWorkOffline(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/gists/GistsPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/gists/GistsPresenter.java new file mode 100644 index 00000000..81f55114 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/gists/GistsPresenter.java @@ -0,0 +1,91 @@ +package com.fastaccess.ui.modules.gists; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; + +import com.fastaccess.data.dao.GistsModel; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; +import com.fastaccess.ui.modules.gists.gist.GistView; + +import java.util.ArrayList; + +import rx.Observable; + +/** + * Created by Kosh on 11 Nov 2016, 12:36 PM + */ + +class GistsPresenter extends BasePresenter implements GistsMvp.Presenter { + private ArrayList gistsModels = new ArrayList<>(); + private int page; + private int previousTotal; + private int lastPage = Integer.MAX_VALUE; + + @Override public int getCurrentPage() { + return page; + } + + @Override public int getPreviousTotal() { + return previousTotal; + } + + @Override public void setCurrentPage(int page) { + this.page = page; + } + + @Override public void setPreviousTotal(int previousTotal) { + this.previousTotal = previousTotal; + } + + @Override public T onError(@NonNull Throwable throwable, @NonNull Observable observable) { + onWorkOffline(); + return super.onError(throwable, observable); + } + + @Override public void onCallApi(int page, @Nullable Object parameter) { + if (page == 1) { + lastPage = Integer.MAX_VALUE; + sendToView(view -> view.getLoadMore().reset()); + } + if (page > lastPage || lastPage == 0) { + sendToView(GistsMvp.View::hideProgress); + return; + } + setCurrentPage(page); + makeRestCall(RestProvider.getGistService().getPublicGists(RestProvider.PAGE_SIZE, page), + listResponse -> { + lastPage = listResponse.getLast(); + if (getCurrentPage() == 1) { + getGists().clear(); + manageSubscription(GistsModel.save(listResponse.getItems()).subscribe()); + } + getGists().addAll(listResponse.getItems()); + sendToView(GistsMvp.View::onNotifyAdapter); + }); + } + + @NonNull @Override public ArrayList getGists() { + return gistsModels; + } + + @Override public void onWorkOffline() { + if (gistsModels.isEmpty()) { + manageSubscription(GistsModel.getGists().subscribe(gistsModels1 -> { + gistsModels.addAll(gistsModels1); + sendToView(GistsMvp.View::onNotifyAdapter); + })); + } else { + sendToView(GistsMvp.View::hideProgress); + } + } + + @Override public void onItemClick(int position, View v, GistsModel item) { + v.getContext().startActivity(GistView.createIntent(v.getContext(), item.getGistId())); + } + + @Override public void onItemLongClick(int position, View v, GistsModel item) { + onItemClick(position, v, item); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/gists/GistsView.java b/app/src/main/java/com/fastaccess/ui/modules/gists/GistsView.java new file mode 100644 index 00000000..3eddc1d5 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/gists/GistsView.java @@ -0,0 +1,102 @@ +package com.fastaccess.ui.modules.gists; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.v4.widget.SwipeRefreshLayout; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.adapter.GistsAdapter; +import com.fastaccess.ui.base.BaseFragment; +import com.fastaccess.ui.widgets.StateLayout; +import com.fastaccess.ui.widgets.recyclerview.DynamicRecyclerView; + +import butterknife.BindView; + +/** + * Created by Kosh on 11 Nov 2016, 12:36 PM + */ + +public class GistsView extends BaseFragment implements GistsMvp.View { + + public static final String TAG = GistsView.class.getSimpleName(); + + @BindView(R.id.recycler) DynamicRecyclerView recycler; + @BindView(R.id.refresh) SwipeRefreshLayout refresh; + @BindView(R.id.stateLayout) StateLayout stateLayout; + + private GistsAdapter adapter; + private OnLoadMore onLoadMore; + + public static GistsView newInstance() { + return new GistsView(); + } + + @Override protected int fragmentLayout() { + return R.layout.small_grid_refresh_list; + } + + @Override protected void onFragmentCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + refresh.setOnRefreshListener(this); + stateLayout.setOnReloadListener(this); + + recycler.setEmptyView(stateLayout, refresh); + adapter = new GistsAdapter(getPresenter().getGists()); + adapter.setListener(getPresenter()); + getLoadMore().setCurrent_page(getPresenter().getCurrentPage(), getPresenter().getPreviousTotal()); + recycler.setAdapter(adapter); + recycler.addOnScrollListener(getLoadMore()); + if (getPresenter().getGists().isEmpty() && !getPresenter().isApiCalled()) { + onRefresh(); + } + } + + @Override public void onRefresh() { + getPresenter().onCallApi(1, null); + } + + @Override public void onNotifyAdapter() { + + hideProgress(); + adapter.notifyDataSetChanged(); + } + + @Override public void showProgress(@StringRes int resId) { + refresh.setRefreshing(true); + stateLayout.showProgress(); + } + + @Override public void hideProgress() { + refresh.setRefreshing(false); + stateLayout.hideProgress(); + } + + @Override public void showErrorMessage(@NonNull String msgRes) { + hideProgress(); + stateLayout.showReload(adapter.getItemCount()); + super.showErrorMessage(msgRes); + } + + @NonNull @Override public GistsPresenter providePresenter() { + return new GistsPresenter(); + } + + @SuppressWarnings("unchecked") @NonNull @Override public OnLoadMore getLoadMore() { + if (onLoadMore == null) { + onLoadMore = new OnLoadMore(getPresenter()); + } + return onLoadMore; + } + + @Override public void onDestroyView() { + recycler.removeOnScrollListener(getLoadMore()); + super.onDestroyView(); + } + + @Override public void onClick(View view) { + onRefresh(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/gists/create/CreateGistMvp.java b/app/src/main/java/com/fastaccess/ui/modules/gists/create/CreateGistMvp.java new file mode 100644 index 00000000..c3017a9e --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/gists/create/CreateGistMvp.java @@ -0,0 +1,37 @@ +package com.fastaccess.ui.modules.gists.create; + +import android.content.Intent; +import android.support.annotation.NonNull; +import android.support.design.widget.TextInputLayout; + +import com.fastaccess.data.dao.CreateGistModel; +import com.fastaccess.data.dao.GistsModel; +import com.fastaccess.ui.base.mvp.BaseMvp; + +/** + * Created by Kosh on 30 Nov 2016, 10:43 AM + */ + +interface CreateGistMvp { + + interface View extends BaseMvp.FAView { + void onSetCode(@NonNull CharSequence charSequence); + + void onDescriptionError(boolean isEmptyDesc); + + void onFileNameError(boolean isEmptyDesc); + + void onFileContentError(boolean isEmptyDesc); + + void onSuccessSubmission(GistsModel gistsModel); + } + + interface Presenter extends BaseMvp.FAPresenter { + void onActivityForResult(int resultCode, int requestCode, Intent intent); + + void onSubmit(@NonNull TextInputLayout description, @NonNull TextInputLayout fileName, + @NonNull CharSequence fileContent, boolean isPublic); + + void onSubmit(@NonNull CreateGistModel model); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/gists/create/CreateGistPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/gists/create/CreateGistPresenter.java new file mode 100644 index 00000000..83bc5cb3 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/gists/create/CreateGistPresenter.java @@ -0,0 +1,62 @@ +package com.fastaccess.ui.modules.gists.create; + +import android.app.Activity; +import android.content.Intent; +import android.support.annotation.NonNull; +import android.support.design.widget.TextInputLayout; + +import com.fastaccess.data.dao.CreateGistModel; +import com.fastaccess.data.dao.FilesListModel; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; + +import java.util.HashMap; + +/** + * Created by Kosh on 30 Nov 2016, 10:51 AM + */ + +class CreateGistPresenter extends BasePresenter implements CreateGistMvp.Presenter { + + @Override public void onActivityForResult(int resultCode, int requestCode, Intent intent) { + if (resultCode == Activity.RESULT_OK && requestCode == BundleConstant.REQUEST_CODE) { + if (intent != null && intent.getExtras() != null) { + CharSequence charSequence = intent.getExtras().getCharSequence(BundleConstant.EXTRA); + if (!InputHelper.isEmpty(charSequence)) { + sendToView(view -> view.onSetCode(charSequence)); + } + } + } + } + + @Override public void onSubmit(@NonNull TextInputLayout description, @NonNull TextInputLayout fileName, + @NonNull CharSequence fileContent, boolean isPublic) { + boolean isEmptyDesc = InputHelper.isEmpty(description); + boolean isEmptyFileName = InputHelper.isEmpty(fileName); + boolean isEmptyFileContent = InputHelper.isEmpty(fileContent); + if (getView() != null) { + getView().onDescriptionError(isEmptyDesc); + getView().onFileNameError(isEmptyDesc); + getView().onFileContentError(isEmptyDesc); + } + if (!isEmptyDesc && !isEmptyFileName && !isEmptyFileContent) { + CreateGistModel createGistModel = new CreateGistModel(); + createGistModel.setDescription(InputHelper.toString(description)); + createGistModel.setPublicGist(isPublic); + HashMap modelHashMap = new HashMap<>(); + FilesListModel file = new FilesListModel(); + file.setFilename(InputHelper.toString(fileName)); + file.setContent(InputHelper.toString(fileContent)); + modelHashMap.put(InputHelper.toString(fileName), file); + createGistModel.setFiles(modelHashMap); + onSubmit(createGistModel); + } + } + + @Override public void onSubmit(@NonNull CreateGistModel model) { + makeRestCall(RestProvider.getGistService().createGist(model), + gistsModel -> sendToView(view -> view.onSuccessSubmission(gistsModel))); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/gists/create/CreateGistView.java b/app/src/main/java/com/fastaccess/ui/modules/gists/create/CreateGistView.java new file mode 100644 index 00000000..74f8f477 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/gists/create/CreateGistView.java @@ -0,0 +1,105 @@ +package com.fastaccess.ui.modules.gists.create; + +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.design.widget.TextInputLayout; +import android.view.MotionEvent; +import android.view.View; +import android.widget.TextView; + +import com.fastaccess.R; +import com.fastaccess.data.dao.GistsModel; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.provider.markdown.MarkDownProvider; +import com.fastaccess.ui.base.BaseActivity; +import com.fastaccess.ui.modules.editor.EditorView; +import com.fastaccess.ui.widgets.FontButton; + +import butterknife.BindView; +import butterknife.OnClick; +import butterknife.OnTouch; + +/** + * Created by Kosh on 30 Nov 2016, 11:02 AM + */ + +public class CreateGistView extends BaseActivity implements CreateGistMvp.View { + + @BindView(R.id.description) TextInputLayout description; + @BindView(R.id.fileName) TextInputLayout fileName; + @BindView(R.id.fileContent) TextView fileContent; + private CharSequence savedText; + + @Override public void onSetCode(@NonNull CharSequence charSequence) { + this.savedText = charSequence; + //noinspection ConstantConditions + MarkDownProvider.setMdText(fileContent, InputHelper.toString(charSequence)); + } + + @Override public void onDescriptionError(boolean isEmptyDesc) { + description.setError(isEmptyDesc ? getString(R.string.required_field) : null); + } + + @Override public void onFileNameError(boolean isEmptyDesc) { + fileName.setError(isEmptyDesc ? getString(R.string.required_field) : null); + } + + @Override public void onFileContentError(boolean isEmptyDesc) { + fileContent.setError(isEmptyDesc ? getString(R.string.required_field) : null); + } + + @Override public void onSuccessSubmission(GistsModel gistsModel) { + hideProgress(); + finish(); + showMessage(R.string.success, R.string.successfully_submitted); + } + + @Override protected int layout() { + return R.layout.create_gist_layout; + } + + @Override protected boolean isTransparent() { + return false; + } + + @Override protected boolean canBack() { + return true; + } + + @Override protected boolean isSecured() { + return false; + } + + @NonNull @Override public CreateGistPresenter providePresenter() { + return new CreateGistPresenter(); + } + + @Override protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + getPresenter().onActivityForResult(resultCode, requestCode, data); + } + + @OnTouch(R.id.fileContent) boolean onTouch(MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_UP) { + Intent intent = new Intent(this, EditorView.class); + intent.putExtras(Bundler.start() + .put(BundleConstant.EXTRA, InputHelper.toString(savedText)) + .put(BundleConstant.EXTRA_TYPE, BundleConstant.ExtraTYpe.FOR_RESULT_EXTRA) + .end()); + startActivityForResult(intent, BundleConstant.REQUEST_CODE); + return true; + } + return false; + } + + @OnClick(value = {R.id.createPublicGist, R.id.createSecretGist}) void onClick(View view) { + getPresenter().onSubmit(description, fileName, savedText, view.getId() == R.id.createPublicGist); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/gists/gist/GistMvp.java b/app/src/main/java/com/fastaccess/ui/modules/gists/gist/GistMvp.java new file mode 100644 index 00000000..5614c7e7 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/gists/gist/GistMvp.java @@ -0,0 +1,50 @@ +package com.fastaccess.ui.modules.gists.gist; + +import android.content.Intent; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.fastaccess.data.dao.GistsModel; +import com.fastaccess.ui.base.mvp.BaseMvp; + +/** + * Created by Kosh on 12 Nov 2016, 12:17 PM + */ + +interface GistMvp { + + interface View extends BaseMvp.FAView { + void onSuccessDeleted(); + + void onErrorDeleting(); + + void onGistStarred(boolean isStarred); + + void onGistForked(boolean isForked); + + void onSetupDetails(); + } + + interface Presenter extends BaseMvp.FAPresenter { + + @Nullable GistsModel getGist(); + + void onActivityCreated(@Nullable Intent intent); + + void onDeleteGist(); + + boolean isOwner(); + + void onStarGist(); + + void onForkGist(); + + boolean isForked(); + + boolean isStarred(); + + void checkStarring(@NonNull String gistId); + + void onWorkOffline(@NonNull String gistId); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/gists/gist/GistPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/gists/gist/GistPresenter.java new file mode 100644 index 00000000..a344de3b --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/gists/gist/GistPresenter.java @@ -0,0 +1,127 @@ +package com.fastaccess.ui.modules.gists.gist; + +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.fastaccess.data.dao.GistsModel; +import com.fastaccess.data.dao.LoginModel; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.RxHelper; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; + +/** + * Created by Kosh on 12 Nov 2016, 12:17 PM + */ + +class GistPresenter extends BasePresenter implements GistMvp.Presenter { + + + private boolean isGistStarred; + private boolean isGistForked; + private GistsModel gist; + private String gistId; + + @Nullable @Override public GistsModel getGist() { + return gist; + } + + @SuppressWarnings("unchecked") @Override public void onActivityCreated(@Nullable Intent intent) { + if (intent == null || intent.getExtras() == null) { + return; + } + Bundle bundle = intent.getExtras(); + gistId = bundle.getString(BundleConstant.EXTRA); + if (gist != null) { + checkStarring(gist.getGistId()); + sendToView(GistMvp.View::onSetupDetails); + } else if (gistId != null) { + checkStarring(gistId); + makeRestCall(RestProvider.getGistService().getGist(gistId), + gistsModel -> { + this.gist = gistsModel; + sendToView(GistMvp.View::onSetupDetails); + }); + } else { + sendToView(GistMvp.View::onSetupDetails); // tell the activity to finish! + } + } + + @Override public void onDeleteGist() { + if (getGist() == null) return; + manageSubscription(RxHelper.getObserver(RestProvider.getGistService().deleteGist(getGist().getGistId())) + .doOnSubscribe(this::onSubscribed) + .doOnNext(booleanResponse -> { + if (booleanResponse.code() == 204) { + sendToView(GistMvp.View::onSuccessDeleted); + } else { + sendToView(GistMvp.View::onErrorDeleting); + } + }) + .onErrorReturn(throwable -> { + sendToView(view -> view.showErrorMessage(throwable.getMessage())); + return null; + }) + .subscribe()); + } + + @Override public boolean isOwner() { + return getGist() != null && getGist().getOwner() != null && + getGist().getOwner().getLogin().equals(LoginModel.getUser().getLogin()); + } + + @Override public void onStarGist() { + if (getGist() != null) { + makeRestCall(!isGistStarred ? RestProvider.getGistService().starGist(gistId) + : RestProvider.getGistService().unStarGist(gistId), + booleanResponse -> { + if (!isGistStarred) { + isGistStarred = booleanResponse.code() == 204; + } else { + isGistStarred = booleanResponse.code() != 204; + } + sendToView(view -> view.onGistStarred(isGistStarred)); + }); + } + } + + @Override public void onForkGist() { + if (getGist() != null) { + if (!isGistForked) { + makeRestCall(RestProvider.getGistService().forkGist(gistId), + gistsModelResponse -> { + isGistForked = gistsModelResponse.code() == 201; + sendToView(view -> view.onGistForked(isGistForked)); + }); + } + } + } + + @Override public boolean isForked() { + return isGistForked; + } + + @Override public boolean isStarred() { + return isGistStarred; + } + + @Override public void checkStarring(@NonNull String gistId) { + makeRestCall(RestProvider.getGistService().checkGistStar(gistId), + booleanResponse -> { + isGistStarred = booleanResponse.code() == 204; + sendToView(view -> view.onGistStarred(isGistStarred)); + }); + } + + @Override public void onWorkOffline(@NonNull String gistId) { + if (gist == null) { + manageSubscription(GistsModel.getGist(gistId) + .subscribe(gistsModel -> { + this.gist = gistsModel; + sendToView(GistMvp.View::onSetupDetails); + })); + } + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/gists/gist/GistView.java b/app/src/main/java/com/fastaccess/ui/modules/gists/gist/GistView.java new file mode 100644 index 00000000..56713171 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/gists/gist/GistView.java @@ -0,0 +1,198 @@ +package com.fastaccess.ui.modules.gists.gist; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.FloatingActionButton; +import android.support.design.widget.TabLayout; +import android.support.v4.view.ViewPager; +import android.text.format.Formatter; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.data.dao.FragmentPagerAdapterModel; +import com.fastaccess.data.dao.GistsModel; +import com.fastaccess.helper.ActivityHelper; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.helper.ParseDateFormat; +import com.fastaccess.ui.adapter.FragmentsPagerAdapter; +import com.fastaccess.ui.base.BaseActivity; +import com.fastaccess.ui.modules.gists.gist.comments.GistCommentsView; +import com.fastaccess.ui.widgets.AvatarLayout; +import com.fastaccess.ui.widgets.FontTextView; +import com.fastaccess.ui.widgets.ForegroundImageView; +import com.fastaccess.ui.widgets.ViewPagerView; +import com.fastaccess.ui.widgets.dialog.MessageDialogView; + +import butterknife.BindView; +import butterknife.OnClick; + +/** + * Created by Kosh on 12 Nov 2016, 12:18 PM + */ + +public class GistView extends BaseActivity + implements GistMvp.View { + + @BindView(R.id.avatarLayout) AvatarLayout avatarLayout; + @BindView(R.id.headerTitle) FontTextView title; + @BindView(R.id.size) FontTextView size; + @BindView(R.id.date) FontTextView date; + @BindView(R.id.pager) ViewPagerView pager; + @BindView(R.id.tabs) TabLayout tabs; + @BindView(R.id.fab) FloatingActionButton fab; + @BindView(R.id.startGist) ForegroundImageView startGist; + @BindView(R.id.forkGist) ForegroundImageView forkGist; + + public static Intent createIntent(@NonNull Context context, @NonNull String gistId) { + Intent intent = new Intent(context, GistView.class); + intent.putExtras(Bundler.start().put(BundleConstant.EXTRA, gistId).end()); + return intent; + } + + @OnClick(R.id.fab) void onAddComment() { + GistCommentsView view = (GistCommentsView) pager.getAdapter().instantiateItem(pager, 1); + if (view != null) { + view.onStartNewComment(); + } + } + + @OnClick(R.id.headerTitle) void onTitleClick() { + if (getPresenter().getGist() != null && !InputHelper.isEmpty(getPresenter().getGist().getDescription())) + showMessage(getString(R.string.details), getPresenter().getGist().getDescription()); + } + + @OnClick({R.id.startGist, R.id.forkGist}) public void onGistActions(View view) { + view.setEnabled(false); + switch (view.getId()) { + case R.id.startGist: + getPresenter().onStarGist(); + break; + case R.id.forkGist: + getPresenter().onForkGist(); + break; + } + } + + @Override protected int layout() { + return R.layout.gists_pager_layout; + } + + @Override protected boolean isTransparent() { + return false; + } + + @Override protected boolean canBack() { + return true; + } + + @Override protected boolean isSecured() { + return false; + } + + @NonNull @Override public GistPresenter providePresenter() { + return new GistPresenter(); + } + + @Override protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (savedInstanceState == null) { + getPresenter().onActivityCreated(getIntent()); + } else { + if (getPresenter().getGist() != null) { + onSetupDetails(); + } + } + } + + @Override public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.gist_menu, menu); + menu.findItem(R.id.deleteGist).setVisible(getPresenter().isOwner()); + return super.onCreateOptionsMenu(menu); + } + + @Override public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.share) { + if (getPresenter().getGist() != null) ActivityHelper.shareUrl(this, getPresenter().getGist().getHtmlUrl()); + return true; + } else if (item.getItemId() == R.id.deleteGist) { + MessageDialogView.newInstance( + getString(R.string.delete_gist), getString(R.string.confirm_message), + Bundler.start().put(BundleConstant.EXTRA, true).end()) + .show(getSupportFragmentManager(), MessageDialogView.TAG); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override public void onMessageDialogActionClicked(boolean isOk, @Nullable Bundle bundle) { + super.onMessageDialogActionClicked(isOk, bundle); + if (bundle != null) { + boolean isDelete = bundle.getBoolean(BundleConstant.EXTRA) && isOk; + if (isDelete) { + getPresenter().onDeleteGist(); + } + } + } + + @Override public void onSuccessDeleted() { + hideProgress(); + finish(); + } + + @Override public void onErrorDeleting() { + showErrorMessage(getString(R.string.error_deleting_gist)); + } + + @Override public void onGistStarred(boolean isStarred) { + startGist.tintDrawable(isStarred ? R.color.accent : R.color.black); + startGist.setEnabled(true); + } + + @Override public void onGistForked(boolean isForked) { + forkGist.tintDrawable(isForked ? R.color.accent : R.color.black); + forkGist.setEnabled(true); + } + + @Override public void onSetupDetails() { + hideProgress(); + GistsModel gistsModel = getPresenter().getGist(); + if (gistsModel == null) { + finish(); + return; + } + String url = gistsModel.getOwner() != null ? gistsModel.getOwner().getAvatarUrl() : + gistsModel.getUser() != null ? gistsModel.getUser().getAvatarUrl() : ""; + String login = gistsModel.getOwner() != null ? gistsModel.getOwner().getLogin() : + gistsModel.getUser() != null ? gistsModel.getUser().getLogin() : ""; + avatarLayout.setUrl(url, login); + title.setText(gistsModel.getDisplayTitle(false)); + date.setText(ParseDateFormat.getTimeAgo(gistsModel.getCreatedAt())); + size.setText(Formatter.formatFileSize(this, gistsModel.getSize())); + pager.setAdapter(new FragmentsPagerAdapter(getSupportFragmentManager(), FragmentPagerAdapterModel.buildForGist(this, gistsModel))); + tabs.setupWithViewPager(pager); + pager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { + @Override public void onPageSelected(int position) { + super.onPageSelected(position); + hideShowFab(); + } + }); + onGistForked(getPresenter().isForked()); + onGistStarred(getPresenter().isStarred()); + hideShowFab(); + } + + private void hideShowFab() { + if (pager.getCurrentItem() == 1) { + fab.show(); + } else { + fab.hide(); + } + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/gists/gist/comments/GistCommentsMvp.java b/app/src/main/java/com/fastaccess/ui/modules/gists/gist/comments/GistCommentsMvp.java new file mode 100644 index 00000000..877d5fa9 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/gists/gist/comments/GistCommentsMvp.java @@ -0,0 +1,61 @@ +package com.fastaccess.ui.modules.gists.gist.comments; + +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.widget.SwipeRefreshLayout; + +import com.fastaccess.data.dao.CommentsModel; +import com.fastaccess.data.dao.UserModel; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.adapter.CommentsAdapter; +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; +import com.fastaccess.ui.widgets.recyclerview.DynamicRecyclerView; + +import java.util.ArrayList; + +import retrofit2.Response; + +/** + * Created by Kosh on 20 Nov 2016, 11:10 AM + */ + +interface GistCommentsMvp { + + interface View extends BaseMvp.FAView, SwipeRefreshLayout.OnRefreshListener, + android.view.View.OnClickListener { + + void onNotifyAdapter(); + + @NonNull OnLoadMore getLoadMore(); + + void onEditComment(@NonNull CommentsModel item); + + void onStartNewComment(); + + void onHandleCommentDelete(@NonNull Response booleanResponse, long commId); + + void onShowDeleteMsg(long id); + + void onShowProgressDialog(); + + void onTagUser(@NonNull UserModel user); + } + + interface Presenter extends BaseMvp.FAPresenter, + BaseMvp.PaginationListener, BaseViewHolder.OnItemClickListener { + + @NonNull ArrayList getComments(); + + void onActivityResult(int requestCode, int resultCode, @Nullable Intent data, + @NonNull DynamicRecyclerView recycler, @NonNull CommentsAdapter adapter); + + void onHandleDeletion(@Nullable Bundle bundle); + + void onWorkOffline(@NonNull String gistId); + } + + +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/gists/gist/comments/GistCommentsPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/gists/gist/comments/GistCommentsPresenter.java new file mode 100644 index 00000000..382febd9 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/gists/gist/comments/GistCommentsPresenter.java @@ -0,0 +1,154 @@ +package com.fastaccess.ui.modules.gists.gist.comments; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.TextUtils; +import android.view.View; + +import com.fastaccess.data.dao.CommentsModel; +import com.fastaccess.data.dao.LoginModel; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Logger; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.ui.adapter.CommentsAdapter; +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; +import com.fastaccess.ui.widgets.recyclerview.DynamicRecyclerView; + +import java.util.ArrayList; + +import rx.Observable; + +/** + * Created by Kosh on 11 Nov 2016, 12:36 PM + */ + +class GistCommentsPresenter extends BasePresenter implements GistCommentsMvp.Presenter { + private ArrayList comments = new ArrayList<>(); + private int page; + private int previousTotal; + private int lastPage = Integer.MAX_VALUE; + + @Override public int getCurrentPage() { + return page; + } + + @Override public int getPreviousTotal() { + return previousTotal; + } + + @Override public void setCurrentPage(int page) { + this.page = page; + } + + @Override public void setPreviousTotal(int previousTotal) { + this.previousTotal = previousTotal; + } + + @Override public T onError(@NonNull Throwable throwable, @NonNull Observable observable) { + //noinspection ConstantConditions + sendToView(view -> onWorkOffline(view.getLoadMore().getParameter())); + return super.onError(throwable, observable); + } + + @Override public void onCallApi(int page, @Nullable String parameter) { + if (page == 1) { + lastPage = Integer.MAX_VALUE; + sendToView(view -> view.getLoadMore().reset()); + } + if (page > lastPage || parameter == null || lastPage == 0) { + sendToView(GistCommentsMvp.View::hideProgress); + return; + } + setCurrentPage(page); + makeRestCall(RestProvider.getGistService().getGistComments(parameter, page), + listResponse -> { + lastPage = listResponse.getLast(); + if (getCurrentPage() == 1) { + getComments().clear(); + manageSubscription(CommentsModel.saveForGist(listResponse.getItems(), parameter).subscribe()); + } + getComments().addAll(listResponse.getItems()); + sendToView(GistCommentsMvp.View::onNotifyAdapter); + }); + } + + @NonNull @Override public ArrayList getComments() { + return comments; + } + + @Override public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data, + @NonNull DynamicRecyclerView recycler, @NonNull CommentsAdapter adapter) { + if (resultCode == Activity.RESULT_OK && data != null) { + if (requestCode == BundleConstant.REQUEST_CODE) { + Bundle bundle = data.getExtras(); + if (bundle != null) { + boolean isNew = bundle.getBoolean(BundleConstant.EXTRA); + CommentsModel commentsModel = bundle.getParcelable(BundleConstant.ITEM); + if (isNew) { + adapter.addItem(commentsModel); + recycler.smoothScrollToPosition(adapter.getItemCount()); + } else { + int position = adapter.getItem(commentsModel); + if (position != -1) { + adapter.swapItem(commentsModel, position); + recycler.smoothScrollToPosition(position); + } else { + adapter.addItem(commentsModel); + recycler.smoothScrollToPosition(adapter.getItemCount()); + } + } + } + } + } + } + + @Override public void onHandleDeletion(@Nullable Bundle bundle) { + if (bundle != null) { + long commId = bundle.getLong(BundleConstant.EXTRA, 0); + String gistId = bundle.getString(BundleConstant.ID); + if (commId != 0 && gistId != null) { + makeRestCall(RestProvider.getGistService().deleteGistComment(gistId, commId), + booleanResponse -> sendToView(view -> view.onHandleCommentDelete(booleanResponse, commId))); + } + } + } + + @Override public void onWorkOffline(@NonNull String gistId) { + if (comments.isEmpty()) { + manageSubscription(CommentsModel.getGistComments(gistId).subscribe( + localComments -> { + if (localComments != null && !localComments.isEmpty()) { + Logger.e(localComments.size()); + comments.addAll(localComments); + sendToView(GistCommentsMvp.View::onNotifyAdapter); + } + } + )); + } else { + sendToView(BaseMvp.FAView::hideProgress); + } + } + + @Override public void onItemClick(int position, View v, CommentsModel item) { + if (item.getUser() != null) { + LoginModel userModel = LoginModel.getUser(); + if (userModel != null && item.getUser().getLogin().equals(userModel.getLogin())) { + if (getView() != null) getView().onEditComment(item); + } else { + if (getView() != null) getView().onTagUser(item.getUser()); + } + } + } + + @Override public void onItemLongClick(int position, View v, CommentsModel item) { + if (item.getUser() != null && TextUtils.equals(item.getUser().getLogin(), LoginModel.getUser().getLogin())) { + if (getView() != null) getView().onShowDeleteMsg(item.getId()); + } else { + onItemClick(position, v, item); + } + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/gists/gist/comments/GistCommentsView.java b/app/src/main/java/com/fastaccess/ui/modules/gists/gist/comments/GistCommentsView.java new file mode 100644 index 00000000..51c64c57 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/gists/gist/comments/GistCommentsView.java @@ -0,0 +1,185 @@ +package com.fastaccess.ui.modules.gists.gist.comments; + +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.v4.widget.SwipeRefreshLayout; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.data.dao.CommentsModel; +import com.fastaccess.data.dao.UserModel; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.adapter.CommentsAdapter; +import com.fastaccess.ui.base.BaseFragment; +import com.fastaccess.ui.modules.editor.EditorView; +import com.fastaccess.ui.widgets.StateLayout; +import com.fastaccess.ui.widgets.dialog.MessageDialogView; +import com.fastaccess.ui.widgets.recyclerview.DynamicRecyclerView; + +import butterknife.BindView; +import retrofit2.Response; + +import static com.fastaccess.helper.BundleConstant.ExtraTYpe.EDIT_GIST_COMMENT_EXTRA; +import static com.fastaccess.helper.BundleConstant.ExtraTYpe.NEW_GIST_COMMENT_EXTRA; + +/** + * Created by Kosh on 11 Nov 2016, 12:36 PM + */ + +public class GistCommentsView extends BaseFragment implements GistCommentsMvp.View { + + @BindView(R.id.recycler) DynamicRecyclerView recycler; + @BindView(R.id.refresh) SwipeRefreshLayout refresh; + @BindView(R.id.stateLayout) StateLayout stateLayout; + + private String gistId; + private CommentsAdapter adapter; + private OnLoadMore onLoadMore; + + public static GistCommentsView newInstance(@NonNull String gistId) { + GistCommentsView view = new GistCommentsView(); + view.setArguments(Bundler.start().put("gistId", gistId).end()); + return view; + } + + @Override protected int fragmentLayout() { + return R.layout.small_grid_refresh_list; + } + + @Override protected void onFragmentCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + gistId = getArguments().getString("gistId"); + recycler.setEmptyView(stateLayout, refresh); + recycler.setItemViewCacheSize(10);//FIXME + if (gistId == null) return; + refresh.setOnRefreshListener(this); + stateLayout.setOnReloadListener(this); + adapter = new CommentsAdapter(getPresenter().getComments()); + adapter.setListener(getPresenter()); + getLoadMore().setCurrent_page(getPresenter().getCurrentPage(), getPresenter().getPreviousTotal()); + recycler.setAdapter(adapter); + recycler.addOnScrollListener(getLoadMore()); + if (getPresenter().getComments().isEmpty() && !getPresenter().isApiCalled()) { + onRefresh(); + } + } + + @Override public void onRefresh() { + getPresenter().onCallApi(1, gistId); + } + + @Override public void onNotifyAdapter() { + hideProgress(); + adapter.notifyDataSetChanged(); + } + + @Override public void hideProgress() { + super.hideProgress(); + refresh.setRefreshing(false); + stateLayout.hideProgress(); + } + + @Override public void showProgress(@StringRes int resId) { + refresh.setRefreshing(true); + stateLayout.showProgress(); + } + + @Override public void showErrorMessage(@NonNull String message) { + hideProgress(); + stateLayout.showReload(adapter.getItemCount()); + super.showErrorMessage(message); + } + + @NonNull @Override public GistCommentsPresenter providePresenter() { + return new GistCommentsPresenter(); + } + + @NonNull @Override public OnLoadMore getLoadMore() { + if (onLoadMore == null) { + onLoadMore = new OnLoadMore<>(getPresenter(), gistId); + } + return onLoadMore; + } + + @Override public void onEditComment(@NonNull CommentsModel item) { + Intent intent = new Intent(getContext(), EditorView.class); + intent.putExtras(Bundler + .start() + .put(BundleConstant.ID, gistId) + .put(BundleConstant.EXTRA, item.getBody()) + .put(BundleConstant.EXTRA_FOUR, item.getId()) + .put(BundleConstant.EXTRA_TYPE, EDIT_GIST_COMMENT_EXTRA) + .end()); + startActivityForResult(intent, BundleConstant.REQUEST_CODE); + } + + @Override public void onStartNewComment() { + Intent intent = new Intent(getContext(), EditorView.class); + intent.putExtras(Bundler + .start() + .put(BundleConstant.ID, gistId) + .put(BundleConstant.EXTRA_TYPE, NEW_GIST_COMMENT_EXTRA) + .end()); + startActivityForResult(intent, BundleConstant.REQUEST_CODE); + } + + @Override public void onHandleCommentDelete(@NonNull Response booleanResponse, long commId) { + hideProgress(); + if (booleanResponse.code() == 204) { + CommentsModel commentsModel = new CommentsModel(); + commentsModel.setId(commId); + adapter.removeItem(commentsModel); + } else { + showErrorMessage(getString(R.string.error_deleting_comment)); + } + } + + @Override public void onShowDeleteMsg(long id) { + MessageDialogView.newInstance(getString(R.string.delete), getString(R.string.confirm_message), + Bundler.start() + .put(BundleConstant.EXTRA, id) + .put(BundleConstant.ID, gistId) + .end()) + .show(getChildFragmentManager(), MessageDialogView.TAG); + } + + @Override public void onShowProgressDialog() { + callback.showProgress(0); + } + + @Override public void onTagUser(@NonNull UserModel user) { + Intent intent = new Intent(getContext(), EditorView.class); + intent.putExtras(Bundler + .start() + .put(BundleConstant.ID, gistId) + .put(BundleConstant.EXTRA, "@" + user.getLogin()) + .put(BundleConstant.EXTRA_TYPE, NEW_GIST_COMMENT_EXTRA) + .end()); + startActivityForResult(intent, BundleConstant.REQUEST_CODE); + } + + @Override public void onDestroyView() { + recycler.removeOnScrollListener(getLoadMore()); + super.onDestroyView(); + } + + @Override public void onClick(View view) { + onRefresh(); + } + + @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + getPresenter().onActivityResult(requestCode, resultCode, data, recycler, adapter); + } + + @Override public void onMessageDialogActionClicked(boolean isOk, @Nullable Bundle bundle) { + super.onMessageDialogActionClicked(isOk, bundle); + if (isOk) { + getPresenter().onHandleDeletion(bundle); + } + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/gists/gist/files/GistFilesListMvp.java b/app/src/main/java/com/fastaccess/ui/modules/gists/gist/files/GistFilesListMvp.java new file mode 100644 index 00000000..91cde58e --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/gists/gist/files/GistFilesListMvp.java @@ -0,0 +1,20 @@ +package com.fastaccess.ui.modules.gists.gist.files; + +import android.support.annotation.NonNull; + +import com.fastaccess.data.dao.FilesListModel; +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +/** + * Created by Kosh on 13 Nov 2016, 1:35 PM + */ + +interface GistFilesListMvp { + + interface View extends BaseMvp.FAView { + void onOpenFile(@NonNull FilesListModel item); + } + + interface Presenter extends BaseMvp.FAPresenter, BaseViewHolder.OnItemClickListener {} +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/gists/gist/files/GistFilesListPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/gists/gist/files/GistFilesListPresenter.java new file mode 100644 index 00000000..1ec0027a --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/gists/gist/files/GistFilesListPresenter.java @@ -0,0 +1,23 @@ +package com.fastaccess.ui.modules.gists.gist.files; + +import android.view.View; + +import com.fastaccess.data.dao.FilesListModel; +import com.fastaccess.helper.Logger; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; + +/** + * Created by Kosh on 13 Nov 2016, 1:35 PM + */ + +class GistFilesListPresenter extends BasePresenter implements GistFilesListMvp.Presenter { + + @Override public void onItemClick(int position, View v, FilesListModel item) { + Logger.e(item.getFilename()); + if (getView() != null) getView().onOpenFile(item); + } + + @Override public void onItemLongClick(int position, View v, FilesListModel item) { + onItemClick(position, v, item); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/gists/gist/files/GistFilesListView.java b/app/src/main/java/com/fastaccess/ui/modules/gists/gist/files/GistFilesListView.java new file mode 100644 index 00000000..34ef3159 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/gists/gist/files/GistFilesListView.java @@ -0,0 +1,85 @@ +package com.fastaccess.ui.modules.gists.gist.files; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.widget.SwipeRefreshLayout; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.data.dao.FilesListModel; +import com.fastaccess.data.dao.GithubFileModel; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.ui.adapter.GistFilesAdapter; +import com.fastaccess.ui.base.BaseFragment; +import com.fastaccess.ui.modules.code.CodeViewerView; +import com.fastaccess.ui.widgets.StateLayout; +import com.fastaccess.ui.widgets.dialog.MessageDialogView; +import com.fastaccess.ui.widgets.recyclerview.DynamicRecyclerView; + +import java.util.ArrayList; + +import butterknife.BindView; + +/** + * Created by Kosh on 13 Nov 2016, 1:36 PM + */ + +public class GistFilesListView extends BaseFragment implements + GistFilesListMvp.View { + + @BindView(R.id.recycler) DynamicRecyclerView recycler; + @BindView(R.id.refresh) SwipeRefreshLayout refresh; + @BindView(R.id.stateLayout) StateLayout stateLayout; + + public static GistFilesListView newInstance(@NonNull GithubFileModel gistsModel) { + GistFilesListView view = new GistFilesListView(); + view.setArguments(Bundler.start().put(BundleConstant.ITEM, gistsModel).end()); + return view; + } + + @Override protected int fragmentLayout() { + return R.layout.small_grid_refresh_list; + } + + @NonNull @Override public GistFilesListPresenter providePresenter() { + return new GistFilesListPresenter(); + } + + @Override protected void onFragmentCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + GithubFileModel gistsModel = (GithubFileModel) getArguments().getSerializable(BundleConstant.ITEM); + stateLayout.hideReload(); + stateLayout.setEmptyText(R.string.no_files); + recycler.setEmptyView(stateLayout); + refresh.setEnabled(false); + if (gistsModel == null) { + return; + } + if (!gistsModel.values().isEmpty()) { + recycler.setAdapter(new GistFilesAdapter(new ArrayList<>(gistsModel.values()), getPresenter())); + } + } + + @Override public void onOpenFile(@NonNull FilesListModel item) { + if (item.getSize() > 1000000 /* > 1mb */) { + MessageDialogView.newInstance(getString(R.string.big_file), getString(R.string.big_file_description), + Bundler.start().put(BundleConstant.EXTRA, item.getRawUrl()).end()) + .show(getChildFragmentManager(), "MessageDialogView"); + } else { + CodeViewerView.startActivity(getContext(), item.getRawUrl()); + } + } + + @Override public void onMessageDialogActionClicked(boolean isOk, @Nullable Bundle bundle) { + super.onMessageDialogActionClicked(isOk, bundle); + if (isOk && bundle != null) { + String url = bundle.getString(BundleConstant.EXTRA); + if (!InputHelper.isEmpty(url)) { + RestProvider.downloadFile(getContext(), url); + } + } + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/login/LoginMvp.java b/app/src/main/java/com/fastaccess/ui/modules/login/LoginMvp.java new file mode 100644 index 00000000..729a8cc2 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/login/LoginMvp.java @@ -0,0 +1,34 @@ +package com.fastaccess.ui.modules.login; + +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.fastaccess.data.dao.AccessTokenModel; +import com.fastaccess.data.dao.LoginModel; +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.widgets.AppbarRefreshLayout; + +/** + * Created by Kosh on 09 Nov 2016, 9:41 PM + */ + +interface LoginMvp { + + interface View extends BaseMvp.FAView, AppbarRefreshLayout.OnRefreshListener { + void onSuccessfullyLoggedIn(); + } + + interface Presenter extends BaseMvp.FAPresenter { + + @Nullable String getCode(@NonNull String url); + + @NonNull Uri getAuthorizationUrl(); + + void onGetToken(@NonNull String code); + + void onTokenResponse(@Nullable AccessTokenModel response); + + void onUserResponse(@Nullable LoginModel response); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/login/LoginPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/login/LoginPresenter.java new file mode 100644 index 00000000..d4da2331 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/login/LoginPresenter.java @@ -0,0 +1,77 @@ +package com.fastaccess.ui.modules.login; + +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.fastaccess.BuildConfig; +import com.fastaccess.R; +import com.fastaccess.data.dao.AccessTokenModel; +import com.fastaccess.data.dao.LoginModel; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.helper.PrefGetter; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; + +/** + * Created by Kosh on 09 Nov 2016, 9:43 PM + */ + +class LoginPresenter extends BasePresenter implements LoginMvp.Presenter { + + @Nullable @Override public String getCode(@NonNull String url) { + Uri uri = Uri.parse(url); + if (uri != null && uri.toString().startsWith(BuildConfig.REDIRECT_URL)) { + String code = uri.getQueryParameter("code"); + if (code != null) { + return code; + } else if (uri.getQueryParameter("error") != null) { + sendToView(view -> view.showMessage(R.string.error, R.string.failed_login)); + } + } + return null; + } + + @NonNull @Override public Uri getAuthorizationUrl() { + return new Uri.Builder() + .scheme("https") + .authority("github.com") + .appendPath("login") + .appendPath("oauth") + .appendPath("authorize") + .appendQueryParameter("client_id", BuildConfig.GITHUB_CLIENT_ID) + .appendQueryParameter("redirect_uri", BuildConfig.REDIRECT_URL) + .appendQueryParameter("scope", "user,repo,gist,delete_repo,notifications") + .appendQueryParameter("state", BuildConfig.APPLICATION_ID) + .build(); + } + + @Override public void onGetToken(@NonNull String code) { + makeRestCall(RestProvider.getLoginRestService().getAccessToken(code, + BuildConfig.GITHUB_CLIENT_ID, BuildConfig.GITHUB_SECRET, + BuildConfig.APPLICATION_ID, BuildConfig.REDIRECT_URL), + this::onTokenResponse); + } + + @Override public void onTokenResponse(@Nullable AccessTokenModel modelResponse) { + if (modelResponse != null) { + String token = modelResponse.getAccessToken(); + if (!InputHelper.isEmpty(token)) { + PrefGetter.setToken(token); + makeRestCall(RestProvider.getUserService().getUser(), this::onUserResponse); + return; + } + } + sendToView(view -> view.showMessage(R.string.error, R.string.failed_login)); + } + + @Override public void onUserResponse(@Nullable LoginModel userModel) { + if (userModel != null) { + userModel.setToken(PrefGetter.getToken()); + userModel.save(); + sendToView(LoginMvp.View::onSuccessfullyLoggedIn); + return; + } + sendToView(view -> view.showMessage(R.string.error, R.string.failed_login)); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/login/LoginView.java b/app/src/main/java/com/fastaccess/ui/modules/login/LoginView.java new file mode 100644 index 00000000..4ff865ef --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/login/LoginView.java @@ -0,0 +1,83 @@ +package com.fastaccess.ui.modules.login; + +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.webkit.WebChromeClient; +import android.webkit.WebView; +import android.webkit.WebViewClient; + +import com.fastaccess.R; +import com.fastaccess.helper.Logger; +import com.fastaccess.ui.base.BaseActivity; +import com.fastaccess.ui.modules.main.MainView; +import com.fastaccess.ui.widgets.AppbarRefreshLayout; + +import butterknife.BindView; + +/** + * Created by Kosh on 08 Feb 2017, 9:10 PM + */ + +public class LoginView extends BaseActivity implements LoginMvp.View { + + + @BindView(R.id.webView) WebView webView; + @BindView(R.id.refresh) AppbarRefreshLayout refresh; + + @Override protected int layout() { + return R.layout.login_layout; + } + + @Override protected boolean isTransparent() { + return true; + } + + @Override protected boolean canBack() { + return false; + } + + @Override protected boolean isSecured() { + return true; + } + + @NonNull @Override public LoginPresenter providePresenter() { + return new LoginPresenter(); + } + + @Override public void onRefresh() { + webView.loadUrl(getPresenter().getAuthorizationUrl().toString()); + } + + @Override public void onSuccessfullyLoggedIn() { + hideProgress(); + startActivity(new Intent(this, MainView.class)); + finish(); + } + + @Override protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + refresh.setOnRefreshListener(this); + webView.setWebChromeClient(new WebChromeClient() { + @Override public void onProgressChanged(WebView view, int progress) { + super.onProgressChanged(view, progress); + if (progress == 100) { + refresh.setRefreshing(false); + } else if (progress < 100) { + refresh.setRefreshing(true);//if (!refresh.isRefreshing()) is handled by the method,we shouldn't care. + } + } + }); + webView.setWebViewClient(new WebViewClient() { + @SuppressWarnings("deprecation") @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { + String code = getPresenter().getCode(url); + Logger.e(code, url); + if (code != null) { + getPresenter().onGetToken(code); + } + return false; + } + }); + onRefresh(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/main/MainMvp.java b/app/src/main/java/com/fastaccess/ui/modules/main/MainMvp.java new file mode 100644 index 00000000..49504e7a --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/main/MainMvp.java @@ -0,0 +1,58 @@ +package com.fastaccess.ui.modules.main; + +import android.support.annotation.IntDef; +import android.support.annotation.NonNull; +import android.support.design.widget.NavigationView; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.widget.DrawerLayout; + +import com.fastaccess.ui.base.mvp.BaseMvp; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import it.sephiroth.android.library.bottomnavigation.BottomNavigation; + +/** + * Created by Kosh on 09 Nov 2016, 7:51 PM + */ + +interface MainMvp { + + int FEEDS = 0; + int GISTS = 1; + int PROFILE = 2; + + @IntDef({ + FEEDS, + GISTS, + PROFILE, + }) + @Retention(RetentionPolicy.SOURCE) @interface NavigationType {} + + interface View extends BaseMvp.FAView { + + void onNavigationChanged(@NavigationType int navType); + + void onOpenDrawer(); + + void onCloseDrawer(); + + void onOpenSettings(); + + void onHideShowFab(); + } + + interface Presenter extends BaseMvp.FAPresenter, NavigationView.OnNavigationItemSelectedListener, + BottomNavigation.OnMenuItemSelectionListener { + + boolean canBackPress(@NonNull DrawerLayout drawerLayout); + + void onModuleChanged(@NonNull FragmentManager fragmentManager, @NavigationType int type); + + void onShowHideFragment(@NonNull FragmentManager fragmentManager, @NonNull Fragment toShow, @NonNull Fragment toHide); + + void onAddAndHide(@NonNull FragmentManager fragmentManager, @NonNull Fragment toAdd, @NonNull Fragment toHide); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/main/MainPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/main/MainPresenter.java new file mode 100644 index 00000000..2d735cb8 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/main/MainPresenter.java @@ -0,0 +1,96 @@ +package com.fastaccess.ui.modules.main; + +import android.support.annotation.IdRes; +import android.support.annotation.NonNull; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.view.GravityCompat; +import android.support.v4.widget.DrawerLayout; +import android.view.MenuItem; + +import com.fastaccess.R; +import com.fastaccess.data.dao.LoginModel; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; +import com.fastaccess.ui.modules.feeds.FeedsView; +import com.fastaccess.ui.modules.gists.GistsView; +import com.fastaccess.ui.modules.profile.ProfilePagerView; + +import hugo.weaving.DebugLog; + +import static com.fastaccess.helper.ActivityHelper.getVisibleFragment; +import static com.fastaccess.helper.AppHelper.getFragmentByTag; + +/** + * Created by Kosh on 09 Nov 2016, 7:53 PM + */ + +class MainPresenter extends BasePresenter implements MainMvp.Presenter { + + @Override public boolean canBackPress(@NonNull DrawerLayout drawerLayout) { + return !drawerLayout.isDrawerOpen(GravityCompat.START); + } + + @DebugLog @SuppressWarnings("ConstantConditions") + @Override public void onModuleChanged(@NonNull FragmentManager fragmentManager, @MainMvp.NavigationType int type) { + Fragment currentVisible = getVisibleFragment(fragmentManager); + FeedsView homeView = (FeedsView) getFragmentByTag(fragmentManager, FeedsView.TAG); + GistsView gistsView = (GistsView) getFragmentByTag(fragmentManager, GistsView.TAG); + ProfilePagerView profileView = (ProfilePagerView) getFragmentByTag(fragmentManager, ProfilePagerView.TAG); + switch (type) { + case MainMvp.FEEDS: + if (homeView == null) { + onAddAndHide(fragmentManager, FeedsView.newInstance(), currentVisible); + } else { + onShowHideFragment(fragmentManager, homeView, currentVisible); + } + break; + case MainMvp.GISTS: + if (gistsView == null) { + onAddAndHide(fragmentManager, GistsView.newInstance(), currentVisible); + } else { + onShowHideFragment(fragmentManager, gistsView, currentVisible); + } + break; + case MainMvp.PROFILE: + if (profileView == null) { + onAddAndHide(fragmentManager, ProfilePagerView.newInstance(LoginModel.getUser().getLogin()), currentVisible); + } else { + onShowHideFragment(fragmentManager, profileView, currentVisible); + } + break; + } + } + + @DebugLog @Override public void onShowHideFragment(@NonNull FragmentManager fragmentManager, @NonNull Fragment toShow, @NonNull Fragment toHide) { + toHide.onHiddenChanged(true); + fragmentManager + .beginTransaction() + .hide(toHide) + .show(toShow) + .commit(); + toShow.onHiddenChanged(false); + } + + @DebugLog @Override public void onAddAndHide(@NonNull FragmentManager fragmentManager, @NonNull Fragment toAdd, @NonNull Fragment toHide) { + toHide.onHiddenChanged(true); + fragmentManager + .beginTransaction() + .hide(toHide) + .add(R.id.container, toAdd, toAdd.getClass().getSimpleName()) + .commit(); + toAdd.onHiddenChanged(false); + } + + @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { + if (getView() != null) getView().onCloseDrawer(); + return false; + } + + @Override public void onMenuItemSelect(@IdRes int id, int position) { + if (getView() != null) { + getView().onNavigationChanged(position); + } + } + + @Override public void onMenuItemReselect(@IdRes int id, int position) {} +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/main/MainView.java b/app/src/main/java/com/fastaccess/ui/modules/main/MainView.java new file mode 100644 index 00000000..fceedea6 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/main/MainView.java @@ -0,0 +1,169 @@ +package com.fastaccess.ui.modules.main; + +import android.content.Intent; +import android.graphics.Typeface; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.FloatingActionButton; +import android.support.design.widget.NavigationView; +import android.support.v4.view.GravityCompat; +import android.support.v4.widget.DrawerLayout; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.TextView; +import android.widget.Toast; + +import com.fastaccess.R; +import com.fastaccess.data.dao.LoginModel; +import com.fastaccess.helper.TypeFaceHelper; +import com.fastaccess.ui.base.BaseActivity; +import com.fastaccess.ui.modules.feeds.FeedsView; +import com.fastaccess.ui.modules.gists.create.CreateGistView; +import com.fastaccess.ui.modules.search.SearchView; +import com.fastaccess.ui.widgets.AvatarLayout; + +import butterknife.BindView; +import butterknife.OnClick; +import icepick.State; +import it.sephiroth.android.library.bottomnavigation.BottomNavigation; + +public class MainView extends BaseActivity implements MainMvp.View { + + @MainMvp.NavigationType @State int navType = MainMvp.FEEDS; + + @BindView(R.id.fab) FloatingActionButton fab; + @BindView(R.id.bottomNavigation) BottomNavigation bottomNavigation; + @BindView(R.id.navigation) NavigationView navigationView; + @BindView(R.id.drawerLayout) DrawerLayout drawerLayout; + + private long backPressTimer; + + @OnClick(R.id.fab) void onClick() { + if (navType == MainMvp.GISTS) { + startActivity(new Intent(this, CreateGistView.class)); + } + } + + @NonNull @Override public MainPresenter providePresenter() { + return new MainPresenter(); + } + + @Override protected int layout() { + return R.layout.activity_main_view; + } + + @Override protected boolean isTransparent() { + return true; + } + + @Override protected boolean canBack() { + return false; + } + + @Override protected boolean isSecured() { + return false; + } + + @Override protected void onCreate(Bundle savedInstanceState) { + setTheme(R.style.AppTheme); + super.onCreate(savedInstanceState); + setToolbarIcon(R.drawable.ic_menu); + onInit(savedInstanceState); + onHideShowFab(); + hideShowShadow(navType != MainMvp.PROFILE); + } + + @Override public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.search_menu, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + onOpenDrawer(); + return true; + } else if (item.getItemId() == R.id.search) { + startActivity(new Intent(this, SearchView.class)); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override public void onBackPressed() { + if (drawerLayout != null) { + if (getPresenter().canBackPress(drawerLayout)) { + if (canExit()) super.onBackPressed(); + } else { + drawerLayout.closeDrawer(GravityCompat.START); + } + } else { + if (canExit()) super.onBackPressed(); + } + } + + @Override public void onNavigationChanged(@MainMvp.NavigationType int navType) { + //noinspection WrongConstant + if (bottomNavigation.getSelectedIndex() != navType) bottomNavigation.setSelectedIndex(navType, true); + this.navType = navType; + hideShowShadow(navType != MainMvp.PROFILE); + onHideShowFab(); + getPresenter().onModuleChanged(getSupportFragmentManager(), navType); + } + + @Override public void onOpenDrawer() { + if (drawerLayout != null && !drawerLayout.isDrawerOpen(GravityCompat.START)) drawerLayout.openDrawer(GravityCompat.START); + } + + @Override public void onCloseDrawer() { + if (drawerLayout != null && drawerLayout.isDrawerOpen(GravityCompat.START)) drawerLayout.closeDrawer(GravityCompat.START); + } + + @Override public void onOpenSettings() { + + } + + @Override public void onHideShowFab() { + if (navType == MainMvp.GISTS) { + fab.show(); + } else { + fab.hide(); + } + } + + private boolean canExit() { + if (backPressTimer + 2000 > System.currentTimeMillis()) { + return true; + } else { + Toast.makeText(getBaseContext(), R.string.press_again_to_exit, Toast.LENGTH_SHORT).show(); + } + backPressTimer = System.currentTimeMillis(); + return false; + } + + private void onInit(@Nullable Bundle savedInstanceState) { + if (isLoggedIn()) { + navigationView.setNavigationItemSelectedListener(getPresenter()); + Typeface myTypeface = TypeFaceHelper.getTypeface(); + bottomNavigation.setDefaultTypeface(myTypeface); + bottomNavigation.setOnMenuItemClickListener(getPresenter()); + if (savedInstanceState == null) { + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.container, FeedsView.newInstance(), FeedsView.TAG) + .commit(); + bottomNavigation.setDefaultSelectedIndex(0); + } + LoginModel userModel = LoginModel.getUser(); + if (userModel != null) { + View view = navigationView.getHeaderView(0); + if (view != null) { + ((AvatarLayout) view.findViewById(R.id.avatarLayout)).setUrl(userModel.getAvatarUrl(), userModel.getLogin()); + ((TextView) view.findViewById(R.id.username)).setText(userModel.getName()); + ((TextView) view.findViewById(R.id.email)).setText(userModel.getLogin()); + } + } + } + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/parser/LinksParserActivity.java b/app/src/main/java/com/fastaccess/ui/modules/parser/LinksParserActivity.java new file mode 100644 index 00000000..ac4bdfae --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/parser/LinksParserActivity.java @@ -0,0 +1,50 @@ +package com.fastaccess.ui.modules.parser; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.widget.Toast; + +import com.fastaccess.R; +import com.fastaccess.data.dao.LoginModel; +import com.fastaccess.provider.scheme.SchemeParser; + +/** + * Created by Kosh on 09 Dec 2016, 12:31 PM + */ + +public class LinksParserActivity extends Activity { + + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (LoginModel.getUser() == null) { + Toast.makeText(this, R.string.please_login, Toast.LENGTH_SHORT).show(); + finish(); + return; + } + onCreate(getIntent()); + } + + @Override protected void onStart() { + super.onStart(); + setVisible(true); + } + + private void onCreate(Intent intent) { + if (intent == null || intent.getAction() == null) { + finish(); + return; + } + if (intent.getAction().equals(Intent.ACTION_VIEW)) { + onUriReceived(); + } else { + finish(); + } + } + + private void onUriReceived() { + SchemeParser.launchUri(this, getIntent()); + finish(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/profile/ProfilePagerMvp.java b/app/src/main/java/com/fastaccess/ui/modules/profile/ProfilePagerMvp.java new file mode 100644 index 00000000..81b566f6 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/profile/ProfilePagerMvp.java @@ -0,0 +1,14 @@ +package com.fastaccess.ui.modules.profile; + +import com.fastaccess.ui.base.mvp.BaseMvp; + +/** + * Created by Kosh on 03 Dec 2016, 7:59 AM + */ + +interface ProfilePagerMvp { + + interface View extends BaseMvp.FAView {} + + interface Presenter extends BaseMvp.FAPresenter {} +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/profile/ProfilePagerPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/profile/ProfilePagerPresenter.java new file mode 100644 index 00000000..0710cbce --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/profile/ProfilePagerPresenter.java @@ -0,0 +1,9 @@ +package com.fastaccess.ui.modules.profile; + +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; + +/** + * Created by Kosh on 03 Dec 2016, 8:00 AM + */ + +class ProfilePagerPresenter extends BasePresenter implements ProfilePagerMvp.Presenter {} diff --git a/app/src/main/java/com/fastaccess/ui/modules/profile/ProfilePagerView.java b/app/src/main/java/com/fastaccess/ui/modules/profile/ProfilePagerView.java new file mode 100644 index 00000000..6025a722 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/profile/ProfilePagerView.java @@ -0,0 +1,59 @@ +package com.fastaccess.ui.modules.profile; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.TabLayout; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.data.dao.FragmentPagerAdapterModel; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.ui.adapter.FragmentsPagerAdapter; +import com.fastaccess.ui.base.BaseFragment; +import com.fastaccess.ui.widgets.ViewPagerView; + +import butterknife.BindView; + +/** + * Created by Kosh on 03 Dec 2016, 8:00 AM + */ + +public class ProfilePagerView extends BaseFragment implements ProfilePagerMvp.View { + + public static final String TAG = ProfilePagerView.class.getSimpleName(); + + @BindView(R.id.tabs) TabLayout tabs; + @BindView(R.id.pager) ViewPagerView pager; + + public static ProfilePagerView newInstance(@NonNull String login) { + ProfilePagerView profileView = new ProfilePagerView(); + profileView.setArguments(Bundler.start().put(BundleConstant.EXTRA, login).end()); + return profileView; + } + + @Override protected int fragmentLayout() { + return R.layout.tabbed_viewpager; + } + + @Override protected void onFragmentCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + if (getArguments() == null) { + throw new RuntimeException("Bundle is null?"); + } + String login = getArguments().getString(BundleConstant.EXTRA); + if (login == null) { + throw new RuntimeException("user is null?"); + } + FragmentsPagerAdapter adapter = new FragmentsPagerAdapter(getChildFragmentManager(), + FragmentPagerAdapterModel.buildForProfile(getContext(), login)); + tabs.setTabGravity(TabLayout.GRAVITY_FILL); + tabs.setTabMode(TabLayout.MODE_SCROLLABLE); + pager.setAdapter(adapter); + tabs.setupWithViewPager(pager); + } + + @NonNull @Override public ProfilePagerPresenter providePresenter() { + return new ProfilePagerPresenter(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/profile/followers/ProfileFollowersMvp.java b/app/src/main/java/com/fastaccess/ui/modules/profile/followers/ProfileFollowersMvp.java new file mode 100644 index 00000000..1659ec08 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/profile/followers/ProfileFollowersMvp.java @@ -0,0 +1,34 @@ + +package com.fastaccess.ui.modules.profile.followers; + +import android.support.annotation.NonNull; +import android.support.v4.widget.SwipeRefreshLayout; + +import com.fastaccess.data.dao.UserModel; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.ArrayList; + +/** + * Created by Kosh on 03 Dec 2016, 3:45 PM + */ + +interface ProfileFollowersMvp { + + interface View extends BaseMvp.FAView, SwipeRefreshLayout.OnRefreshListener, android.view.View.OnClickListener { + void onNotifyAdapter(); + + @NonNull OnLoadMore getLoadMore(); + } + + interface Presenter extends BaseMvp.FAPresenter, + BaseViewHolder.OnItemClickListener, + BaseMvp.PaginationListener { + + @NonNull ArrayList getFollowers(); + + void onWorkOffline(@NonNull String login); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/profile/followers/ProfileFollowersPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/profile/followers/ProfileFollowersPresenter.java new file mode 100644 index 00000000..89a61438 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/profile/followers/ProfileFollowersPresenter.java @@ -0,0 +1,94 @@ +package com.fastaccess.ui.modules.profile.followers; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; + +import com.fastaccess.data.dao.UserModel; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; + +import java.util.ArrayList; + +import rx.Observable; + +/** + * Created by Kosh on 03 Dec 2016, 3:48 PM + */ + +class ProfileFollowersPresenter extends BasePresenter implements ProfileFollowersMvp.Presenter { + + private ArrayList users = new ArrayList<>(); + private int page; + private int previousTotal; + private int lastPage = Integer.MAX_VALUE; + + @Override public int getCurrentPage() { + return page; + } + + @Override public int getPreviousTotal() { + return previousTotal; + } + + @Override public void setCurrentPage(int page) { + this.page = page; + } + + @Override public void setPreviousTotal(int previousTotal) { + this.previousTotal = previousTotal; + } + + @Override public T onError(@NonNull Throwable throwable, @NonNull Observable observable) { + sendToView(view -> { + if (view.getLoadMore().getParameter() != null) { + onWorkOffline(view.getLoadMore().getParameter()); + } + }); + return super.onError(throwable, observable); + } + + @Override public void onCallApi(int page, @Nullable String parameter) { + if (parameter == null) { + throw new NullPointerException("Username is null"); + } + if (page == 1) { + lastPage = Integer.MAX_VALUE; + sendToView(view -> view.getLoadMore().reset()); + } + setCurrentPage(page); + if (page > lastPage || lastPage == 0) { + sendToView(ProfileFollowersMvp.View::hideProgress); + return; + } + makeRestCall(RestProvider.getUserService().getFollowers(parameter, page), + response -> { + lastPage = response.getLast(); + if (getCurrentPage() == 1) { + users.clear(); + manageSubscription(UserModel.saveFollowers(response.getItems(), parameter).subscribe()); + } + users.addAll(response.getItems()); + sendToView(ProfileFollowersMvp.View::onNotifyAdapter); + }); + } + + @NonNull @Override public ArrayList getFollowers() { + return users; + } + + @Override public void onWorkOffline(@NonNull String login) { + if (users.isEmpty()) { + manageSubscription(UserModel.getFollowers(login).subscribe(userModels -> { + users.addAll(userModels); + sendToView(ProfileFollowersMvp.View::onNotifyAdapter); + })); + } else { + sendToView(ProfileFollowersMvp.View::hideProgress); + } + } + + @Override public void onItemClick(int position, View v, UserModel item) {} + + @Override public void onItemLongClick(int position, View v, UserModel item) {} +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/profile/followers/ProfileFollowersView.java b/app/src/main/java/com/fastaccess/ui/modules/profile/followers/ProfileFollowersView.java new file mode 100644 index 00000000..6c9ddde3 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/profile/followers/ProfileFollowersView.java @@ -0,0 +1,101 @@ +package com.fastaccess.ui.modules.profile.followers; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.v4.widget.SwipeRefreshLayout; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.helper.Logger; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.adapter.UsersAdapter; +import com.fastaccess.ui.base.BaseFragment; +import com.fastaccess.ui.widgets.StateLayout; +import com.fastaccess.ui.widgets.recyclerview.DynamicRecyclerView; + +import butterknife.BindView; + +/** + * Created by Kosh on 03 Dec 2016, 3:56 PM + */ + +public class ProfileFollowersView extends BaseFragment implements ProfileFollowersMvp.View { + + @BindView(R.id.recycler) DynamicRecyclerView recycler; + @BindView(R.id.refresh) SwipeRefreshLayout refresh; + @BindView(R.id.stateLayout) StateLayout stateLayout; + private OnLoadMore onLoadMore; + private UsersAdapter adapter; + + public static ProfileFollowersView newInstance(@NonNull String username) { + ProfileFollowersView view = new ProfileFollowersView(); + view.setArguments(Bundler.start().put(BundleConstant.EXTRA, username).end()); + return view; + } + + @Override public void onNotifyAdapter() { + + hideProgress(); + adapter.notifyDataSetChanged(); + } + + @Override protected int fragmentLayout() { + return R.layout.small_grid_refresh_list; + } + + @Override protected void onFragmentCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + if (getArguments() == null) { + throw new NullPointerException("Bundle is null, username is required"); + } + stateLayout.setOnReloadListener(this); + refresh.setOnRefreshListener(this); + recycler.setEmptyView(stateLayout, refresh); + getLoadMore().setCurrent_page(getPresenter().getCurrentPage(), getPresenter().getPreviousTotal()); + adapter = new UsersAdapter(getPresenter().getFollowers()); + adapter.setListener(getPresenter()); + recycler.setAdapter(adapter); + recycler.addOnScrollListener(getLoadMore()); + if (getPresenter().getFollowers().isEmpty() && !getPresenter().isApiCalled()) { + onRefresh(); + } + } + + @NonNull @Override public ProfileFollowersPresenter providePresenter() { + return new ProfileFollowersPresenter(); + } + + @Override public void showProgress(@StringRes int resId) { + refresh.setRefreshing(true); + stateLayout.showProgress(); + } + + @Override public void hideProgress() { + refresh.setRefreshing(false); + stateLayout.hideProgress(); + } + + @Override public void showErrorMessage(@NonNull String msgRes) { + hideProgress(); + stateLayout.showReload(adapter.getItemCount()); + super.showErrorMessage(msgRes); + } + + @NonNull @Override public OnLoadMore getLoadMore() { + if (onLoadMore == null) { + onLoadMore = new OnLoadMore<>(getPresenter(), getArguments().getString(BundleConstant.EXTRA)); + } + return onLoadMore; + } + + @Override public void onRefresh() { + getPresenter().onCallApi(1, getArguments().getString(BundleConstant.EXTRA)); + } + + @Override public void onClick(View view) { + onRefresh(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/profile/following/ProfileFollowingMvp.java b/app/src/main/java/com/fastaccess/ui/modules/profile/following/ProfileFollowingMvp.java new file mode 100644 index 00000000..712ab970 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/profile/following/ProfileFollowingMvp.java @@ -0,0 +1,34 @@ + +package com.fastaccess.ui.modules.profile.following; + +import android.support.annotation.NonNull; +import android.support.v4.widget.SwipeRefreshLayout; + +import com.fastaccess.data.dao.UserModel; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.ArrayList; + +/** + * Created by Kosh on 03 Dec 2016, 3:45 PM + */ + +interface ProfileFollowingMvp { + + interface View extends BaseMvp.FAView, SwipeRefreshLayout.OnRefreshListener, android.view.View.OnClickListener { + void onNotifyAdapter(); + + @NonNull OnLoadMore getLoadMore(); + } + + interface Presenter extends BaseMvp.FAPresenter, + BaseViewHolder.OnItemClickListener, + BaseMvp.PaginationListener { + + @NonNull ArrayList getFollowing(); + + void onWorkOffline(@NonNull String login); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/profile/following/ProfileFollowingPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/profile/following/ProfileFollowingPresenter.java new file mode 100644 index 00000000..d70c7a61 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/profile/following/ProfileFollowingPresenter.java @@ -0,0 +1,94 @@ +package com.fastaccess.ui.modules.profile.following; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; + +import com.fastaccess.data.dao.UserModel; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; + +import java.util.ArrayList; + +import rx.Observable; + +/** + * Created by Kosh on 03 Dec 2016, 3:48 PM + */ + +class ProfileFollowingPresenter extends BasePresenter implements ProfileFollowingMvp.Presenter { + + private ArrayList users = new ArrayList<>(); + private int page; + private int previousTotal; + private int lastPage = Integer.MAX_VALUE; + + @Override public int getCurrentPage() { + return page; + } + + @Override public int getPreviousTotal() { + return previousTotal; + } + + @Override public void setCurrentPage(int page) { + this.page = page; + } + + @Override public void setPreviousTotal(int previousTotal) { + this.previousTotal = previousTotal; + } + + @Override public T onError(@NonNull Throwable throwable, @NonNull Observable observable) { + sendToView(view -> {//wait view + if (view.getLoadMore().getParameter() != null) { + onWorkOffline(view.getLoadMore().getParameter()); + } + }); + return super.onError(throwable, observable); + } + + @Override public void onCallApi(int page, @Nullable String parameter) { + if (parameter == null) { + throw new NullPointerException("Username is null"); + } + if (page == 1) { + lastPage = Integer.MAX_VALUE; + sendToView(view -> view.getLoadMore().reset()); + } + setCurrentPage(page); + if (page > lastPage || lastPage == 0) { + sendToView(ProfileFollowingMvp.View::hideProgress); + return; + } + makeRestCall(RestProvider.getUserService().getFollowing(parameter, page), + response -> { + lastPage = response.getLast(); + if (getCurrentPage() == 1) { + users.clear(); + manageSubscription(UserModel.saveFollowings(response.getItems(), parameter).subscribe()); + } + users.addAll(response.getItems()); + sendToView(ProfileFollowingMvp.View::onNotifyAdapter); + }); + } + + @NonNull @Override public ArrayList getFollowing() { + return users; + } + + @Override public void onWorkOffline(@NonNull String login) { + if (users.isEmpty()) { + manageSubscription(UserModel.getFollowing(login).subscribe(userModels -> { + users.addAll(userModels); + sendToView(ProfileFollowingMvp.View::onNotifyAdapter); + })); + } else { + sendToView(ProfileFollowingMvp.View::hideProgress); + } + } + + @Override public void onItemClick(int position, View v, UserModel item) {} + + @Override public void onItemLongClick(int position, View v, UserModel item) {} +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/profile/following/ProfileFollowingView.java b/app/src/main/java/com/fastaccess/ui/modules/profile/following/ProfileFollowingView.java new file mode 100644 index 00000000..7b3227ac --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/profile/following/ProfileFollowingView.java @@ -0,0 +1,101 @@ +package com.fastaccess.ui.modules.profile.following; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.v4.widget.SwipeRefreshLayout; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.helper.Logger; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.adapter.UsersAdapter; +import com.fastaccess.ui.base.BaseFragment; +import com.fastaccess.ui.widgets.StateLayout; +import com.fastaccess.ui.widgets.recyclerview.DynamicRecyclerView; + +import butterknife.BindView; + +/** + * Created by Kosh on 03 Dec 2016, 3:56 PM + */ + +public class ProfileFollowingView extends BaseFragment implements ProfileFollowingMvp.View { + + @BindView(R.id.recycler) DynamicRecyclerView recycler; + @BindView(R.id.refresh) SwipeRefreshLayout refresh; + @BindView(R.id.stateLayout) StateLayout stateLayout; + private OnLoadMore onLoadMore; + private UsersAdapter adapter; + + public static ProfileFollowingView newInstance(@NonNull String username) { + ProfileFollowingView view = new ProfileFollowingView(); + view.setArguments(Bundler.start().put(BundleConstant.EXTRA, username).end()); + return view; + } + + @Override public void onNotifyAdapter() { + + hideProgress(); + adapter.notifyDataSetChanged(); + } + + @Override protected int fragmentLayout() { + return R.layout.small_grid_refresh_list; + } + + @Override protected void onFragmentCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + if (getArguments() == null) { + throw new NullPointerException("Bundle is null, username is required"); + } + stateLayout.setOnReloadListener(this); + refresh.setOnRefreshListener(this); + recycler.setEmptyView(stateLayout, refresh); + getLoadMore().setCurrent_page(getPresenter().getCurrentPage(), getPresenter().getPreviousTotal()); + adapter = new UsersAdapter(getPresenter().getFollowing()); + adapter.setListener(getPresenter()); + recycler.setAdapter(adapter); + recycler.addOnScrollListener(getLoadMore()); + if (getPresenter().getFollowing().isEmpty() && !getPresenter().isApiCalled()) { + onRefresh(); + } + } + + @NonNull @Override public ProfileFollowingPresenter providePresenter() { + return new ProfileFollowingPresenter(); + } + + @Override public void showProgress(@StringRes int resId) { + refresh.setRefreshing(true); + stateLayout.showProgress(); + } + + @Override public void hideProgress() { + refresh.setRefreshing(false); + stateLayout.hideProgress(); + } + + @Override public void showErrorMessage(@NonNull String msgRes) { + hideProgress(); + stateLayout.showReload(adapter.getItemCount()); + super.showErrorMessage(msgRes); + } + + @NonNull @Override public OnLoadMore getLoadMore() { + if (onLoadMore == null) { + onLoadMore = new OnLoadMore<>(getPresenter(), getArguments().getString(BundleConstant.EXTRA)); + } + return onLoadMore; + } + + @Override public void onRefresh() { + getPresenter().onCallApi(1, getArguments().getString(BundleConstant.EXTRA)); + } + + @Override public void onClick(View view) { + onRefresh(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/profile/gists/ProfileGistsMvp.java b/app/src/main/java/com/fastaccess/ui/modules/profile/gists/ProfileGistsMvp.java new file mode 100644 index 00000000..34ae1b5f --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/profile/gists/ProfileGistsMvp.java @@ -0,0 +1,33 @@ +package com.fastaccess.ui.modules.profile.gists; + +import android.support.annotation.NonNull; +import android.support.v4.widget.SwipeRefreshLayout; + +import com.fastaccess.data.dao.GistsModel; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.ArrayList; + +/** + * Created by Kosh on 11 Nov 2016, 12:35 PM + */ + +interface ProfileGistsMvp { + interface View extends BaseMvp.FAView, SwipeRefreshLayout.OnRefreshListener, android.view.View.OnClickListener { + + void onNotifyAdapter(); + + @NonNull OnLoadMore getLoadMore(); + } + + interface Presenter extends BaseMvp.FAPresenter, + BaseViewHolder.OnItemClickListener, + BaseMvp.PaginationListener { + + @NonNull ArrayList getGists(); + + void onWorkOffline(@NonNull String login); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/profile/gists/ProfileGistsPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/profile/gists/ProfileGistsPresenter.java new file mode 100644 index 00000000..ce5e2878 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/profile/gists/ProfileGistsPresenter.java @@ -0,0 +1,98 @@ +package com.fastaccess.ui.modules.profile.gists; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; + +import com.fastaccess.data.dao.GistsModel; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; +import com.fastaccess.ui.modules.gists.gist.GistView; + +import java.util.ArrayList; + +import rx.Observable; + +/** + * Created by Kosh on 11 Nov 2016, 12:36 PM + */ + +class ProfileGistsPresenter extends BasePresenter implements ProfileGistsMvp.Presenter { + private ArrayList gistsModels = new ArrayList<>(); + private int page; + private int previousTotal; + private int lastPage = Integer.MAX_VALUE; + + @Override public int getCurrentPage() { + return page; + } + + @Override public int getPreviousTotal() { + return previousTotal; + } + + @Override public void setCurrentPage(int page) { + this.page = page; + } + + @Override public void setPreviousTotal(int previousTotal) { + this.previousTotal = previousTotal; + } + + @Override public T onError(@NonNull Throwable throwable, @NonNull Observable observable) { + sendToView(view -> { + if (view.getLoadMore().getParameter() != null) { + onWorkOffline(view.getLoadMore().getParameter()); + } + }); + return super.onError(throwable, observable); + } + + @Override public void onCallApi(int page, @Nullable String parameter) { + if (parameter == null) { + throw new NullPointerException("Username is null"); + } + if (page == 1) { + lastPage = Integer.MAX_VALUE; + sendToView(view -> view.getLoadMore().reset()); + } + setCurrentPage(page); + if (page > lastPage || lastPage == 0) { + sendToView(ProfileGistsMvp.View::hideProgress); + return; + } + makeRestCall(RestProvider.getGistService().getUserGists(parameter, RestProvider.PAGE_SIZE, page), + listResponse -> { + lastPage = listResponse.getLast(); + if (getCurrentPage() == 1) { + getGists().clear(); + manageSubscription(GistsModel.save(listResponse.getItems(), parameter).subscribe()); + } + getGists().addAll(listResponse.getItems()); + sendToView(ProfileGistsMvp.View::onNotifyAdapter); + }); + } + + @NonNull @Override public ArrayList getGists() { + return gistsModels; + } + + @Override public void onWorkOffline(@NonNull String login) { + if (gistsModels.isEmpty()) { + manageSubscription(GistsModel.getMyGists(login).subscribe(gistsModels1 -> { + gistsModels.addAll(gistsModels1); + sendToView(ProfileGistsMvp.View::onNotifyAdapter); + })); + } else { + sendToView(ProfileGistsMvp.View::hideProgress); + } + } + + @Override public void onItemClick(int position, View v, GistsModel item) { + v.getContext().startActivity(GistView.createIntent(v.getContext(), item.getGistId())); + } + + @Override public void onItemLongClick(int position, View v, GistsModel item) { + onItemClick(position, v, item); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/profile/gists/ProfileGistsView.java b/app/src/main/java/com/fastaccess/ui/modules/profile/gists/ProfileGistsView.java new file mode 100644 index 00000000..463704d9 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/profile/gists/ProfileGistsView.java @@ -0,0 +1,107 @@ +package com.fastaccess.ui.modules.profile.gists; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.v4.widget.SwipeRefreshLayout; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.helper.Logger; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.adapter.GistsAdapter; +import com.fastaccess.ui.base.BaseFragment; +import com.fastaccess.ui.widgets.StateLayout; +import com.fastaccess.ui.widgets.recyclerview.DynamicRecyclerView; + +import butterknife.BindView; + +/** + * Created by Kosh on 11 Nov 2016, 12:36 PM + */ + +public class ProfileGistsView extends BaseFragment implements ProfileGistsMvp.View { + + @BindView(R.id.recycler) DynamicRecyclerView recycler; + @BindView(R.id.refresh) SwipeRefreshLayout refresh; + @BindView(R.id.stateLayout) StateLayout stateLayout; + + private GistsAdapter adapter; + private OnLoadMore onLoadMore; + + public static ProfileGistsView newInstance(@NonNull String login) { + ProfileGistsView view = new ProfileGistsView(); + view.setArguments(Bundler.start().put(BundleConstant.EXTRA, login).end()); + return view; + } + + @Override protected int fragmentLayout() { + return R.layout.small_grid_refresh_list; + } + + @Override protected void onFragmentCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + if (getArguments().getString(BundleConstant.EXTRA) == null) { + throw new NullPointerException("Username is null"); + } + refresh.setOnRefreshListener(this); + stateLayout.setOnReloadListener(this); + recycler.setEmptyView(stateLayout, refresh); + adapter = new GistsAdapter(getPresenter().getGists(), true); + adapter.setListener(getPresenter()); + getLoadMore().setCurrent_page(getPresenter().getCurrentPage(), getPresenter().getPreviousTotal()); + recycler.setAdapter(adapter); + recycler.addOnScrollListener(getLoadMore()); + if (getPresenter().getGists().isEmpty() && !getPresenter().isApiCalled()) { + onRefresh(); + } + } + + @Override public void onRefresh() { + getPresenter().onCallApi(1, getArguments().getString(BundleConstant.EXTRA)); + } + + @Override public void onNotifyAdapter() { + + hideProgress(); + adapter.notifyDataSetChanged(); + } + + @Override public void showProgress(@StringRes int resId) { + refresh.setRefreshing(true); + stateLayout.showProgress(); + } + + @Override public void hideProgress() { + refresh.setRefreshing(false); + stateLayout.hideProgress(); + } + + @Override public void showErrorMessage(@NonNull String msgRes) { + hideProgress(); + stateLayout.showReload(adapter.getItemCount()); + super.showErrorMessage(msgRes); + } + + @NonNull @Override public ProfileGistsPresenter providePresenter() { + return new ProfileGistsPresenter(); + } + + @NonNull @Override public OnLoadMore getLoadMore() { + if (onLoadMore == null) { + onLoadMore = new OnLoadMore<>(getPresenter(), getArguments().getString(BundleConstant.EXTRA)); + } + return onLoadMore; + } + + @Override public void onDestroyView() { + recycler.removeOnScrollListener(getLoadMore()); + super.onDestroyView(); + } + + @Override public void onClick(View view) { + onRefresh(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/profile/overview/ProfileOverviewMvp.java b/app/src/main/java/com/fastaccess/ui/modules/profile/overview/ProfileOverviewMvp.java new file mode 100644 index 00000000..d0383879 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/profile/overview/ProfileOverviewMvp.java @@ -0,0 +1,27 @@ +package com.fastaccess.ui.modules.profile.overview; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.fastaccess.data.dao.UserModel; +import com.fastaccess.ui.base.mvp.BaseMvp; + +/** + * Created by Kosh on 03 Dec 2016, 9:15 AM + */ + +interface ProfileOverviewMvp { + + interface View extends BaseMvp.FAView { + void onInitViews(@Nullable UserModel userModel); + } + + interface Presenter extends BaseMvp.FAPresenter { + void onFragmentCreated(@Nullable Bundle bundle); + + void onWorkOffline(@NonNull String login); + + void onSendUserToView(@Nullable UserModel userModel); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/profile/overview/ProfileOverviewPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/profile/overview/ProfileOverviewPresenter.java new file mode 100644 index 00000000..eca7422c --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/profile/overview/ProfileOverviewPresenter.java @@ -0,0 +1,55 @@ +package com.fastaccess.ui.modules.profile.overview; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.fastaccess.data.dao.UserModel; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; + +import rx.Observable; + +/** + * Created by Kosh on 03 Dec 2016, 9:16 AM + */ + +class ProfileOverviewPresenter extends BasePresenter implements ProfileOverviewMvp.Presenter { + + private String login; + + @Override public T onError(@NonNull Throwable throwable, @NonNull Observable observable) { + if (!InputHelper.isEmpty(login)) { + onWorkOffline(login); + } + return super.onError(throwable, observable); + } + + @Override public void onFragmentCreated(@Nullable Bundle bundle) { + if (bundle == null || bundle.getString(BundleConstant.EXTRA) == null) { + throw new NullPointerException("Either bundle or User is null"); + } + login = bundle.getString(BundleConstant.EXTRA); + if (login != null) { + makeRestCall(RestProvider.getUserService().getUser(login), + userModel -> { + onSendUserToView(userModel); + if (userModel != null) { + userModel.save(); + } + }); + } + } + + @Override public void onWorkOffline(@NonNull String login) { + UserModel userModel = UserModel.getUser(login); + if (userModel == null) return; + onSendUserToView(userModel); + } + + @Override public void onSendUserToView(@Nullable UserModel userModel) { + sendToView(view -> view.onInitViews(userModel)); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/ui/modules/profile/overview/ProfileOverviewView.java b/app/src/main/java/com/fastaccess/ui/modules/profile/overview/ProfileOverviewView.java new file mode 100644 index 00000000..7f209faf --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/profile/overview/ProfileOverviewView.java @@ -0,0 +1,100 @@ +package com.fastaccess.ui.modules.profile.overview; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.data.dao.UserModel; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.helper.ParseDateFormat; +import com.fastaccess.ui.base.BaseFragment; +import com.fastaccess.ui.widgets.AvatarLayout; +import com.fastaccess.ui.widgets.FontTextView; +import com.fastaccess.ui.widgets.SpannableBuilder; + +import butterknife.BindView; +import icepick.State; + +/** + * Created by Kosh on 03 Dec 2016, 9:16 AM + */ + +public class ProfileOverviewView extends BaseFragment implements ProfileOverviewMvp.View { + + @BindView(R.id.username) FontTextView username; + @BindView(R.id.description) FontTextView description; + @BindView(R.id.avatarLayout) AvatarLayout avatarLayout; + @BindView(R.id.organization) FontTextView organization; + @BindView(R.id.location) FontTextView location; + @BindView(R.id.email) FontTextView email; + @BindView(R.id.link) FontTextView link; + @BindView(R.id.joined) FontTextView joined; + @BindView(R.id.following) FontTextView following; + @BindView(R.id.followers) FontTextView followers; + @BindView(R.id.progress) View progress; + + @State UserModel userModel; + + public static ProfileOverviewView newInstance(String login) { + ProfileOverviewView view = new ProfileOverviewView(); + view.setArguments(Bundler.start().put(BundleConstant.EXTRA, login).end()); + return view; + } + + @Override protected int fragmentLayout() { + return R.layout.profile_overview_layout; + } + + @Override protected void onFragmentCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + if (savedInstanceState == null) { + getPresenter().onFragmentCreated(getArguments()); + } else { + if (userModel != null) onInitViews(userModel); + else getPresenter().onFragmentCreated(getArguments()); + } + } + + @NonNull @Override public ProfileOverviewPresenter providePresenter() { + return new ProfileOverviewPresenter(); + } + + @Override public void onInitViews(@Nullable UserModel userModel) { + progress.setVisibility(View.GONE); + if (userModel == null) return; + this.userModel = userModel; + username.setText(userModel.getLogin()); + description.setText(userModel.getBio()); + avatarLayout.setUrl(userModel.getAvatarUrl(), null); + organization.setText(InputHelper.toNA(userModel.getCompany())); + location.setText(InputHelper.toNA(userModel.getLocation())); + email.setText(InputHelper.toNA(userModel.getEmail())); + link.setText(InputHelper.toNA(userModel.getBlog())); + joined.setText(userModel.getCreatedAt() != null ? ParseDateFormat.getTimeAgo(userModel.getCreatedAt()) : "N/A"); + followers.setText(SpannableBuilder.builder() + .append(getString(R.string.followers)) + .append("\n") + .bold(String.valueOf(userModel.getFollowers()))); + following.setText(SpannableBuilder.builder() + .append(getString(R.string.following)) + .append("\n") + .bold(String.valueOf(userModel.getFollowing()))); + } + + @Override public void showProgress(@StringRes int resId) { + progress.setVisibility(View.VISIBLE); + } + + @Override public void hideProgress() { + progress.setVisibility(View.GONE); + } + + @Override public void showErrorMessage(@NonNull String msgRes) { + hideProgress(); + super.showErrorMessage(msgRes); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/profile/repos/ProfileReposMvp.java b/app/src/main/java/com/fastaccess/ui/modules/profile/repos/ProfileReposMvp.java new file mode 100644 index 00000000..0e7606ae --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/profile/repos/ProfileReposMvp.java @@ -0,0 +1,33 @@ +package com.fastaccess.ui.modules.profile.repos; + +import android.support.annotation.NonNull; +import android.support.v4.widget.SwipeRefreshLayout; + +import com.fastaccess.data.dao.RepoModel; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.ArrayList; + +/** + * Created by Kosh on 03 Dec 2016, 3:45 PM + */ + +interface ProfileReposMvp { + + interface View extends BaseMvp.FAView, SwipeRefreshLayout.OnRefreshListener, android.view.View.OnClickListener { + void onNotifyAdapter(); + + @NonNull OnLoadMore getLoadMore(); + } + + interface Presenter extends BaseMvp.FAPresenter, + BaseViewHolder.OnItemClickListener, + BaseMvp.PaginationListener { + + @NonNull ArrayList getRepos(); + + void onWorkOffline(@NonNull String login); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/profile/repos/ProfileReposPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/profile/repos/ProfileReposPresenter.java new file mode 100644 index 00000000..45821e5d --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/profile/repos/ProfileReposPresenter.java @@ -0,0 +1,100 @@ +package com.fastaccess.ui.modules.profile.repos; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; + +import com.fastaccess.data.dao.NameParser; +import com.fastaccess.data.dao.RepoModel; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; +import com.fastaccess.ui.modules.repos.RepoPagerView; + +import java.util.ArrayList; + +import rx.Observable; + +/** + * Created by Kosh on 03 Dec 2016, 3:48 PM + */ + +class ProfileReposPresenter extends BasePresenter implements ProfileReposMvp.Presenter { + + private ArrayList repos = new ArrayList<>(); + private int page; + private int previousTotal; + private int lastPage = Integer.MAX_VALUE; + + @Override public int getCurrentPage() { + return page; + } + + @Override public int getPreviousTotal() { + return previousTotal; + } + + @Override public void setCurrentPage(int page) { + this.page = page; + } + + @Override public void setPreviousTotal(int previousTotal) { + this.previousTotal = previousTotal; + } + + @Override public T onError(@NonNull Throwable throwable, @NonNull Observable observable) { + sendToView(view -> { + if (view.getLoadMore().getParameter() != null) { + onWorkOffline(view.getLoadMore().getParameter()); + } + }); + return super.onError(throwable, observable); + } + + @Override public void onCallApi(int page, @Nullable String parameter) { + if (parameter == null) { + throw new NullPointerException("Username is null"); + } + if (page == 1) { + lastPage = Integer.MAX_VALUE; + sendToView(view -> view.getLoadMore().reset()); + } + setCurrentPage(page); + if (page > lastPage || lastPage == 0) { + sendToView(ProfileReposMvp.View::hideProgress); + return; + } + makeRestCall(RestProvider.getUserService().getRepos(parameter, page), + repoModelPageable -> { + lastPage = repoModelPageable.getLast(); + if (getCurrentPage() == 1) { + getRepos().clear(); + manageSubscription(RepoModel.saveMyRepos(repoModelPageable.getItems(), parameter).subscribe()); + } + getRepos().addAll(repoModelPageable.getItems()); + sendToView(ProfileReposMvp.View::onNotifyAdapter); + }); + } + + @NonNull @Override public ArrayList getRepos() { + return repos; + } + + @Override public void onWorkOffline(@NonNull String login) { + if (repos.isEmpty()) { + manageSubscription(RepoModel.getMyRepos(login).subscribe(repoModels -> { + repos.addAll(repoModels); + sendToView(ProfileReposMvp.View::onNotifyAdapter); + })); + } else { + sendToView(ProfileReposMvp.View::hideProgress); + } + } + + @Override public void onItemClick(int position, View v, RepoModel item) { + RepoPagerView.startRepoPager(v.getContext(), new NameParser(item.getHtmlUrl())); + } + + @Override public void onItemLongClick(int position, View v, RepoModel item) { + onItemClick(position, v, item); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/profile/repos/ProfileReposView.java b/app/src/main/java/com/fastaccess/ui/modules/profile/repos/ProfileReposView.java new file mode 100644 index 00000000..49e31982 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/profile/repos/ProfileReposView.java @@ -0,0 +1,101 @@ +package com.fastaccess.ui.modules.profile.repos; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.v4.widget.SwipeRefreshLayout; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.helper.Logger; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.adapter.ReposAdapter; +import com.fastaccess.ui.base.BaseFragment; +import com.fastaccess.ui.widgets.StateLayout; +import com.fastaccess.ui.widgets.recyclerview.DynamicRecyclerView; + +import butterknife.BindView; + +/** + * Created by Kosh on 03 Dec 2016, 3:56 PM + */ + +public class ProfileReposView extends BaseFragment implements ProfileReposMvp.View { + + @BindView(R.id.recycler) DynamicRecyclerView recycler; + @BindView(R.id.refresh) SwipeRefreshLayout refresh; + @BindView(R.id.stateLayout) StateLayout stateLayout; + private OnLoadMore onLoadMore; + private ReposAdapter adapter; + + public static ProfileReposView newInstance(@NonNull String username) { + ProfileReposView view = new ProfileReposView(); + view.setArguments(Bundler.start().put(BundleConstant.EXTRA, username).end()); + return view; + } + + @Override public void onNotifyAdapter() { + + hideProgress(); + adapter.notifyDataSetChanged(); + } + + @Override protected int fragmentLayout() { + return R.layout.small_grid_refresh_list; + } + + @Override protected void onFragmentCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + if (getArguments() == null) { + throw new NullPointerException("Bundle is null, username is required"); + } + stateLayout.setOnReloadListener(this); + refresh.setOnRefreshListener(this); + recycler.setEmptyView(stateLayout, refresh); + getLoadMore().setCurrent_page(getPresenter().getCurrentPage(), getPresenter().getPreviousTotal()); + adapter = new ReposAdapter(getPresenter().getRepos(), false); + adapter.setListener(getPresenter()); + recycler.setAdapter(adapter); + recycler.addOnScrollListener(getLoadMore()); + if (getPresenter().getRepos().isEmpty() && !getPresenter().isApiCalled()) { + onRefresh(); + } + } + + @NonNull @Override public ProfileReposPresenter providePresenter() { + return new ProfileReposPresenter(); + } + + @Override public void showProgress(@StringRes int resId) { + refresh.setRefreshing(true); + stateLayout.showProgress(); + } + + @Override public void hideProgress() { + refresh.setRefreshing(false); + stateLayout.hideProgress(); + } + + @Override public void showErrorMessage(@NonNull String msgRes) { + hideProgress(); + stateLayout.showReload(adapter.getItemCount()); + super.showErrorMessage(msgRes); + } + + @NonNull @Override public OnLoadMore getLoadMore() { + if (onLoadMore == null) { + onLoadMore = new OnLoadMore<>(getPresenter(), getArguments().getString(BundleConstant.EXTRA)); + } + return onLoadMore; + } + + @Override public void onRefresh() { + getPresenter().onCallApi(1, getArguments().getString(BundleConstant.EXTRA)); + } + + @Override public void onClick(View view) { + onRefresh(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/profile/starred/ProfileStarredMvp.java b/app/src/main/java/com/fastaccess/ui/modules/profile/starred/ProfileStarredMvp.java new file mode 100644 index 00000000..bc029890 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/profile/starred/ProfileStarredMvp.java @@ -0,0 +1,33 @@ +package com.fastaccess.ui.modules.profile.starred; + +import android.support.annotation.NonNull; +import android.support.v4.widget.SwipeRefreshLayout; + +import com.fastaccess.data.dao.RepoModel; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.ArrayList; + +/** + * Created by Kosh on 03 Dec 2016, 3:45 PM + */ + +interface ProfileStarredMvp { + + interface View extends BaseMvp.FAView, SwipeRefreshLayout.OnRefreshListener, android.view.View.OnClickListener { + void onNotifyAdapter(); + + @NonNull OnLoadMore getLoadMore(); + } + + interface Presenter extends BaseMvp.FAPresenter, + BaseViewHolder.OnItemClickListener, + BaseMvp.PaginationListener { + + @NonNull ArrayList getRepos(); + + void onWorkOffline(@NonNull String login); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/profile/starred/ProfileStarredPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/profile/starred/ProfileStarredPresenter.java new file mode 100644 index 00000000..c9216ffc --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/profile/starred/ProfileStarredPresenter.java @@ -0,0 +1,102 @@ +package com.fastaccess.ui.modules.profile.starred; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; + +import com.fastaccess.data.dao.NameParser; +import com.fastaccess.data.dao.RepoModel; +import com.fastaccess.helper.Logger; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; +import com.fastaccess.ui.modules.repos.RepoPagerView; + +import java.util.ArrayList; + +import rx.Observable; + +/** + * Created by Kosh on 03 Dec 2016, 3:48 PM + */ + +class ProfileStarredPresenter extends BasePresenter implements ProfileStarredMvp.Presenter { + + private ArrayList repos = new ArrayList<>(); + private int page; + private int previousTotal; + private int lastPage = Integer.MAX_VALUE; + + @Override public int getCurrentPage() { + return page; + } + + @Override public int getPreviousTotal() { + return previousTotal; + } + + @Override public void setCurrentPage(int page) { + this.page = page; + } + + @Override public void setPreviousTotal(int previousTotal) { + this.previousTotal = previousTotal; + } + + @Override public T onError(@NonNull Throwable throwable, @NonNull Observable observable) { + sendToView(view -> { + if (view.getLoadMore().getParameter() != null) { + onWorkOffline(view.getLoadMore().getParameter()); + } + }); + return super.onError(throwable, observable); + } + + @Override public void onCallApi(int page, @Nullable String parameter) { + if (parameter == null) { + throw new NullPointerException("Username is null"); + } + if (page == 1) { + lastPage = Integer.MAX_VALUE; + sendToView(view -> view.getLoadMore().reset()); + } + setCurrentPage(page); + if (page > lastPage || lastPage == 0) { + sendToView(ProfileStarredMvp.View::hideProgress); + return; + } + makeRestCall(RestProvider.getUserService().getStarred(parameter, page), + repoModelPageable -> { + lastPage = repoModelPageable.getLast(); + if (getCurrentPage() == 1) { + getRepos().clear(); + manageSubscription(RepoModel.saveStarred(repoModelPageable.getItems(), parameter).subscribe()); + } + getRepos().addAll(repoModelPageable.getItems()); + sendToView(ProfileStarredMvp.View::onNotifyAdapter); + }); + } + + @NonNull @Override public ArrayList getRepos() { + return repos; + } + + @Override public void onWorkOffline(@NonNull String login) { + if (repos.isEmpty()) { + manageSubscription(RepoModel.getStarred(login).subscribe(repoModels -> { + repos.addAll(repoModels); + Logger.e(repoModels); + sendToView(ProfileStarredMvp.View::onNotifyAdapter); + })); + } else { + sendToView(ProfileStarredMvp.View::hideProgress); + } + } + + @Override public void onItemClick(int position, View v, RepoModel item) { + RepoPagerView.startRepoPager(v.getContext(), new NameParser(item.getHtmlUrl())); + } + + @Override public void onItemLongClick(int position, View v, RepoModel item) { + onItemClick(position, v, item); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/profile/starred/ProfileStarredView.java b/app/src/main/java/com/fastaccess/ui/modules/profile/starred/ProfileStarredView.java new file mode 100644 index 00000000..8b8e8b80 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/profile/starred/ProfileStarredView.java @@ -0,0 +1,103 @@ +package com.fastaccess.ui.modules.profile.starred; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.v4.widget.SwipeRefreshLayout; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.helper.Logger; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.adapter.ReposAdapter; +import com.fastaccess.ui.base.BaseFragment; +import com.fastaccess.ui.widgets.StateLayout; +import com.fastaccess.ui.widgets.recyclerview.DynamicRecyclerView; + +import butterknife.BindView; + +/** + * Created by Kosh on 03 Dec 2016, 3:56 PM + */ + +public class ProfileStarredView extends BaseFragment implements ProfileStarredMvp.View { + + @BindView(R.id.recycler) DynamicRecyclerView recycler; + @BindView(R.id.refresh) SwipeRefreshLayout refresh; + @BindView(R.id.stateLayout) StateLayout stateLayout; + private OnLoadMore onLoadMore; + private ReposAdapter adapter; + + public static ProfileStarredView newInstance(@NonNull String username) { + ProfileStarredView view = new ProfileStarredView(); + view.setArguments(Bundler.start().put(BundleConstant.EXTRA, username).end()); + return view; + } + + @Override public void onNotifyAdapter() { + + hideProgress(); + adapter.notifyDataSetChanged(); + } + + @Override protected int fragmentLayout() { + return R.layout.small_grid_refresh_list; + } + + @Override protected void onFragmentCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + if (getArguments() == null) { + throw new NullPointerException("Bundle is null, username is required"); + } + stateLayout.setOnReloadListener(this); + refresh.setOnRefreshListener(this); + recycler.setEmptyView(stateLayout, refresh); + getLoadMore().setCurrent_page(getPresenter().getCurrentPage(), getPresenter().getPreviousTotal()); + adapter = new ReposAdapter(getPresenter().getRepos(), true); + adapter.setListener(getPresenter()); + recycler.setAdapter(adapter); + recycler.addOnScrollListener(getLoadMore()); + if (getPresenter().getRepos().isEmpty() && !getPresenter().isApiCalled()) { + onRefresh(); + } + } + + @NonNull @Override public ProfileStarredPresenter providePresenter() { + return new ProfileStarredPresenter(); + } + + + @Override public void showProgress(@StringRes int resId) { + refresh.setRefreshing(true); + stateLayout.showProgress(); + } + + @Override public void hideProgress() { + refresh.setRefreshing(false); + stateLayout.hideProgress(); + } + + @Override public void showErrorMessage(@NonNull String msgRes) { + hideProgress(); + stateLayout.showReload(adapter.getItemCount()); + super.showErrorMessage(msgRes); + } + + + @NonNull @Override public OnLoadMore getLoadMore() { + if (onLoadMore == null) { + onLoadMore = new OnLoadMore<>(getPresenter(), getArguments().getString(BundleConstant.EXTRA)); + } + return onLoadMore; + } + + @Override public void onRefresh() { + getPresenter().onCallApi(1, getArguments().getString(BundleConstant.EXTRA)); + } + + @Override public void onClick(View view) { + onRefresh(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/RepoPagerMvp.java b/app/src/main/java/com/fastaccess/ui/modules/repos/RepoPagerMvp.java new file mode 100644 index 00000000..0f274f4d --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/RepoPagerMvp.java @@ -0,0 +1,97 @@ +package com.fastaccess.ui.modules.repos; + +import android.content.Intent; +import android.support.annotation.IntDef; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; + +import com.fastaccess.data.dao.RepoModel; +import com.fastaccess.ui.base.mvp.BaseMvp; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import it.sephiroth.android.library.bottomnavigation.BottomNavigation; + +/** + * Created by Kosh on 09 Dec 2016, 4:16 PM + */ + +interface RepoPagerMvp { + + int CODE = 0; + int ISSUES = 1; + int PULL_REQUEST = 2; + + @IntDef({ + CODE, + ISSUES, + PULL_REQUEST, + }) + @Retention(RetentionPolicy.SOURCE) @interface RepoNavigationType {} + + + interface View extends BaseMvp.FAView { + + void onNavigationChanged(@RepoNavigationType int navType); + + void onFinishActivity(); + + void onInitRepo(); + + void onRepoWatched(boolean isWatched); + + void onRepoStarred(boolean isStarred); + + void onRepoForked(boolean isForked); + + void onEnableDisableWatch(boolean isEnabled); + + void onEnableDisableStar(boolean isEnabled); + + void onEnableDisableFork(boolean isEnabled); + + void onChangeWatchedCount(boolean isWatched); + + void onChangeStarCount(boolean isStarred); + + void onChangeForkCount(boolean isForked); + } + + interface Presenter extends BaseMvp.FAPresenter, BottomNavigation.OnMenuItemSelectionListener { + void onActivityCreated(@Nullable Intent intent); + + @NonNull String repoId(); + + @NonNull String login(); + + @Nullable RepoModel getRepo(); + + boolean isWatched(); + + boolean isStarred(); + + boolean isForked(); + + void onWatch(); + + void onStar(); + + void onFork(); + + void onCheckWatching(); + + void onCheckStarring(); + + void onWorkOffline(); + + void onModuleChanged(@NonNull FragmentManager fragmentManager, @RepoNavigationType int type); + + void onShowHideFragment(@NonNull FragmentManager fragmentManager, @NonNull Fragment toShow, @NonNull Fragment toHide); + + void onAddAndHide(@NonNull FragmentManager fragmentManager, @NonNull Fragment toAdd, @NonNull Fragment toHide); + + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/RepoPagerPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/repos/RepoPagerPresenter.java new file mode 100644 index 00000000..d9aad04e --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/RepoPagerPresenter.java @@ -0,0 +1,281 @@ +package com.fastaccess.ui.modules.repos; + +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.IdRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; + +import com.fastaccess.R; +import com.fastaccess.data.dao.RepoModel; +import com.fastaccess.helper.AppHelper; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.helper.Logger; +import com.fastaccess.helper.RxHelper; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; +import com.fastaccess.ui.modules.repos.code.RepoCodePagerView; +import com.fastaccess.ui.modules.repos.issues.RepoIssuesPagerView; +import com.fastaccess.ui.modules.repos.pull_requests.RepoPullRequestPagerView; + +import retrofit2.Response; +import rx.Observable; + +import static com.fastaccess.helper.ActivityHelper.getVisibleFragment; + +/** + * Created by Kosh on 09 Dec 2016, 4:17 PM + */ + +class RepoPagerPresenter extends BasePresenter implements RepoPagerMvp.Presenter { + private boolean isWatched; + private boolean isStarred; + private boolean isForked; + private String login; + private String repoId; + private RepoModel repo; + + @Override public T onError(@NonNull Throwable throwable, @NonNull Observable observable) { + onWorkOffline(); + return super.onError(throwable, observable); + } + + @Override public void onActivityCreated(@Nullable Intent intent) { + if (intent != null && intent.getExtras() != null) { + Bundle bundle = intent.getExtras(); + repoId = bundle.getString(BundleConstant.ID); + login = bundle.getString(BundleConstant.EXTRA_TWO); + if (!InputHelper.isEmpty(login()) && !InputHelper.isEmpty(repoId())) { + makeRestCall(RestProvider.getRepoService().getRepo(login(), repoId()), + repoModel -> { + this.repo = repoModel; + manageSubscription(this.repo.persist().observe().subscribe()); + sendToView(view -> { + view.onInitRepo(); + view.onNavigationChanged(RepoPagerMvp.CODE); + }); + onCheckStarring(); + onCheckWatching(); + }); + return; + } + } + sendToView(RepoPagerMvp.View::onFinishActivity); + } + + @NonNull @Override public String repoId() { + return repoId; + } + + @NonNull @Override public String login() { + return login; + } + + @Nullable @Override public RepoModel getRepo() { + return repo; + } + + @Override public boolean isWatched() { + return isWatched; + } + + @Override public boolean isStarred() { + return isStarred; + } + + @Override public boolean isForked() { + return isForked; + } + + @Override public void onWatch() { + if (getRepo() == null) return; + String login = getRepo().getOwner().getLogin(); + String name = getRepo().getName(); + Observable> observable = RxHelper + .getObserver(!isWatched ? RestProvider.getRepoService().watchRepo(login, name) + : RestProvider.getRepoService().unwatchRepo(login, name)); + manageSubscription(observable + .doOnSubscribe(() -> sendToView(view -> view.onEnableDisableWatch(false))) + .doOnNext(booleanResponse -> { + if (!isWatched) { + isWatched = booleanResponse.code() == 204; + } else { + isWatched = booleanResponse.code() != 204; + } + sendToView(view -> { + view.onRepoWatched(isWatched); + view.onChangeWatchedCount(isWatched); + }); + }) + .onErrorReturn(throwable -> { + sendToView(view -> view.onEnableDisableWatch(true)); + return null; + }) + .subscribe()); + } + + @Override public void onStar() { + if (getRepo() == null) return; + String login = getRepo().getOwner().getLogin(); + String name = getRepo().getName(); + Observable> observable = RxHelper + .getObserver(!isStarred ? RestProvider.getRepoService().starRepo(login, name) + : RestProvider.getRepoService().unstarRepo(login, name)); + manageSubscription(observable + .doOnSubscribe(() -> sendToView(view -> view.onEnableDisableStar(false))) + .doOnNext(booleanResponse -> { + if (!isStarred) { + isStarred = booleanResponse.code() == 204; + } else { + isStarred = booleanResponse.code() != 204; + } + sendToView(view -> { + view.onRepoStarred(isStarred); + view.onChangeStarCount(isStarred); + }); + }) + .onErrorReturn(throwable -> { + sendToView(view -> view.onEnableDisableStar(true)); + return null; + }) + .subscribe()); + } + + @Override public void onFork() { + if (!isForked && getRepo() != null) { + String login = login(); + String name = repoId(); + manageSubscription(RxHelper.getObserver(RestProvider.getRepoService().forkRepo(login, name)) + .doOnSubscribe(() -> sendToView(view -> view.onEnableDisableFork(false))) + .doOnNext(repoModel -> sendToView(view -> { + view.onRepoForked(isForked = repoModel != null); + view.onChangeForkCount(isForked); + })) + .onErrorReturn(throwable -> { + sendToView(view -> view.onEnableDisableFork(true)); + return null; + }) + .subscribe()); + } + } + + @Override public void onCheckWatching() { + if (getRepo() != null) { + String login = login(); + String name = repoId(); + manageSubscription(RxHelper.getObserver(RestProvider.getRepoService().isWatchingRepo(login, name)) + .doOnSubscribe(() -> sendToView(view -> view.onEnableDisableWatch(false))) + .doOnNext(subscriptionModel -> sendToView(view -> view.onRepoWatched(isWatched = subscriptionModel.code() == 204))) + .onErrorReturn(throwable -> { + isWatched = false; + sendToView(view -> view.onRepoWatched(isWatched)); + return null; + }) + .subscribe()); + } + } + + @Override public void onCheckStarring() { + if (getRepo() != null) { + String login = login(); + String name = repoId(); + manageSubscription(RxHelper.getObserver(RestProvider.getRepoService().checkStarring(login, name)) + .doOnSubscribe(() -> sendToView(view -> view.onEnableDisableStar(false))) + .doOnNext(response -> sendToView(view -> view.onRepoStarred(isStarred = response.code() == 204))) + .onErrorReturn(throwable -> { + isStarred = false; + sendToView(view -> view.onRepoStarred(isStarred)); + return null; + }) + .subscribe()); + } + } + + @Override public void onWorkOffline() { + if (!InputHelper.isEmpty(login()) && !InputHelper.isEmpty(repoId())) { + manageSubscription(RepoModel.getRepo(repoId) + .subscribe(repoModel -> { + repo = repoModel; + Logger.e(repo.getName(), repo.getOwner().getLogin()); + sendToView(view -> { + view.onInitRepo(); + view.onNavigationChanged(RepoPagerMvp.CODE); + }); + })); + } else { + sendToView(RepoPagerMvp.View::onFinishActivity); + } + } + + @Override public void onModuleChanged(@NonNull FragmentManager fragmentManager, @RepoPagerMvp.RepoNavigationType int type) { + Fragment currentVisible = getVisibleFragment(fragmentManager); + RepoCodePagerView codePagerView = (RepoCodePagerView) AppHelper.getFragmentByTag(fragmentManager, RepoCodePagerView.TAG); + RepoIssuesPagerView repoIssuesPagerView = (RepoIssuesPagerView) AppHelper.getFragmentByTag(fragmentManager, RepoIssuesPagerView.TAG); + RepoPullRequestPagerView pullRequestPagerView = (RepoPullRequestPagerView) AppHelper.getFragmentByTag(fragmentManager, + RepoPullRequestPagerView.TAG); + if (getRepo() == null) { + sendToView(RepoPagerMvp.View::onFinishActivity); + return; + } + if (currentVisible == null) { + fragmentManager.beginTransaction() + .add(R.id.container, RepoCodePagerView.newInstance(repoId(), login(), getRepo().getUrl()), RepoCodePagerView.TAG) + .commit(); + return; + } + switch (type) { + case RepoPagerMvp.CODE: + if (codePagerView == null) { + onAddAndHide(fragmentManager, RepoCodePagerView.newInstance(repoId(), login(), getRepo().getHtmlUrl()), currentVisible); + } else { + onShowHideFragment(fragmentManager, codePagerView, currentVisible); + } + break; + case RepoPagerMvp.ISSUES: + if (repoIssuesPagerView == null) { + onAddAndHide(fragmentManager, RepoIssuesPagerView.newInstance(repoId(), login()), currentVisible); + } else { + onShowHideFragment(fragmentManager, repoIssuesPagerView, currentVisible); + } + break; + case RepoPagerMvp.PULL_REQUEST: + if (pullRequestPagerView == null) { + onAddAndHide(fragmentManager, RepoPullRequestPagerView.newInstance(repoId(), login()), currentVisible); + } else { + onShowHideFragment(fragmentManager, pullRequestPagerView, currentVisible); + } + break; + } + } + + @Override public void onShowHideFragment(@NonNull FragmentManager fragmentManager, @NonNull Fragment toShow, @NonNull Fragment toHide) { + toHide.onHiddenChanged(true); + fragmentManager + .beginTransaction() + .hide(toHide) + .show(toShow) + .commit(); + toShow.onHiddenChanged(false); + } + + @Override public void onAddAndHide(@NonNull FragmentManager fragmentManager, @NonNull Fragment toAdd, @NonNull Fragment toHide) { + toHide.onHiddenChanged(true); + fragmentManager + .beginTransaction() + .hide(toHide) + .add(R.id.container, toAdd, toAdd.getClass().getSimpleName()) + .commit(); + toAdd.onHiddenChanged(false); + } + + @Override public void onMenuItemSelect(@IdRes int id, int position) { + if (getView() != null && isViewAttached()) { + getView().onNavigationChanged(position); + } + } + + @Override public void onMenuItemReselect(@IdRes int id, int position) {} +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/RepoPagerView.java b/app/src/main/java/com/fastaccess/ui/modules/repos/RepoPagerView.java new file mode 100644 index 00000000..30eff20f --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/RepoPagerView.java @@ -0,0 +1,227 @@ +package com.fastaccess.ui.modules.repos; + +import android.content.Context; +import android.content.Intent; +import android.graphics.Color; +import android.graphics.Typeface; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.data.dao.NameParser; +import com.fastaccess.data.dao.RepoModel; +import com.fastaccess.helper.ActivityHelper; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.helper.ParseDateFormat; +import com.fastaccess.helper.TypeFaceHelper; +import com.fastaccess.ui.base.BaseActivity; +import com.fastaccess.ui.widgets.AvatarLayout; +import com.fastaccess.ui.widgets.FontTextView; + +import java.text.NumberFormat; + +import butterknife.BindColor; +import butterknife.BindView; +import butterknife.OnClick; +import hugo.weaving.DebugLog; +import it.sephiroth.android.library.bottomnavigation.BottomNavigation; + +/** + * Created by Kosh on 09 Dec 2016, 4:17 PM + */ + +public class RepoPagerView extends BaseActivity implements RepoPagerMvp.View { + + @BindView(R.id.avatarLayout) AvatarLayout avatarLayout; + @BindView(R.id.headerTitle) FontTextView title; + @BindView(R.id.size) FontTextView size; + @BindView(R.id.date) FontTextView date; + @BindView(R.id.forkRepo) FontTextView forkRepo; + @BindView(R.id.starRepo) FontTextView starRepo; + @BindView(R.id.watchRepo) FontTextView watchRepo; + @BindView(R.id.license) FontTextView license; + @BindColor(R.color.accent) int accentColor; + @BindView(R.id.bottomNavigation) BottomNavigation bottomNavigation; + private NumberFormat numberFormat = NumberFormat.getNumberInstance(); + + @DebugLog public static void startRepoPager(@NonNull Context context, @NonNull NameParser nameParser) { + if (!InputHelper.isEmpty(nameParser.getName()) && !InputHelper.isEmpty(nameParser.getUsername())) { + context.startActivity(createIntent(context, nameParser.getName(), nameParser.getUsername())); + } + } + + @DebugLog public static Intent createIntent(@NonNull Context context, @NonNull String repoId, @NonNull String login) { + Intent intent = new Intent(context, RepoPagerView.class); + intent.putExtras(Bundler.start() + .put(BundleConstant.ID, repoId) + .put(BundleConstant.EXTRA_TWO, login) + .end()); + return intent; + } + + @OnClick(R.id.headerTitle) void onTitleClick() { + RepoModel repoModel = getPresenter().getRepo(); + if (repoModel != null && !InputHelper.isEmpty(repoModel.getDescription())) + showMessage(getString(R.string.details), repoModel.getDescription()); + } + + @OnClick({R.id.forkRepo, R.id.starRepo, R.id.watchRepo}) public void onClick(View view) { + switch (view.getId()) { + case R.id.forkRepo: + getPresenter().onFork(); + break; + case R.id.starRepo: + getPresenter().onStar(); + break; + case R.id.watchRepo: + getPresenter().onWatch(); + break; + } + } + + @Override protected int layout() { + return R.layout.repo_pager_activity; + } + + @Override protected boolean isTransparent() { + return false; + } + + @Override protected boolean canBack() { + return true; + } + + @Override protected boolean isSecured() { + return false; + } + + @NonNull @Override public RepoPagerPresenter providePresenter() { + return new RepoPagerPresenter(); + } + + @Override protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setTitle(""); + Typeface myTypeface = TypeFaceHelper.getTypeface(); + bottomNavigation.setDefaultTypeface(myTypeface); + if (savedInstanceState == null) { + getPresenter().onActivityCreated(getIntent()); + bottomNavigation.setDefaultSelectedIndex(0); + } else { + if (getPresenter().getRepo() != null) { + onInitRepo(); + } + } + } + + @Override public void onNavigationChanged(@RepoPagerMvp.RepoNavigationType int navType) { + //noinspection WrongConstant + if (bottomNavigation.getSelectedIndex() != navType) bottomNavigation.setSelectedIndex(navType, true); + + getPresenter().onModuleChanged(getSupportFragmentManager(), navType); + } + + @Override public void onFinishActivity() { + finish(); + } + + @Override public void onInitRepo() { + if (getPresenter().getRepo() == null) { + finish(); + return; + } + bottomNavigation.setOnMenuItemClickListener(getPresenter()); + RepoModel repoModel = getPresenter().getRepo(); + hideProgress(); + forkRepo.setText(numberFormat.format(repoModel.getForksCount())); + starRepo.setText(numberFormat.format(repoModel.getStargazersCount())); + watchRepo.setText(numberFormat.format(repoModel.getSubsCount())); + if (repoModel.getOwner() != null) { + avatarLayout.setUrl(repoModel.getOwner().getAvatarUrl(), repoModel.getOwner().getLogin()); + } else if (repoModel.getOrganization() != null) { + avatarLayout.setUrl(repoModel.getOrganization().getAvatarUrl(), repoModel.getOrganization().getLogin()); + } + date.setText(ParseDateFormat.getTimeAgo(repoModel.getCreatedAt())); + size.setText(ParseDateFormat.getTimeAgo(repoModel.getUpdatedAt())); + title.setText(repoModel.getFullName()); + license.setVisibility(repoModel.getLicense() != null ? View.VISIBLE : View.GONE); + if (repoModel.getLicense() != null) license.setText(repoModel.getLicense().getSpdxId()); + supportInvalidateOptionsMenu(); + } + + @Override public void onRepoWatched(boolean isWatched) { + watchRepo.tintDrawables(isWatched ? accentColor : Color.BLACK); + onEnableDisableWatch(true); + } + + @Override public void onRepoStarred(boolean isStarred) { + starRepo.tintDrawables(isStarred ? accentColor : Color.BLACK); + onEnableDisableStar(true); + } + + @Override public void onRepoForked(boolean isForked) { + forkRepo.tintDrawables(isForked ? accentColor : Color.BLACK); + onEnableDisableFork(true); + } + + @Override public void onEnableDisableWatch(boolean isEnabled) { + watchRepo.setEnabled(isEnabled); + } + + @Override public void onEnableDisableStar(boolean isEnabled) { + starRepo.setEnabled(isEnabled); + } + + @Override public void onEnableDisableFork(boolean isEnabled) { + forkRepo.setEnabled(isEnabled); + } + + @Override public void onChangeWatchedCount(boolean isWatched) { + long count = InputHelper.toLong(watchRepo); + watchRepo.setText(numberFormat.format(isWatched ? (count + 1) : (count > 0 ? (count - 1) : 0))); + } + + @Override public void onChangeStarCount(boolean isStarred) { + long count = InputHelper.toLong(starRepo); + starRepo.setText(numberFormat.format(isStarred ? (count + 1) : (count > 0 ? (count - 1) : 0))); + } + + @Override public void onChangeForkCount(boolean isForked) { + long count = InputHelper.toLong(forkRepo); + forkRepo.setText(numberFormat.format(isForked ? (count + 1) : (count > 0 ? (count - 1) : 0))); + } + + @Override public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.repo_menu, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override public boolean onPrepareOptionsMenu(Menu menu) { + RepoModel repoModel = getPresenter().getRepo(); + if (repoModel != null && repoModel.isFork() && repoModel.getParent() != null) { + MenuItem menuItem = menu.findItem(R.id.originalRepo); + menuItem.setVisible(true); + menuItem.setTitle(repoModel.getParent().getFullName()); + } + return super.onPrepareOptionsMenu(menu); + } + + @Override public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.share) { + if (getPresenter().getRepo() != null) ActivityHelper.shareUrl(this, getPresenter().getRepo().getHtmlUrl()); + return true; + } else if (item.getItemId() == R.id.originalRepo) { + if (getPresenter().getRepo() != null && getPresenter().getRepo().getParent() != null) { + RepoModel parent = getPresenter().getRepo().getParent(); + RepoPagerView.startRepoPager(this, new NameParser(parent.getHtmlUrl())); + } + return true; + } + return super.onOptionsItemSelected(item); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/code/RepoCodePagerMvp.java b/app/src/main/java/com/fastaccess/ui/modules/repos/code/RepoCodePagerMvp.java new file mode 100644 index 00000000..19b0653a --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/code/RepoCodePagerMvp.java @@ -0,0 +1,14 @@ +package com.fastaccess.ui.modules.repos.code; + +import com.fastaccess.ui.base.mvp.BaseMvp; + +/** + * Created by Kosh on 31 Dec 2016, 1:35 AM + */ + +interface RepoCodePagerMvp { + + interface View extends BaseMvp.FAView {} + + interface Presenter extends BaseMvp.FAPresenter {} +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/code/RepoCodePagerPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/repos/code/RepoCodePagerPresenter.java new file mode 100644 index 00000000..265101b8 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/code/RepoCodePagerPresenter.java @@ -0,0 +1,9 @@ +package com.fastaccess.ui.modules.repos.code; + +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; + +/** + * Created by Kosh on 31 Dec 2016, 1:36 AM + */ + +class RepoCodePagerPresenter extends BasePresenter implements RepoCodePagerMvp.Presenter {} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/code/RepoCodePagerView.java b/app/src/main/java/com/fastaccess/ui/modules/repos/code/RepoCodePagerView.java new file mode 100644 index 00000000..3e9dedb1 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/code/RepoCodePagerView.java @@ -0,0 +1,60 @@ +package com.fastaccess.ui.modules.repos.code; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.TabLayout; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.data.dao.FragmentPagerAdapterModel; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.ui.adapter.FragmentsPagerAdapter; +import com.fastaccess.ui.base.BaseFragment; +import com.fastaccess.ui.widgets.ViewPagerView; + +import butterknife.BindView; + +/** + * Created by Kosh on 31 Dec 2016, 1:36 AM + */ + +public class RepoCodePagerView extends BaseFragment implements RepoCodePagerMvp.View { + public static final String TAG = RepoCodePagerView.class.getSimpleName(); + + @BindView(R.id.tabs) TabLayout tabs; + @BindView(R.id.pager) ViewPagerView pager; + + public static RepoCodePagerView newInstance(@NonNull String repoId, @NonNull String login, @NonNull String htmlLink) { + RepoCodePagerView view = new RepoCodePagerView(); + view.setArguments(Bundler.start() + .put(BundleConstant.ID, repoId) + .put(BundleConstant.EXTRA, login) + .put(BundleConstant.EXTRA_TWO, htmlLink) + .end()); + return view; + } + + @Override protected int fragmentLayout() { + return R.layout.tabbed_viewpager; + } + + @Override protected void onFragmentCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + String repoId = getArguments().getString(BundleConstant.ID); + String login = getArguments().getString(BundleConstant.EXTRA); + String htmlLink = getArguments().getString(BundleConstant.EXTRA_TWO); + if (InputHelper.isEmpty(repoId) || InputHelper.isEmpty(login) || InputHelper.isEmpty(htmlLink)) { + throw new NullPointerException(String.format("Failed Initializing (%s) %s %s %s", getClass().getSimpleName(), repoId, login, htmlLink)); + } + pager.setAdapter(new FragmentsPagerAdapter(getChildFragmentManager(), + FragmentPagerAdapterModel.buildForRepoCode(getContext(), repoId, login, htmlLink))); + tabs.setTabMode(TabLayout.MODE_SCROLLABLE); + tabs.setupWithViewPager(pager); + } + + @NonNull @Override public RepoCodePagerPresenter providePresenter() { + return new RepoCodePagerPresenter(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/RepoCommitsMvp.java b/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/RepoCommitsMvp.java new file mode 100644 index 00000000..780fd40a --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/RepoCommitsMvp.java @@ -0,0 +1,35 @@ +package com.fastaccess.ui.modules.repos.code.commit; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.widget.SwipeRefreshLayout; + +import com.fastaccess.data.dao.CommitModel; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.ArrayList; + +/** + * Created by Kosh on 03 Dec 2016, 3:45 PM + */ + +interface RepoCommitsMvp { + + interface View extends BaseMvp.FAView, SwipeRefreshLayout.OnRefreshListener, android.view.View.OnClickListener { + void onNotifyAdapter(); + + @NonNull OnLoadMore getLoadMore(); + } + + interface Presenter extends BaseMvp.FAPresenter, + BaseViewHolder.OnItemClickListener, + BaseMvp.PaginationListener { + void onFragmentCreated(@NonNull Bundle bundle); + + @NonNull ArrayList getCommits(); + + void onWorkOffline(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/RepoCommitsPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/RepoCommitsPresenter.java new file mode 100644 index 00000000..0fed980f --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/RepoCommitsPresenter.java @@ -0,0 +1,108 @@ +package com.fastaccess.ui.modules.repos.code.commit; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; + +import com.fastaccess.data.dao.CommitModel; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; +import com.fastaccess.ui.modules.repos.code.commit.details.CommitPagerView; + +import java.util.ArrayList; + +import rx.Observable; + +/** + * Created by Kosh on 03 Dec 2016, 3:48 PM + */ + +class RepoCommitsPresenter extends BasePresenter implements RepoCommitsMvp.Presenter { + + private ArrayList commits = new ArrayList<>(); + private String login; + private String repoId; + private int page; + private int previousTotal; + private int lastPage = Integer.MAX_VALUE; + + @Override public int getCurrentPage() { + return page; + } + + @Override public int getPreviousTotal() { + return previousTotal; + } + + @Override public void setCurrentPage(int page) { + this.page = page; + } + + @Override public void setPreviousTotal(int previousTotal) { + this.previousTotal = previousTotal; + } + + @Override public T onError(@NonNull Throwable throwable, @NonNull Observable observable) { + onWorkOffline(); + return super.onError(throwable, observable); + } + + @Override public void onCallApi(int page, @Nullable Object parameter) { + if (page == 1) { + lastPage = Integer.MAX_VALUE; + sendToView(view -> view.getLoadMore().reset()); + } + setCurrentPage(page); + if (page > lastPage || lastPage == 0) { + sendToView(RepoCommitsMvp.View::hideProgress); + return; + } + if (repoId == null || login == null) return; + makeRestCall(RestProvider.getRepoService().getCommits(login, repoId, page), + response -> { + lastPage = response.getLast(); + if (getCurrentPage() == 1) { + getCommits().clear(); + manageSubscription(CommitModel.save(response.getItems(), repoId, login).subscribe()); + } + getCommits().addAll(response.getItems()); + sendToView(RepoCommitsMvp.View::onNotifyAdapter); + }); + } + + @Override public void onFragmentCreated(@NonNull Bundle bundle) { + repoId = bundle.getString(BundleConstant.ID); + login = bundle.getString(BundleConstant.EXTRA); + if (!InputHelper.isEmpty(login) && !InputHelper.isEmpty(repoId)) { + onCallApi(1, null); + } + } + + @NonNull @Override public ArrayList getCommits() { + return commits; + } + + @Override public void onWorkOffline() { + if (commits.isEmpty()) { + manageSubscription(CommitModel.getCommits(repoId, login) + .subscribe(models -> { + commits.addAll(models); + sendToView(RepoCommitsMvp.View::onNotifyAdapter); + })); + } else { + sendToView(BaseMvp.FAView::hideProgress); + } + } + + @Override public void onItemClick(int position, View v, CommitModel item) { + CommitPagerView.createIntentForOffline(v.getContext(), item); + } + + @Override public void onItemLongClick(int position, View v, CommitModel item) { + onItemClick(position, v, item); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/RepoCommitsView.java b/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/RepoCommitsView.java new file mode 100644 index 00000000..677dded1 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/RepoCommitsView.java @@ -0,0 +1,104 @@ +package com.fastaccess.ui.modules.repos.code.commit; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.v4.widget.SwipeRefreshLayout; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.adapter.CommitsAdapter; +import com.fastaccess.ui.base.BaseFragment; +import com.fastaccess.ui.widgets.StateLayout; +import com.fastaccess.ui.widgets.recyclerview.DynamicRecyclerView; + +import butterknife.BindView; + +/** + * Created by Kosh on 03 Dec 2016, 3:56 PM + */ + +public class RepoCommitsView extends BaseFragment implements RepoCommitsMvp.View { + @BindView(R.id.recycler) DynamicRecyclerView recycler; + @BindView(R.id.refresh) SwipeRefreshLayout refresh; + @BindView(R.id.stateLayout) StateLayout stateLayout; + private OnLoadMore onLoadMore; + private CommitsAdapter adapter; + + public static RepoCommitsView newInstance(@NonNull String repoId, @NonNull String login) { + RepoCommitsView view = new RepoCommitsView(); + view.setArguments(Bundler.start() + .put(BundleConstant.ID, repoId) + .put(BundleConstant.EXTRA, login) + .end()); + return view; + } + + @Override public void onNotifyAdapter() { + + hideProgress(); + adapter.notifyDataSetChanged(); + } + + @Override protected int fragmentLayout() { + return R.layout.small_grid_refresh_list; + } + + @Override protected void onFragmentCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + if (getArguments() == null) { + throw new NullPointerException("Bundle is null, therefore, issues can't be proceeded."); + } + stateLayout.setOnReloadListener(this); + refresh.setOnRefreshListener(this); + recycler.setEmptyView(stateLayout, refresh); + adapter = new CommitsAdapter(getPresenter().getCommits()); + adapter.setListener(getPresenter()); + getLoadMore().setCurrent_page(getPresenter().getCurrentPage(), getPresenter().getPreviousTotal()); + recycler.setAdapter(adapter); + recycler.addOnScrollListener(getLoadMore()); + if (savedInstanceState == null) { + getPresenter().onFragmentCreated(getArguments()); + } else if (getPresenter().getCommits().isEmpty() && !getPresenter().isApiCalled()) { + onRefresh(); + } + } + + @NonNull @Override public RepoCommitsPresenter providePresenter() { + return new RepoCommitsPresenter(); + } + + @Override public void showProgress(@StringRes int resId) { + refresh.setRefreshing(true); + stateLayout.showProgress(); + } + + @Override public void hideProgress() { + refresh.setRefreshing(false); + stateLayout.hideProgress(); + } + + @Override public void showErrorMessage(@NonNull String msgRes) { + hideProgress(); + stateLayout.showReload(adapter.getItemCount()); + super.showErrorMessage(msgRes); + } + + @SuppressWarnings("unchecked") @NonNull @Override public OnLoadMore getLoadMore() { + if (onLoadMore == null) { + onLoadMore = new OnLoadMore(getPresenter()); + } + return onLoadMore; + } + + @Override public void onRefresh() { + getPresenter().onCallApi(1, null); + } + + @Override public void onClick(View view) { + onRefresh(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/CommitPagerMvp.java b/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/CommitPagerMvp.java new file mode 100644 index 00000000..42450457 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/CommitPagerMvp.java @@ -0,0 +1,31 @@ +package com.fastaccess.ui.modules.repos.code.commit.details; + +import android.content.Intent; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.fastaccess.data.dao.CommitModel; +import com.fastaccess.ui.base.mvp.BaseMvp; + +/** + * Created by Kosh on 10 Dec 2016, 9:21 AM + */ + +interface CommitPagerMvp { + + interface View extends BaseMvp.FAView { + + void onSetup(); + } + + interface Presenter extends BaseMvp.FAPresenter { + + @Nullable CommitModel getCommit(); + + void onActivityCreated(@Nullable Intent intent); + + void onWorkOffline(@NonNull String sha, @NonNull String repoId, @NonNull String login); + + } + +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/CommitPagerPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/CommitPagerPresenter.java new file mode 100644 index 00000000..159b7e4e --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/CommitPagerPresenter.java @@ -0,0 +1,65 @@ +package com.fastaccess.ui.modules.repos.code.commit.details; + +import android.content.Intent; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.fastaccess.data.dao.CommitModel; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; + +import rx.Observable; + +/** + * Created by Kosh on 10 Dec 2016, 9:23 AM + */ + +class CommitPagerPresenter extends BasePresenter implements CommitPagerMvp.Presenter { + private CommitModel commitModel; + private String sha; + private String login; + private String repoId; + + @Nullable @Override public CommitModel getCommit() { + return commitModel; + } + + @Override public T onError(@NonNull Throwable throwable, @NonNull Observable observable) { + onWorkOffline(sha, repoId, login); + return super.onError(throwable, observable); + } + + @Override public void onActivityCreated(@Nullable Intent intent) { + if (intent != null && intent.getExtras() != null) { + sha = intent.getExtras().getString(BundleConstant.ID); + login = intent.getExtras().getString(BundleConstant.EXTRA); + repoId = intent.getExtras().getString(BundleConstant.EXTRA_TWO); + if (commitModel != null) { + sendToView(CommitPagerMvp.View::onSetup); + return; + } else if (!InputHelper.isEmpty(sha) && !InputHelper.isEmpty(login) && !InputHelper.isEmpty(repoId)) { + makeRestCall(RestProvider.getRepoService().getCommit(login, repoId, sha), + commit -> { + commitModel = commit; + commitModel.setRepoId(repoId); + commitModel.setLogin(login); + sendToView(CommitPagerMvp.View::onSetup); + manageSubscription(commitModel.save().subscribe()); + }); + return; + } + } + sendToView(CommitPagerMvp.View::onSetup); + } + + @Override public void onWorkOffline(@NonNull String sha, @NonNull String repoId, @NonNull String login) { + manageSubscription(CommitModel.getCommit(sha, repoId, login) + .subscribe(commit -> { + commitModel = commit; + sendToView(CommitPagerMvp.View::onSetup); + })); + } + +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/CommitPagerView.java b/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/CommitPagerView.java new file mode 100644 index 00000000..b8d6a15b --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/CommitPagerView.java @@ -0,0 +1,158 @@ +package com.fastaccess.ui.modules.repos.code.commit.details; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.design.widget.CoordinatorLayout; +import android.support.design.widget.FloatingActionButton; +import android.support.design.widget.TabLayout; +import android.support.v4.view.ViewPager; +import android.view.Menu; +import android.view.MenuItem; + +import com.fastaccess.R; +import com.fastaccess.data.dao.CommitModel; +import com.fastaccess.data.dao.FragmentPagerAdapterModel; +import com.fastaccess.helper.ActivityHelper; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.helper.ParseDateFormat; +import com.fastaccess.provider.scheme.SchemeParser; +import com.fastaccess.ui.adapter.FragmentsPagerAdapter; +import com.fastaccess.ui.base.BaseActivity; +import com.fastaccess.ui.modules.repos.code.commit.details.comments.CommitCommentsView; +import com.fastaccess.ui.widgets.AvatarLayout; +import com.fastaccess.ui.widgets.FontTextView; +import com.fastaccess.ui.widgets.ViewPagerView; + +import java.util.Date; + +import butterknife.BindView; +import butterknife.OnClick; + +/** + * Created by Kosh on 10 Dec 2016, 9:23 AM + */ + +public class CommitPagerView extends BaseActivity implements CommitPagerMvp.View { + + @BindView(R.id.avatarLayout) AvatarLayout avatarLayout; + @BindView(R.id.headerTitle) FontTextView title; + @BindView(R.id.size) FontTextView size; + @BindView(R.id.date) FontTextView date; + @BindView(R.id.tabs) TabLayout tabs; + @BindView(R.id.pager) ViewPagerView pager; + @BindView(R.id.fab) FloatingActionButton fab; + @BindView(R.id.changes) FontTextView changes; + @BindView(R.id.addition) FontTextView addition; + @BindView(R.id.deletion) FontTextView deletion; + @BindView(R.id.coordinatorLayout) CoordinatorLayout coordinatorLayout; + + public static Intent createIntent(@NonNull Context context, @NonNull String repoId, @NonNull String login, @NonNull String sha) { + Intent intent = new Intent(context, CommitPagerView.class); + intent.putExtras(Bundler.start() + .put(BundleConstant.ID, sha) + .put(BundleConstant.EXTRA, login) + .put(BundleConstant.EXTRA_TWO, repoId) + .end()); + return intent; + + } + + public static void createIntentForOffline(@NonNull Context context, @NonNull CommitModel commitModel) { + SchemeParser.launchUri(context, Uri.parse(commitModel.getHtmlUrl())); + } + + @OnClick(R.id.headerTitle) void onTitleClick() { + if (getPresenter().getCommit() != null && !InputHelper.isEmpty(getPresenter().getCommit().getGitCommit().getMessage())) + showMessage(getString(R.string.details), getPresenter().getCommit().getGitCommit().getMessage()); + } + + @OnClick(R.id.fab) void onAddComment() { + CommitCommentsView view = (CommitCommentsView) pager.getAdapter().instantiateItem(pager, 1); + if (view != null) { + view.onStartNewComment(); + } + } + + @Override protected int layout() { + return R.layout.commit_pager_activity; + } + + @Override protected boolean isTransparent() { + return false; + } + + @Override protected boolean canBack() { + return true; + } + + @Override protected boolean isSecured() { + return false; + } + + @NonNull @Override public CommitPagerPresenter providePresenter() { + return new CommitPagerPresenter(); + } + + @Override protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (savedInstanceState == null) { + getPresenter().onActivityCreated(getIntent()); + } else { + onSetup(); + } + } + + @Override public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.share_menu, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.share) { + if (getPresenter().getCommit() != null) ActivityHelper.shareUrl(this, getPresenter().getCommit().getHtmlUrl()); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override public void onSetup() { + hideProgress(); + if (getPresenter().getCommit() == null) { + finish(); + return; + } + supportInvalidateOptionsMenu(); + CommitModel commit = getPresenter().getCommit(); + String login = commit.getAuthor() != null ? commit.getAuthor().getLogin() : commit.getGitCommit().getAuthor().getName(); + String avatar = commit.getAuthor() != null ? commit.getAuthor().getAvatarUrl() : null; + Date dateValue = commit.getGitCommit().getAuthor().getDate(); + title.setText(commit.getGitCommit().getMessage()); + size.setText(ParseDateFormat.getTimeAgo(dateValue)); + avatarLayout.setUrl(avatar, login); + addition.setText(String.valueOf(commit.getStats() != null ? commit.getStats().getAdditions() : 0)); + deletion.setText(String.valueOf(commit.getStats() != null ? commit.getStats().getDeletions() : 0)); + changes.setText(String.valueOf(commit.getFiles() != null ? commit.getFiles().size() : 0)); + pager.setAdapter(new FragmentsPagerAdapter(getSupportFragmentManager(), FragmentPagerAdapterModel.buildForCommit(this, commit))); + tabs.setupWithViewPager(pager); + pager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { + @Override public void onPageSelected(int position) { + super.onPageSelected(position); + hideShowFab(); + } + }); + hideShowFab(); + } + + private void hideShowFab() { + if (pager.getCurrentItem() == 1) { + fab.show(); + } else { + fab.hide(); + } + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/comments/CommitCommentsMvp.java b/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/comments/CommitCommentsMvp.java new file mode 100644 index 00000000..1fb6be41 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/comments/CommitCommentsMvp.java @@ -0,0 +1,70 @@ +package com.fastaccess.ui.modules.repos.code.commit.details.comments; + +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.widget.SwipeRefreshLayout; + +import com.fastaccess.data.dao.CommentsModel; +import com.fastaccess.data.dao.UserModel; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.adapter.CommentsAdapter; +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; +import com.fastaccess.ui.widgets.recyclerview.DynamicRecyclerView; + +import java.util.ArrayList; + +import retrofit2.Response; + +/** + * Created by Kosh on 20 Nov 2016, 11:10 AM + */ + +interface CommitCommentsMvp { + + interface View extends BaseMvp.FAView, SwipeRefreshLayout.OnRefreshListener, + android.view.View.OnClickListener { + + void onNotifyAdapter(); + + @NonNull OnLoadMore getLoadMore(); + + void onEditComment(@NonNull CommentsModel item); + + void onStartNewComment(); + + void onHandleCommentDelete(@NonNull Response booleanResponse, long commId); + + void onShowDeleteMsg(long id); + + void onShowProgressDialog(); + + void onTagUser(@Nullable UserModel user); + + } + + interface Presenter extends BaseMvp.FAPresenter, + BaseMvp.PaginationListener, BaseViewHolder.OnItemClickListener { + + void onFragmentCreated(@Nullable Bundle bundle); + + @NonNull ArrayList getComments(); + + void onActivityResult(int requestCode, int resultCode, @Nullable Intent data, + @NonNull DynamicRecyclerView recycler, @NonNull CommentsAdapter adapter); + + void onHandleDeletion(@Nullable Bundle bundle); + + void onWorkOffline(); + + @NonNull String repoId(); + + @NonNull String login(); + + String sha(); + } + + +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/comments/CommitCommentsPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/comments/CommitCommentsPresenter.java new file mode 100644 index 00000000..84ef0a24 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/comments/CommitCommentsPresenter.java @@ -0,0 +1,168 @@ +package com.fastaccess.ui.modules.repos.code.commit.details.comments; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.TextUtils; +import android.view.View; + +import com.fastaccess.data.dao.CommentsModel; +import com.fastaccess.data.dao.LoginModel; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.ui.adapter.CommentsAdapter; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; +import com.fastaccess.ui.widgets.recyclerview.DynamicRecyclerView; + +import java.util.ArrayList; + +/** + * Created by Kosh on 11 Nov 2016, 12:36 PM + */ + +class CommitCommentsPresenter extends BasePresenter implements CommitCommentsMvp.Presenter { + private ArrayList comments = new ArrayList<>(); + private int page; + private int previousTotal; + private int lastPage = Integer.MAX_VALUE; + private String repoId; + private String login; + private String sha; + + + @Override public int getCurrentPage() { + return page; + } + + @Override public int getPreviousTotal() { + return previousTotal; + } + + @Override public void setCurrentPage(int page) { + this.page = page; + } + + @Override public void setPreviousTotal(int previousTotal) { + this.previousTotal = previousTotal; + } + + @Override public void onCallApi(int page, @Nullable String parameter) { + if (page == 1) { + lastPage = Integer.MAX_VALUE; + sendToView(view -> view.getLoadMore().reset()); + } + if (page > lastPage || lastPage == 0) { + sendToView(CommitCommentsMvp.View::hideProgress); + return; + } + setCurrentPage(page); + makeRestCall(RestProvider.getRepoService().getCommitComments(login, repoId, sha, page), + listResponse -> { + lastPage = listResponse.getLast(); + if (getCurrentPage() == 1) { + getComments().clear(); + } + getComments().addAll(listResponse.getItems()); + sendToView(CommitCommentsMvp.View::onNotifyAdapter); + }); + } + + @Override public void onFragmentCreated(@Nullable Bundle bundle) { + if (bundle == null) throw new NullPointerException("Bundle is null?"); + repoId = bundle.getString(BundleConstant.ID); + login = bundle.getString(BundleConstant.EXTRA); + sha = bundle.getString(BundleConstant.EXTRA_TWO); + } + + @NonNull @Override public ArrayList getComments() { + return comments; + } + + @Override public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data, + @NonNull DynamicRecyclerView recycler, + @NonNull CommentsAdapter adapter) { + if (resultCode == Activity.RESULT_OK && data != null) { + if (requestCode == BundleConstant.REQUEST_CODE) { + Bundle bundle = data.getExtras(); + if (bundle != null) { + boolean isNew = bundle.getBoolean(BundleConstant.EXTRA); + CommentsModel commentsModel = bundle.getParcelable(BundleConstant.ITEM); + if (isNew) { + adapter.addItem(commentsModel); + recycler.smoothScrollToPosition(adapter.getItemCount()); + } else { + int position = adapter.getItem(commentsModel); + if (position != -1) { + adapter.swapItem(commentsModel, position); + recycler.smoothScrollToPosition(position); + } else { + adapter.addItem(commentsModel); + recycler.smoothScrollToPosition(adapter.getItemCount()); + } + } + } + } + } + } + + @Override public void onHandleDeletion(@Nullable Bundle bundle) { + if (bundle != null) { + long commId = bundle.getLong(BundleConstant.EXTRA, 0); + if (commId != 0) { + makeRestCall(RestProvider.getRepoService().deleteComment(login, repoId, commId) + , booleanResponse -> sendToView(view -> view.onHandleCommentDelete(booleanResponse, commId))); + } + } + } + + @Override public void onWorkOffline() { + if (comments.isEmpty()) { + manageSubscription(CommentsModel.getCommitComments(repoId(), login(), sha) + .subscribe(models -> { + if (models != null) { + comments.addAll(models); + sendToView(CommitCommentsMvp.View::onNotifyAdapter); + } + })); + } else { + sendToView(CommitCommentsMvp.View::hideProgress); + } + } + + @NonNull @Override public String repoId() { + return repoId; + } + + @NonNull @Override public String login() { + return login; + } + + @Override public String sha() { + return sha; + } + + @Override public void onItemClick(int position, View v, CommentsModel item) { + if (getView() != null) { + if (item.getUser() != null) { + LoginModel login = LoginModel.getUser(); + if (login != null && item.getUser().getLogin().equals(login.getLogin())) { + getView().onEditComment(item); + } else { + getView().onTagUser(item.getUser()); + } + } else { + getView().onTagUser(item.getUser()); + } + } + } + + @Override public void onItemLongClick(int position, View v, CommentsModel item) { + if (item.getUser() != null && TextUtils.equals(item.getUser().getLogin(), LoginModel.getUser().getLogin())) { + if (getView() != null) getView().onShowDeleteMsg(item.getId()); + } else { + onItemClick(position, v, item); + } + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/comments/CommitCommentsView.java b/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/comments/CommitCommentsView.java new file mode 100644 index 00000000..4880d514 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/comments/CommitCommentsView.java @@ -0,0 +1,179 @@ +package com.fastaccess.ui.modules.repos.code.commit.details.comments; + +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.v4.widget.SwipeRefreshLayout; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.data.dao.CommentsModel; +import com.fastaccess.data.dao.UserModel; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.adapter.CommentsAdapter; +import com.fastaccess.ui.base.BaseFragment; +import com.fastaccess.ui.modules.editor.EditorView; +import com.fastaccess.ui.widgets.StateLayout; +import com.fastaccess.ui.widgets.dialog.MessageDialogView; +import com.fastaccess.ui.widgets.recyclerview.DynamicRecyclerView; + +import butterknife.BindView; +import retrofit2.Response; + +/** + * Created by Kosh on 11 Nov 2016, 12:36 PM + */ + +public class CommitCommentsView extends BaseFragment implements CommitCommentsMvp.View { + + @BindView(R.id.recycler) DynamicRecyclerView recycler; + @BindView(R.id.refresh) SwipeRefreshLayout refresh; + @BindView(R.id.stateLayout) StateLayout stateLayout; + private CommentsAdapter adapter; + private OnLoadMore onLoadMore; + + public static CommitCommentsView newInstance(@NonNull String login, @NonNull String repoId, @NonNull String sha) { + CommitCommentsView view = new CommitCommentsView(); + view.setArguments(Bundler.start() + .put(BundleConstant.ID, repoId) + .put(BundleConstant.EXTRA, login) + .put(BundleConstant.EXTRA_TWO, sha) + .end()); + return view; + } + + @Override protected int fragmentLayout() { + return R.layout.small_grid_refresh_list; + } + + @Override protected void onFragmentCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + if (savedInstanceState == null) getPresenter().onFragmentCreated(getArguments()); + recycler.setEmptyView(stateLayout, refresh); + refresh.setOnRefreshListener(this); + stateLayout.setOnReloadListener(this); + adapter = new CommentsAdapter(getPresenter().getComments()); + adapter.setListener(getPresenter()); + getLoadMore().setCurrent_page(getPresenter().getCurrentPage(), getPresenter().getPreviousTotal()); + recycler.setAdapter(adapter); + recycler.addOnScrollListener(getLoadMore()); + if (getPresenter().getComments().isEmpty() && !getPresenter().isApiCalled()) { + onRefresh(); + } + } + + @Override public void onRefresh() { + getPresenter().onCallApi(1, null); + } + + @Override public void onNotifyAdapter() { + hideProgress(); + adapter.notifyDataSetChanged(); + } + + @Override public void hideProgress() { + super.hideProgress(); + refresh.setRefreshing(false); + stateLayout.hideProgress(); + } + + @Override public void showProgress(@StringRes int resId) { + refresh.setRefreshing(true); + stateLayout.showProgress(); + } + + @Override public void showErrorMessage(@NonNull String message) { + hideProgress(); + stateLayout.showReload(adapter.getItemCount()); + super.showErrorMessage(message); + } + + @NonNull @Override public CommitCommentsPresenter providePresenter() { + return new CommitCommentsPresenter(); + } + + @NonNull @Override public OnLoadMore getLoadMore() { + if (onLoadMore == null) { + onLoadMore = new OnLoadMore<>(getPresenter()); + } + return onLoadMore; + } + + @Override public void onEditComment(@NonNull CommentsModel item) { + Intent intent = new Intent(getContext(), EditorView.class); + intent.putExtras(Bundler + .start() + .put(BundleConstant.ID, getPresenter().repoId()) + .put(BundleConstant.EXTRA_TWO, getPresenter().login()) + .put(BundleConstant.EXTRA_THREE, getPresenter().sha()) + .put(BundleConstant.EXTRA_FOUR, item.getId()) + .put(BundleConstant.EXTRA, item.getBody()) + .put(BundleConstant.EXTRA_TYPE, BundleConstant.ExtraTYpe.EDIT_COMMIT_COMMENT_EXTRA) + .end()); + startActivityForResult(intent, BundleConstant.REQUEST_CODE); + } + + @Override public void onStartNewComment() { + onTagUser(null); + } + + @Override public void onHandleCommentDelete(@NonNull Response booleanResponse, long commId) { + hideProgress(); + if (booleanResponse.code() == 204) { + CommentsModel commentsModel = new CommentsModel(); + commentsModel.setId(commId); + adapter.removeItem(commentsModel); + } else { + showErrorMessage(getString(R.string.error_deleting_comment)); + } + } + + @Override public void onShowDeleteMsg(long id) { + MessageDialogView.newInstance(getString(R.string.delete), getString(R.string.confirm_message), + Bundler.start() + .put(BundleConstant.EXTRA, id) + .end()) + .show(getChildFragmentManager(), MessageDialogView.TAG); + } + + @Override public void onShowProgressDialog() { + callback.showProgress(0); + } + + @Override public void onTagUser(@Nullable UserModel user) { + Intent intent = new Intent(getContext(), EditorView.class); + intent.putExtras(Bundler + .start() + .put(BundleConstant.ID, getPresenter().repoId()) + .put(BundleConstant.EXTRA_TWO, getPresenter().login()) + .put(BundleConstant.EXTRA_THREE, getPresenter().sha()) + .put(BundleConstant.EXTRA, user != null ? "@" + user.getLogin() : "") + .put(BundleConstant.EXTRA_TYPE, BundleConstant.ExtraTYpe.NEW_COMMIT_COMMENT_EXTRA) + .end()); + startActivityForResult(intent, BundleConstant.REQUEST_CODE); + } + + @Override public void onDestroyView() { + recycler.removeOnScrollListener(getLoadMore()); + super.onDestroyView(); + } + + @Override public void onClick(View view) { + onRefresh(); + } + + @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + getPresenter().onActivityResult(requestCode, resultCode, data, recycler, adapter); + } + + @Override public void onMessageDialogActionClicked(boolean isOk, @Nullable Bundle bundle) { + super.onMessageDialogActionClicked(isOk, bundle); + if (isOk) { + getPresenter().onHandleDeletion(bundle); + } + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/files/CommitFilesMvp.java b/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/files/CommitFilesMvp.java new file mode 100644 index 00000000..7b0d9fb8 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/files/CommitFilesMvp.java @@ -0,0 +1,34 @@ +package com.fastaccess.ui.modules.repos.code.commit.details.files; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.fastaccess.data.dao.CommitFileListModel; +import com.fastaccess.data.dao.CommitFileModel; +import com.fastaccess.ui.adapter.CommitFilesAdapter; +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +/** + * Created by Kosh on 20 Nov 2016, 11:10 AM + */ + +interface CommitFilesMvp { + + interface View extends BaseMvp.FAView, CommitFilesAdapter.OnTogglePatch { + + void onNotifyAdapter(); + + } + + interface Presenter extends BaseMvp.FAPresenter, + BaseMvp.PaginationListener, BaseViewHolder.OnItemClickListener { + + void onFragmentCreated(@Nullable Bundle bundle); + + @NonNull CommitFileListModel getFiles(); + } + + +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/files/CommitFilesPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/files/CommitFilesPresenter.java new file mode 100644 index 00000000..4b5c248a --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/files/CommitFilesPresenter.java @@ -0,0 +1,64 @@ +package com.fastaccess.ui.modules.repos.code.commit.details.files; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; + +import com.fastaccess.data.dao.CommitFileListModel; +import com.fastaccess.data.dao.CommitFileModel; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; + +/** + * Created by Kosh on 15 Feb 2017, 10:10 PM + */ + +class CommitFilesPresenter extends BasePresenter implements CommitFilesMvp.Presenter { + + private CommitFileListModel files = new CommitFileListModel(); + + @Override public void onItemClick(int position, View v, CommitFileModel item) { + + } + + @Override public void onItemLongClick(int position, View v, CommitFileModel item) { + + } + + @Override public void onFragmentCreated(@Nullable Bundle bundle) { + if (bundle != null) { + CommitFileListModel files = (CommitFileListModel) bundle.get(BundleConstant.EXTRA); + if (files != null) { + this.files.addAll(files); + } + sendToView(CommitFilesMvp.View::onNotifyAdapter); + } else { + throw new NullPointerException("Bundle is null"); + } + } + + @NonNull @Override public CommitFileListModel getFiles() { + return files; + } + + @Override public int getCurrentPage() { + return 0; + } + + @Override public int getPreviousTotal() { + return 0; + } + + @Override public void setCurrentPage(int page) { + + } + + @Override public void setPreviousTotal(int previousTotal) { + + } + + @Override public void onCallApi(int page, @Nullable String parameter) { + + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/files/CommitFilesView.java b/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/files/CommitFilesView.java new file mode 100644 index 00000000..27b4cdf5 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/files/CommitFilesView.java @@ -0,0 +1,83 @@ +package com.fastaccess.ui.modules.repos.code.commit.details.files; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.data.dao.CommitFileListModel; +import com.fastaccess.data.dao.SparseBooleanArrayParcelable; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.ui.adapter.CommitFilesAdapter; +import com.fastaccess.ui.base.BaseFragment; +import com.fastaccess.ui.widgets.AppbarRefreshLayout; +import com.fastaccess.ui.widgets.StateLayout; +import com.fastaccess.ui.widgets.recyclerview.DynamicRecyclerView; + +import butterknife.BindView; +import hugo.weaving.DebugLog; +import icepick.State; + +/** + * Created by Kosh on 15 Feb 2017, 10:16 PM + */ + +public class CommitFilesView extends BaseFragment implements CommitFilesMvp.View { + + @BindView(R.id.recycler) DynamicRecyclerView recycler; + @BindView(R.id.refresh) AppbarRefreshLayout refresh; + @BindView(R.id.stateLayout) StateLayout stateLayout; + @State SparseBooleanArrayParcelable sparseBooleanArray; + + private CommitFilesAdapter adapter; + + public static CommitFilesView newInstance(@Nullable CommitFileListModel commitFileModels) { + CommitFilesView view = new CommitFilesView(); + view.setArguments(Bundler.start() + .putParcelableArrayList(BundleConstant.EXTRA, commitFileModels) + .end()); + return view; + } + + @Override public void onNotifyAdapter() { + stateLayout.hideProgress(); + adapter.notifyDataSetChanged(); + } + + @Override protected int fragmentLayout() { + return R.layout.small_grid_refresh_list; + } + + @Override protected void onFragmentCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + refresh.setEnabled(false); + recycler.setEmptyView(stateLayout, refresh); + adapter = new CommitFilesAdapter(getPresenter().getFiles(), this); + recycler.setAdapter(adapter); + if (savedInstanceState == null) { + sparseBooleanArray = new SparseBooleanArrayParcelable(); + getPresenter().onFragmentCreated(getArguments()); + } + } + + @NonNull @Override public CommitFilesPresenter providePresenter() { + return new CommitFilesPresenter(); + } + + @Override public void onToggle(int position, boolean isCollapsed) { + getSparseBooleanArray().put(position, isCollapsed); + adapter.notifyItemChanged(position); + } + + @DebugLog @Override public boolean isCollapsed(int position) { + return getSparseBooleanArray().get(position); + } + + public SparseBooleanArrayParcelable getSparseBooleanArray() { + if (sparseBooleanArray == null) { + sparseBooleanArray = new SparseBooleanArrayParcelable(); + } + return sparseBooleanArray; + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/code/contributors/RepoContributorsMvp.java b/app/src/main/java/com/fastaccess/ui/modules/repos/code/contributors/RepoContributorsMvp.java new file mode 100644 index 00000000..b5cc64af --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/code/contributors/RepoContributorsMvp.java @@ -0,0 +1,36 @@ +package com.fastaccess.ui.modules.repos.code.contributors; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.widget.SwipeRefreshLayout; + +import com.fastaccess.data.dao.UserModel; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.ArrayList; + +/** + * Created by Kosh on 03 Dec 2016, 3:45 PM + */ + +interface RepoContributorsMvp { + + interface View extends BaseMvp.FAView, SwipeRefreshLayout.OnRefreshListener, android.view.View.OnClickListener { + void onNotifyAdapter(); + + @NonNull OnLoadMore getLoadMore(); + } + + interface Presenter extends BaseMvp.FAPresenter, + BaseViewHolder.OnItemClickListener, + BaseMvp.PaginationListener { + + void onFragmentCreated(@NonNull Bundle bundle); + + void onWorkOffline(); + + @NonNull ArrayList getUsers(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/code/contributors/RepoContributorsPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/repos/code/contributors/RepoContributorsPresenter.java new file mode 100644 index 00000000..72d3578a --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/code/contributors/RepoContributorsPresenter.java @@ -0,0 +1,95 @@ +package com.fastaccess.ui.modules.repos.code.contributors; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; + +import com.fastaccess.data.dao.UserModel; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; + +import java.util.ArrayList; + +/** + * Created by Kosh on 03 Dec 2016, 3:48 PM + */ + +class RepoContributorsPresenter extends BasePresenter implements RepoContributorsMvp.Presenter { + + private ArrayList users = new ArrayList<>(); + private int page; + private int previousTotal; + private int lastPage = Integer.MAX_VALUE; + private String repoId; + private String login; + + @Override public int getCurrentPage() { + return page; + } + + @Override public int getPreviousTotal() { + return previousTotal; + } + + @Override public void setCurrentPage(int page) { + this.page = page; + } + + @Override public void setPreviousTotal(int previousTotal) { + this.previousTotal = previousTotal; + } + + @Override public void onCallApi(int page, @Nullable Object parameter) { + if (page == 1) { + lastPage = Integer.MAX_VALUE; + sendToView(view -> view.getLoadMore().reset()); + } + setCurrentPage(page); + if (page > lastPage || lastPage == 0) { + sendToView(RepoContributorsMvp.View::hideProgress); + return; + } + makeRestCall(RestProvider.getRepoService().getContributors(login, repoId, page), + response -> { + lastPage = response.getLast(); + if (getCurrentPage() == 1) { + getUsers().clear(); + manageSubscription(UserModel.saveContributors(response.getItems(), repoId).subscribe()); + } + getUsers().addAll(response.getItems()); + sendToView(RepoContributorsMvp.View::onNotifyAdapter); + }); + } + + @Override public void onFragmentCreated(@NonNull Bundle bundle) { + repoId = bundle.getString(BundleConstant.ID); + login = bundle.getString(BundleConstant.EXTRA); + if (!InputHelper.isEmpty(login) && !InputHelper.isEmpty(repoId)) { + onCallApi(1, null); + } + } + + @Override public void onWorkOffline() { + if (users.isEmpty()) { + manageSubscription(UserModel.getContributors(repoId) + .subscribe(userModels -> { + users.addAll(userModels); + sendToView(RepoContributorsMvp.View::onNotifyAdapter); + })); + } else { + sendToView(BaseMvp.FAView::hideProgress); + } + } + + @NonNull @Override public ArrayList getUsers() { + return users; + } + + @Override public void onItemClick(int position, View v, UserModel item) {} + + @Override public void onItemLongClick(int position, View v, UserModel item) {} +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/code/contributors/RepoContributorsView.java b/app/src/main/java/com/fastaccess/ui/modules/repos/code/contributors/RepoContributorsView.java new file mode 100644 index 00000000..f2da8c44 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/code/contributors/RepoContributorsView.java @@ -0,0 +1,104 @@ +package com.fastaccess.ui.modules.repos.code.contributors; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.v4.widget.SwipeRefreshLayout; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.adapter.UsersAdapter; +import com.fastaccess.ui.base.BaseFragment; +import com.fastaccess.ui.widgets.StateLayout; +import com.fastaccess.ui.widgets.recyclerview.DynamicRecyclerView; + +import butterknife.BindView; + +/** + * Created by Kosh on 03 Dec 2016, 3:56 PM + */ + +public class RepoContributorsView extends BaseFragment implements RepoContributorsMvp.View { + + @BindView(R.id.recycler) DynamicRecyclerView recycler; + @BindView(R.id.refresh) SwipeRefreshLayout refresh; + @BindView(R.id.stateLayout) StateLayout stateLayout; + private OnLoadMore onLoadMore; + private UsersAdapter adapter; + + public static RepoContributorsView newInstance(@NonNull String repoId, @NonNull String login) { + RepoContributorsView view = new RepoContributorsView(); + view.setArguments(Bundler.start() + .put(BundleConstant.ID, repoId) + .put(BundleConstant.EXTRA, login) + .end()); + return view; + } + + @Override public void onNotifyAdapter() { + hideProgress(); + adapter.notifyDataSetChanged(); + } + + @Override protected int fragmentLayout() { + return R.layout.small_grid_refresh_list; + } + + @Override protected void onFragmentCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + if (getArguments() == null) { + throw new NullPointerException("Bundle is null, therefore, issues can't be proceeded."); + } + stateLayout.setOnReloadListener(this); + refresh.setOnRefreshListener(this); + recycler.setEmptyView(stateLayout, refresh); + adapter = new UsersAdapter(getPresenter().getUsers(), true); + adapter.setListener(getPresenter()); + getLoadMore().setCurrent_page(getPresenter().getCurrentPage(), getPresenter().getPreviousTotal()); + recycler.setAdapter(adapter); + recycler.addOnScrollListener(getLoadMore()); + if (savedInstanceState == null) { + getPresenter().onFragmentCreated(getArguments()); + } else if (getPresenter().getUsers().isEmpty() && !getPresenter().isApiCalled()) { + onRefresh(); + } + } + + @NonNull @Override public RepoContributorsPresenter providePresenter() { + return new RepoContributorsPresenter(); + } + + @Override public void showProgress(@StringRes int resId) { + refresh.setRefreshing(true); + stateLayout.showProgress(); + } + + @Override public void hideProgress() { + refresh.setRefreshing(false); + stateLayout.hideProgress(); + } + + @Override public void showErrorMessage(@NonNull String msgRes) { + hideProgress(); + stateLayout.showReload(adapter.getItemCount()); + super.showErrorMessage(msgRes); + } + + @SuppressWarnings("unchecked") @NonNull @Override public OnLoadMore getLoadMore() { + if (onLoadMore == null) { + onLoadMore = new OnLoadMore(getPresenter()); + } + return onLoadMore; + } + + @Override public void onRefresh() { + getPresenter().onCallApi(1, getArguments().getString(BundleConstant.EXTRA)); + } + + @Override public void onClick(View view) { + onRefresh(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/code/files/RepoFilesMvp.java b/app/src/main/java/com/fastaccess/ui/modules/repos/code/files/RepoFilesMvp.java new file mode 100644 index 00000000..4f5bd604 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/code/files/RepoFilesMvp.java @@ -0,0 +1,44 @@ +package com.fastaccess.ui.modules.repos.code.files; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.widget.SwipeRefreshLayout; + +import com.fastaccess.data.dao.RepoFilesModel; +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.ArrayList; + +/** + * Created by Kosh on 20 Nov 2016, 11:10 AM + */ + +interface RepoFilesMvp { + + interface View extends BaseMvp.FAView, SwipeRefreshLayout.OnRefreshListener { + void onNotifyAdapter(); + + void onItemClicked(@NonNull RepoFilesModel model); + + void onMenuClicked(@NonNull RepoFilesModel model, android.view.View view); + + void onSetData(@NonNull String login, @NonNull String repoId, @Nullable String path); + + boolean isRefreshing(); + } + + interface Presenter extends BaseMvp.FAPresenter, + BaseViewHolder.OnItemClickListener { + + @NonNull ArrayList getFiles(); + + void onWorkOffline(); + + void onCallApi(); + + void onInitDataAndRequest(@NonNull String login, @NonNull String repoId, @Nullable String path); + } + + +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/code/files/RepoFilesPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/repos/code/files/RepoFilesPresenter.java new file mode 100644 index 00000000..1adbf73a --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/code/files/RepoFilesPresenter.java @@ -0,0 +1,82 @@ +package com.fastaccess.ui.modules.repos.code.files; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; + +import com.annimon.stream.Objects; +import com.annimon.stream.Stream; +import com.fastaccess.R; +import com.fastaccess.data.dao.RepoFilesModel; +import com.fastaccess.data.dao.types.FilesType; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; + +import java.util.ArrayList; + +import rx.Observable; + +/** + * Created by Kosh on 15 Feb 2017, 10:10 PM + */ + +class RepoFilesPresenter extends BasePresenter implements RepoFilesMvp.Presenter { + private ArrayList files = new ArrayList<>(); + private String repoId; + private String login; + private String path; + + @Override public void onItemClick(int position, View v, RepoFilesModel item) { + if (getView() == null) return; + if (v.getId() != R.id.menu) { + getView().onItemClicked(item); + } else { + getView().onMenuClicked(item, v); + } + } + + @Override public void onItemLongClick(int position, View v, RepoFilesModel item) { + onItemClick(position, v, item); + } + + @Override public T onError(@NonNull Throwable throwable, @NonNull Observable observable) { + onWorkOffline(); + return super.onError(throwable, observable); + } + + @NonNull @Override public ArrayList getFiles() { + return files; + } + + @Override public void onWorkOffline() { + if ((repoId == null || login == null) || !files.isEmpty()) return; + manageSubscription(RepoFilesModel.getFiles(login, repoId).subscribe( + models -> { + files.addAll(models); + sendToView(RepoFilesMvp.View::onNotifyAdapter); + } + )); + } + + @Override public void onCallApi() { + if (repoId == null || login == null) return; + makeRestCall(RestProvider.getRepoService().getRepoFiles(login, repoId, path), + response -> { + getFiles().clear(); + manageSubscription(RepoFilesModel.save(response.getItems(), login, repoId).subscribe()); + getFiles().addAll(Stream.of(response.getItems()).sortBy(model -> model.getType() == FilesType.file) + .collect(com.annimon.stream.Collectors.toList())); + sendToView(RepoFilesMvp.View::onNotifyAdapter); + }); + + } + + @Override public void onInitDataAndRequest(@NonNull String login, @NonNull String repoId, @Nullable String path) { + this.login = login; + this.repoId = repoId; + if (!Objects.toString(path, "").equalsIgnoreCase(this.path)) { + this.path = Objects.toString(path, ""); + onCallApi(); + } + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/code/files/RepoFilesView.java b/app/src/main/java/com/fastaccess/ui/modules/repos/code/files/RepoFilesView.java new file mode 100644 index 00000000..92b48247 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/code/files/RepoFilesView.java @@ -0,0 +1,145 @@ +package com.fastaccess.ui.modules.repos.code.files; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.view.MenuInflater; +import android.view.View; +import android.widget.PopupMenu; + +import com.fastaccess.R; +import com.fastaccess.data.dao.RepoFilesModel; +import com.fastaccess.data.dao.types.FilesType; +import com.fastaccess.helper.ActivityHelper; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.ui.adapter.RepoFilesAdapter; +import com.fastaccess.ui.base.BaseFragment; +import com.fastaccess.ui.modules.code.CodeViewerView; +import com.fastaccess.ui.modules.repos.code.files.paths.RepoFilePathView; +import com.fastaccess.ui.widgets.AppbarRefreshLayout; +import com.fastaccess.ui.widgets.StateLayout; +import com.fastaccess.ui.widgets.dialog.MessageDialogView; +import com.fastaccess.ui.widgets.recyclerview.DynamicRecyclerView; + +import butterknife.BindView; + +/** + * Created by Kosh on 18 Feb 2017, 2:10 AM + */ + +public class RepoFilesView extends BaseFragment implements RepoFilesMvp.View { + + @BindView(R.id.recycler) DynamicRecyclerView recycler; + @BindView(R.id.refresh) AppbarRefreshLayout refresh; + @BindView(R.id.stateLayout) StateLayout stateLayout; + private RepoFilesAdapter adapter; + private RepoFilePathView parentFragment; + + @Override public void onNotifyAdapter() { + hideProgress(); + adapter.notifyDataSetChanged(); + } + + @Override public void onItemClicked(@NonNull RepoFilesModel model) { + if (refresh.isRefreshing()) return; + if (model.getType() == FilesType.dir) { + if (getParent() != null) { + getParent().onAppendPath(model); + } + } else { + if (model.getSize() > 1000000 /* > 1mb */) { + MessageDialogView.newInstance(getString(R.string.big_file), getString(R.string.big_file_description), + Bundler.start().put(BundleConstant.EXTRA, model.getDownloadUrl()).end()) + .show(getChildFragmentManager(), "MessageDialogView"); + } else { + CodeViewerView.startActivity(getContext(), model.getDownloadUrl()); + } + } + } + + @Override public void onMenuClicked(@NonNull RepoFilesModel item, View v) { + if (refresh.isRefreshing()) return; + PopupMenu popup = new PopupMenu(getContext(), v); + MenuInflater inflater = popup.getMenuInflater(); + inflater.inflate(R.menu.download_share_menu, popup.getMenu()); + popup.getMenu().findItem(R.id.download).setVisible(item.getType() == FilesType.file); + popup.setOnMenuItemClickListener(item1 -> { + switch (item1.getItemId()) { + case R.id.share: + ActivityHelper.shareUrl(v.getContext(), item.getHtmlUrl()); + break; + case R.id.download: + RestProvider.downloadFile(getContext(), item.getDownloadUrl()); + break; + } + return true; + }); + popup.show(); + } + + @Override public void onSetData(@NonNull String login, @NonNull String repoId, @Nullable String path) { + getPresenter().onInitDataAndRequest(login, repoId, path); + } + + @Override public boolean isRefreshing() { + return refresh.isRefreshing(); + } + + @Override protected int fragmentLayout() { + return R.layout.vertical_refresh_list; + } + + @Override protected void onFragmentCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + refresh.setOnRefreshListener(this); + stateLayout.setOnReloadListener(v -> onRefresh()); + recycler.setEmptyView(stateLayout, refresh); + adapter = new RepoFilesAdapter(getPresenter().getFiles()); + adapter.setListener(getPresenter()); + recycler.setAdapter(adapter); + } + + @Override public void showProgress(@StringRes int resId) { + refresh.setRefreshing(true); + stateLayout.showProgress(); + } + + @Override public void hideProgress() { + refresh.setRefreshing(false); + stateLayout.hideProgress(); + } + + @Override public void showErrorMessage(@NonNull String msgRes) { + hideProgress(); + stateLayout.showReload(adapter.getItemCount()); + super.showErrorMessage(msgRes); + } + + @NonNull @Override public RepoFilesPresenter providePresenter() { + return new RepoFilesPresenter(); + } + + @Override public void onRefresh() { + getPresenter().onCallApi(); + } + + @Override public void onMessageDialogActionClicked(boolean isOk, @Nullable Bundle bundle) { + super.onMessageDialogActionClicked(isOk, bundle); + if (isOk && bundle != null) { + String url = bundle.getString(BundleConstant.EXTRA); + if (!InputHelper.isEmpty(url)) { + RestProvider.downloadFile(getContext(), url); + } + } + } + + private RepoFilePathView getParent() { + if (parentFragment == null) { + parentFragment = (RepoFilePathView) getParentFragment(); + } + return parentFragment; + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/code/files/paths/RepoFilePathMvp.java b/app/src/main/java/com/fastaccess/ui/modules/repos/code/files/paths/RepoFilePathMvp.java new file mode 100644 index 00000000..c05acece --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/code/files/paths/RepoFilePathMvp.java @@ -0,0 +1,44 @@ +package com.fastaccess.ui.modules.repos.code.files.paths; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.fastaccess.data.dao.RepoFilesModel; +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.ArrayList; + +/** + * Created by Kosh on 20 Nov 2016, 11:10 AM + */ + +interface RepoFilePathMvp { + + interface View extends BaseMvp.FAView { + void onNotifyAdapter(); + + void onItemClicked(@NonNull RepoFilesModel model, int position); + + void onAppendPath(@NonNull RepoFilesModel model); + + void onSendData(); + } + + interface Presenter extends BaseMvp.FAPresenter, + BaseViewHolder.OnItemClickListener { + + void onFragmentCreated(@Nullable Bundle bundle); + + @NonNull String getRepoId(); + + @NonNull String getLogin(); + + @Nullable String getPath(); + + @NonNull ArrayList getPaths(); + } + + +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/code/files/paths/RepoFilePathPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/repos/code/files/paths/RepoFilePathPresenter.java new file mode 100644 index 00000000..f994f6d0 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/code/files/paths/RepoFilePathPresenter.java @@ -0,0 +1,63 @@ +package com.fastaccess.ui.modules.repos.code.files.paths; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; + +import com.annimon.stream.Objects; +import com.fastaccess.data.dao.RepoFilesModel; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; + +import java.util.ArrayList; + +/** + * Created by Kosh on 15 Feb 2017, 10:10 PM + */ + +class RepoFilePathPresenter extends BasePresenter implements RepoFilePathMvp.Presenter { + private String repoId; + private String login; + private String path; + private ArrayList paths = new ArrayList<>(); + + @Override public void onItemClick(int position, View v, RepoFilesModel item) { + if (!item.getPath().equalsIgnoreCase(path)) if (getView() != null) getView().onItemClicked(item, position); + } + + @Override public void onItemLongClick(int position, View v, RepoFilesModel item) { + onItemClick(position, v, item); + } + + @Override public void onFragmentCreated(@Nullable Bundle bundle) { + if (bundle != null) { + repoId = bundle.getString(BundleConstant.ID); + login = bundle.getString(BundleConstant.EXTRA); + path = Objects.toString(bundle.getString(BundleConstant.EXTRA_TWO), ""); + if (InputHelper.isEmpty(repoId) || InputHelper.isEmpty(login)) { + throw new NullPointerException(String.format("error, repoId(%s) or login(%s) is null", repoId, login)); + } + sendToView(RepoFilePathMvp.View::onSendData); + } else { + throw new NullPointerException("Bundle is null"); + } + } + + @NonNull @Override public String getRepoId() { + return repoId; + } + + @NonNull @Override public String getLogin() { + return login; + } + + @Nullable @Override public String getPath() { + return path; + } + + @NonNull @Override public ArrayList getPaths() { + return paths; + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/code/files/paths/RepoFilePathView.java b/app/src/main/java/com/fastaccess/ui/modules/repos/code/files/paths/RepoFilePathView.java new file mode 100644 index 00000000..15b6bd39 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/code/files/paths/RepoFilePathView.java @@ -0,0 +1,107 @@ +package com.fastaccess.ui.modules.repos.code.files.paths; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.RecyclerView; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.data.dao.RepoFilesModel; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.ui.adapter.RepoFilePathsAdapter; +import com.fastaccess.ui.base.BaseFragment; +import com.fastaccess.ui.modules.repos.code.files.RepoFilesView; + +import butterknife.BindView; +import butterknife.OnClick; + +/** + * Created by Kosh on 18 Feb 2017, 2:10 AM + */ + +public class RepoFilePathView extends BaseFragment implements RepoFilePathMvp.View { + + @BindView(R.id.recycler) RecyclerView recycler; + @BindView(R.id.toParentFolder) View toParentFolder; + + private RepoFilePathsAdapter adapter; + private RepoFilesView repoFilesView; + + public static RepoFilePathView newInstance(@NonNull String login, @NonNull String repoId, @Nullable String path) { + RepoFilePathView view = new RepoFilePathView(); + view.setArguments(Bundler.start() + .put(BundleConstant.ID, repoId) + .put(BundleConstant.EXTRA, login) + .put(BundleConstant.EXTRA_TWO, path) + .end()); + return view; + } + + @OnClick(R.id.toParentFolder) void onBackClicked() { + getPresenter().getPaths().clear(); + onNotifyAdapter(); + getRepoFilesView().onSetData(getPresenter().getLogin(), getPresenter().getRepoId(), null); + } + + @Override public void onNotifyAdapter() { + adapter.notifyDataSetChanged(); + onShowHideBackBtn(); + } + + @Override public void onItemClicked(@NonNull RepoFilesModel model, int position) { + if (getRepoFilesView().isRefreshing()) return; // avoid calling for path while the other still loading... + if ((adapter.getItemCount() - 1) > position) { + adapter.subList(position + 1, adapter.getItemCount()); + } + getRepoFilesView().onSetData(getPresenter().getLogin(), getPresenter().getRepoId(), model.getPath()); + } + + @Override public void onAppendPath(@NonNull RepoFilesModel model) { + adapter.addItem(model); + onShowHideBackBtn(); + recycler.scrollToPosition(adapter.getItemCount() - 1); //smoothScrollToPosition(index) hides the recyclerview? WTF GOOGLE. + getRepoFilesView().onSetData(getPresenter().getLogin(), getPresenter().getRepoId(), model.getPath()); + } + + @Override public void onSendData() { + getRepoFilesView().onSetData(getPresenter().getLogin(), getPresenter().getRepoId(), getPresenter().getPath()); + } + + @Override protected int fragmentLayout() { + return R.layout.repo_file_layout; + } + + @Override protected void onFragmentCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + adapter = new RepoFilePathsAdapter(getPresenter().getPaths()); + adapter.setListener(getPresenter()); + recycler.setAdapter(adapter); + if (savedInstanceState == null) { + getPresenter().onFragmentCreated(getArguments()); + } + } + + @NonNull @Override public RepoFilePathPresenter providePresenter() { + return new RepoFilePathPresenter(); + } + + @NonNull public RepoFilesView getRepoFilesView() { + if (repoFilesView == null) { + repoFilesView = (RepoFilesView) getChildFragmentManager().findFragmentById(R.id.filesFragment); + } + return repoFilesView; + } + + private void onShowHideBackBtn() { + if (adapter.getItemCount() > 0) { + if (!toParentFolder.isShown()) { + toParentFolder.setVisibility(View.VISIBLE); + } + } else { + if (toParentFolder.isShown()) { + toParentFolder.setVisibility(View.GONE); + } + } + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/code/prettifier/ViewerMvp.java b/app/src/main/java/com/fastaccess/ui/modules/repos/code/prettifier/ViewerMvp.java new file mode 100644 index 00000000..017772e8 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/code/prettifier/ViewerMvp.java @@ -0,0 +1,49 @@ +package com.fastaccess.ui.modules.repos.code.prettifier; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; + +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.prettifier.pretty.PrettifyWebView; + +/** + * Created by Kosh on 27 Nov 2016, 3:41 PM + */ + +interface ViewerMvp { + + interface View extends BaseMvp.FAView, PrettifyWebView.OnContentChangedListener { + + void onSetImageUrl(@NonNull String url); + + void onSetMdText(@NonNull String text, String baseUrl); + + void onSetCode(@NonNull String text); + + void onShowError(@NonNull String msg); + + void onShowError(@StringRes int msg); + + void onShowMdProgress(); + + } + + interface Presenter extends BaseMvp.FAPresenter { + + void onHandleIntent(@Nullable Bundle intent); + + String downloadedStream(); + + boolean isMarkDown(); + + void onWorkOffline(); + + void onWorkOnline(); + + boolean isRepo(); + + boolean isImage(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/code/prettifier/ViewerPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/repos/code/prettifier/ViewerPresenter.java new file mode 100644 index 00000000..5d5af564 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/code/prettifier/ViewerPresenter.java @@ -0,0 +1,147 @@ +package com.fastaccess.ui.modules.repos.code.prettifier; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.fastaccess.R; +import com.fastaccess.data.dao.FileModel; +import com.fastaccess.data.dao.MarkdownModel; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.provider.markdown.MarkDownProvider; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; + +import rx.Observable; + +/** + * Created by Kosh on 27 Nov 2016, 3:43 PM + */ + +class ViewerPresenter extends BasePresenter implements ViewerMvp.Presenter { + private String downloadedStream; + private boolean isMarkdown; + private boolean isRepo; + private boolean isImage; + private String url; + + @Override public T onError(@NonNull Throwable throwable, @NonNull Observable observable) { + throwable.printStackTrace(); + int code = RestProvider.getErrorCode(throwable); + if (code == 404) { + sendToView(view -> view.onShowError(isRepo ? R.string.no_readme_found : R.string.no_file_found)); + } else { + sendToView(view -> view.onShowError(throwable.getMessage())); + } + onWorkOffline(); + return super.onError(throwable, observable); + } + + @Override public void onHandleIntent(@Nullable Bundle intent) { + if (intent == null) return; + isRepo = intent.getBoolean(BundleConstant.EXTRA); + url = intent.getString(BundleConstant.ITEM); + if (!InputHelper.isEmpty(url)) { + if (MarkDownProvider.isArchive(url)) { + sendToView(view -> view.onShowError(R.string.archive_file_detected_error)); + return; + } + if (isRepo) { + url = url.endsWith("/") ? (url + "readme") : (url + "/readme"); + } +// url = url.contains("/blobs/") ? url : url.endsWith("/") ? url : url + "/"; + onWorkOnline(); + } + } + + @Override public String downloadedStream() { + return downloadedStream; + } + + @Override public boolean isMarkDown() { + return isMarkdown; + } + + @Override public void onWorkOffline() { + if (downloadedStream == null) { + manageSubscription(FileModel.get(url) + .subscribe(fileModel -> { + if (fileModel == null) { + onWorkOnline(); + } else { + isImage = MarkDownProvider.isImage(fileModel.getFullUrl()); + if (isImage) { + sendToView(view -> view.onSetImageUrl(fileModel.getFullUrl())); + } else { + downloadedStream = fileModel.getContent(); + isRepo = fileModel.isRepo(); + isMarkdown = fileModel.isMarkdown(); + sendToView(view -> { + if (isRepo) { + view.onSetMdText(downloadedStream, fileModel.getFullUrl()); + } else if (isMarkdown) { + view.onSetMdText(downloadedStream, null); + } else { + view.onSetCode(downloadedStream); + } + }); + } + } + }, throwable -> sendToView(view -> view.showErrorMessage(throwable.getMessage())))); + } + } + + @Override public void onWorkOnline() { + isImage = MarkDownProvider.isImage(url); + if (isImage) { + sendToView(view -> view.onSetImageUrl(url)); + return; + } + makeRestCall(isRepo ? RestProvider.getRepoService().getReadmeHtml(url) : RestProvider.getRepoService().getFileAsStream(url), + content -> { + downloadedStream = content; + FileModel fileModel = new FileModel(); + fileModel.setContent(downloadedStream); + fileModel.setFullUrl(url); + fileModel.setRepo(isRepo); + if (isRepo) { + fileModel.setMarkdown(true); + isMarkdown = true; + isRepo = true; + sendToView(view -> view.onSetMdText(downloadedStream, url)); + } else { + isMarkdown = MarkDownProvider.isMarkdown(url); + if (isMarkdown) { + MarkdownModel model = new MarkdownModel(); + model.setText(downloadedStream); + makeRestCall(RestProvider.getRepoService().convertReadmeToHtml(model), + s -> { + isMarkdown = true; + downloadedStream = s; + fileModel.setMarkdown(true); + fileModel.setContent(downloadedStream); + manageSubscription(fileModel.save().subscribe()); + sendToView(view -> view.onSetMdText(downloadedStream, url)); + }); + return; + } + fileModel.setMarkdown(false); + if (isMarkdown) { + sendToView(view -> view.onSetMdText(downloadedStream, null)); + } else { + sendToView(view -> view.onSetCode(downloadedStream)); + } + } + manageSubscription(fileModel.save().subscribe()); + }); + } + + @Override public boolean isRepo() { + return isRepo; + } + + @Override public boolean isImage() { + return isImage; + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/code/prettifier/ViewerView.java b/app/src/main/java/com/fastaccess/ui/modules/repos/code/prettifier/ViewerView.java new file mode 100644 index 00000000..b16d5ec7 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/code/prettifier/ViewerView.java @@ -0,0 +1,118 @@ +package com.fastaccess.ui.modules.repos.code.prettifier; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.ui.base.BaseFragment; +import com.fastaccess.ui.widgets.StateLayout; +import com.prettifier.pretty.PrettifyWebView; + +import butterknife.BindView; + +/** + * Created by Kosh on 28 Nov 2016, 9:27 PM + */ + +public class ViewerView extends BaseFragment implements ViewerMvp.View { + + public static final String TAG = ViewerView.class.getSimpleName(); + + @BindView(R.id.webView) PrettifyWebView webView; + @BindView(R.id.stateLayout) StateLayout stateLayout; + + public static ViewerView newInstance(@NonNull String url) { + return newInstance(url, false); + } + + public static ViewerView newInstance(@NonNull String url, boolean isRepo) { + return newInstance(Bundler.start() + .put(BundleConstant.ITEM, url) + .put(BundleConstant.EXTRA, isRepo) + .end()); + } + + private static ViewerView newInstance(@NonNull Bundle bundle) { + ViewerView fragmentView = new ViewerView(); + fragmentView.setArguments(bundle); + return fragmentView; + } + + @Override public void onSetImageUrl(@NonNull String url) { + onShowMdProgress(); + webView.loadImage(url); + webView.setOnContentChangedListener(this); + webView.setVisibility(View.VISIBLE); + } + + @Override public void onSetMdText(@NonNull String text, String baseUrl) { + stateLayout.hideProgress(); + webView.setVisibility(View.VISIBLE); + webView.setGithubContent(text, baseUrl); + } + + @Override public void onSetCode(@NonNull String text) { + stateLayout.hideProgress(); + webView.setVisibility(View.VISIBLE); + webView.setSource(text); + } + + @Override public void onShowError(@NonNull String msg) { + stateLayout.hideProgress(); + showErrorMessage(msg); + } + + @Override public void onShowError(@StringRes int msg) { + stateLayout.hideProgress(); + onShowError(getString(msg)); + } + + @Override public void onShowMdProgress() { + stateLayout.showProgress(); + } + + @Override public void showProgress(@StringRes int resId) { + onShowMdProgress(); + } + + @Override public void hideProgress() { + stateLayout.hideProgress(); + } + + @Override public void showErrorMessage(@NonNull String msgRes) { + stateLayout.hideProgress(); + super.showErrorMessage(msgRes); + } + + @Override protected int fragmentLayout() { + return R.layout.general_viewer_layout; + } + + @NonNull @Override public ViewerPresenter providePresenter() { + return new ViewerPresenter(); + } + + @Override public void onContentChanged(int progress) { + if (progress == 100) { + if (stateLayout != null) stateLayout.hideProgress(); + } + } + + @Override protected void onFragmentCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + if (InputHelper.isEmpty(getPresenter().downloadedStream())) { + getPresenter().onHandleIntent(getArguments()); + } else { + if (getPresenter().isMarkDown()) { + onSetMdText(getPresenter().downloadedStream(), getArguments().getString(BundleConstant.EXTRA)); + } else { + onSetCode(getPresenter().downloadedStream()); + } + } + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/code/releases/RepoReleasesMvp.java b/app/src/main/java/com/fastaccess/ui/modules/repos/code/releases/RepoReleasesMvp.java new file mode 100644 index 00000000..96506a17 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/code/releases/RepoReleasesMvp.java @@ -0,0 +1,43 @@ +package com.fastaccess.ui.modules.repos.code.releases; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.widget.SwipeRefreshLayout; + +import com.fastaccess.data.dao.ReleasesModel; +import com.fastaccess.data.dao.SimpleUrlsModel; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.widgets.dialog.ListDialogView; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.ArrayList; + +/** + * Created by Kosh on 03 Dec 2016, 3:45 PM + */ + +interface RepoReleasesMvp { + + interface View extends BaseMvp.FAView, SwipeRefreshLayout.OnRefreshListener, + android.view.View.OnClickListener, ListDialogView.onSimpleItemSelection { + void onNotifyAdapter(); + + @NonNull OnLoadMore getLoadMore(); + + void onDownload(@NonNull ReleasesModel item); + + void onShowDetails(@NonNull ReleasesModel item); + } + + interface Presenter extends BaseMvp.FAPresenter, + BaseViewHolder.OnItemClickListener, + BaseMvp.PaginationListener { + + void onFragmentCreated(@NonNull Bundle bundle); + + void onWorkOffline(); + + @NonNull ArrayList getReleases(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/code/releases/RepoReleasesPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/repos/code/releases/RepoReleasesPresenter.java new file mode 100644 index 00000000..f6a2623a --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/code/releases/RepoReleasesPresenter.java @@ -0,0 +1,113 @@ +package com.fastaccess.ui.modules.repos.code.releases; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.data.dao.ReleasesModel; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; + +import java.util.ArrayList; + +import rx.Observable; + +/** + * Created by Kosh on 03 Dec 2016, 3:48 PM + */ + +class RepoReleasesPresenter extends BasePresenter implements RepoReleasesMvp.Presenter { + + private ArrayList releases = new ArrayList<>(); + private String login; + private String repoId; + private int page; + private int previousTotal; + private int lastPage = Integer.MAX_VALUE; + + @Override public int getCurrentPage() { + return page; + } + + @Override public int getPreviousTotal() { + return previousTotal; + } + + @Override public void setCurrentPage(int page) { + this.page = page; + } + + @Override public void setPreviousTotal(int previousTotal) { + this.previousTotal = previousTotal; + } + + @Override public T onError(@NonNull Throwable throwable, @NonNull Observable observable) { + onWorkOffline(); + return super.onError(throwable, observable); + } + + @Override public void onCallApi(int page, @Nullable Object parameter) { + if (page == 1) { + lastPage = Integer.MAX_VALUE; + sendToView(view -> view.getLoadMore().reset()); + } + setCurrentPage(page); + if (page > lastPage || lastPage == 0) { + sendToView(RepoReleasesMvp.View::hideProgress); + return; + } + if (repoId == null || login == null) return; + makeRestCall(RestProvider.getRepoService().getReleases(login, repoId, page), + response -> { + lastPage = response.getLast(); + if (getCurrentPage() == 1) { + getReleases().clear(); + manageSubscription(ReleasesModel.save(response.getItems(), repoId, login).subscribe()); + } + getReleases().addAll(response.getItems()); + sendToView(RepoReleasesMvp.View::onNotifyAdapter); + }); + + } + + @Override public void onFragmentCreated(@NonNull Bundle bundle) { + repoId = bundle.getString(BundleConstant.ID); + login = bundle.getString(BundleConstant.EXTRA); + if (!InputHelper.isEmpty(login) && !InputHelper.isEmpty(repoId)) { + onCallApi(1, null); + } + } + + @Override public void onWorkOffline() { + if (releases.isEmpty()) { + manageSubscription(ReleasesModel.get(repoId, login) + .subscribe(releasesModels -> { + releases.addAll(releasesModels); + sendToView(RepoReleasesMvp.View::onNotifyAdapter); + })); + } else { + sendToView(RepoReleasesMvp.View::hideProgress); + } + } + + @NonNull @Override public ArrayList getReleases() { + return releases; + } + + @Override public void onItemClick(int position, View v, ReleasesModel item) { + if (getView() == null) return; + if (v.getId() == R.id.download) { + getView().onDownload(item); + } else { + getView().onShowDetails(item); + } + } + + @Override public void onItemLongClick(int position, View v, ReleasesModel item) { + onItemClick(position, v, item); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/code/releases/RepoReleasesView.java b/app/src/main/java/com/fastaccess/ui/modules/repos/code/releases/RepoReleasesView.java new file mode 100644 index 00000000..03128e58 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/code/releases/RepoReleasesView.java @@ -0,0 +1,135 @@ +package com.fastaccess.ui.modules.repos.code.releases; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.v4.widget.SwipeRefreshLayout; +import android.view.View; + +import com.annimon.stream.Collectors; +import com.annimon.stream.Stream; +import com.fastaccess.R; +import com.fastaccess.data.dao.ReleasesModel; +import com.fastaccess.data.dao.SimpleUrlsModel; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.adapter.ReleasesAdapter; +import com.fastaccess.ui.base.BaseFragment; +import com.fastaccess.ui.widgets.StateLayout; +import com.fastaccess.ui.widgets.dialog.ListDialogView; +import com.fastaccess.ui.widgets.dialog.MessageDialogView; +import com.fastaccess.ui.widgets.recyclerview.DynamicRecyclerView; + +import java.util.ArrayList; + +import butterknife.BindView; + +/** + * Created by Kosh on 03 Dec 2016, 3:56 PM + */ + +public class RepoReleasesView extends BaseFragment implements RepoReleasesMvp.View { + @BindView(R.id.recycler) DynamicRecyclerView recycler; + @BindView(R.id.refresh) SwipeRefreshLayout refresh; + @BindView(R.id.stateLayout) StateLayout stateLayout; + private OnLoadMore onLoadMore; + private ReleasesAdapter adapter; + + public static RepoReleasesView newInstance(@NonNull String repoId, @NonNull String login) { + RepoReleasesView view = new RepoReleasesView(); + view.setArguments(Bundler.start() + .put(BundleConstant.ID, repoId) + .put(BundleConstant.EXTRA, login) + .end()); + return view; + } + + @Override public void onNotifyAdapter() { + hideProgress(); + adapter.notifyDataSetChanged(); + } + + @Override protected int fragmentLayout() { + return R.layout.small_grid_refresh_list; + } + + @Override protected void onFragmentCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + if (getArguments() == null) { + throw new NullPointerException("Bundle is null, therefore, issues can't be proceeded."); + } + stateLayout.setOnReloadListener(this); + refresh.setOnRefreshListener(this); + recycler.setEmptyView(stateLayout, refresh); + adapter = new ReleasesAdapter(getPresenter().getReleases()); + adapter.setListener(getPresenter()); + getLoadMore().setCurrent_page(getPresenter().getCurrentPage(), getPresenter().getPreviousTotal()); + recycler.setAdapter(adapter); + recycler.addOnScrollListener(getLoadMore()); + if (savedInstanceState == null) { + getPresenter().onFragmentCreated(getArguments()); + } else if (getPresenter().getReleases().isEmpty() && !getPresenter().isApiCalled()) { + onRefresh(); + } + } + + @NonNull @Override public RepoReleasesPresenter providePresenter() { + return new RepoReleasesPresenter(); + } + + @SuppressWarnings("unchecked") @NonNull @Override public OnLoadMore getLoadMore() { + if (onLoadMore == null) { + onLoadMore = new OnLoadMore(getPresenter()); + } + return onLoadMore; + } + + @Override public void showProgress(@StringRes int resId) { + refresh.setRefreshing(true); + stateLayout.showProgress(); + } + + @Override public void hideProgress() { + refresh.setRefreshing(false); + stateLayout.hideProgress(); + } + + @Override public void showErrorMessage(@NonNull String msgRes) { + hideProgress(); + stateLayout.showReload(adapter.getItemCount()); + super.showErrorMessage(msgRes); + } + + @Override public void onDownload(@NonNull ReleasesModel item) { + ListDialogView dialogView = new ListDialogView<>(); + dialogView.initArguments(getString(R.string.releases), + Stream.of(new SimpleUrlsModel(getString(R.string.download_as_zip), item.getZipBallUrl()), + new SimpleUrlsModel(getString(R.string.download_as_tar), item.getTarballUrl())) + .collect(Collectors.toCollection(ArrayList::new))); + dialogView.show(getChildFragmentManager(), "ListDialogView"); + } + + @Override public void onShowDetails(@NonNull ReleasesModel item) { + if (!InputHelper.isEmpty(item.getBody())) { + MessageDialogView.newInstance(!InputHelper.isEmpty(item.getName()) ? item.getName() : item.getTagName(), + item.getBody(), true).show(getChildFragmentManager(), "MessageDialogView"); + } else { + showErrorMessage(getString(R.string.no_body)); + } + } + + @Override public void onRefresh() { + getPresenter().onCallApi(1, null); + } + + @Override public void onClick(View view) { + onRefresh(); + } + + @Override public void onItemSelected(SimpleUrlsModel item) { + RestProvider.downloadFile(getContext(), item.getUrl()); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/issues/RepoIssuesPagerMvp.java b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/RepoIssuesPagerMvp.java new file mode 100644 index 00000000..4eb534f7 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/RepoIssuesPagerMvp.java @@ -0,0 +1,14 @@ +package com.fastaccess.ui.modules.repos.issues; + +import com.fastaccess.ui.base.mvp.BaseMvp; + +/** + * Created by Kosh on 31 Dec 2016, 1:35 AM + */ + +interface RepoIssuesPagerMvp { + + interface View extends BaseMvp.FAView {} + + interface Presenter extends BaseMvp.FAPresenter {} +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/issues/RepoIssuesPagerPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/RepoIssuesPagerPresenter.java new file mode 100644 index 00000000..e5683fb2 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/RepoIssuesPagerPresenter.java @@ -0,0 +1,9 @@ +package com.fastaccess.ui.modules.repos.issues; + +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; + +/** + * Created by Kosh on 31 Dec 2016, 1:36 AM + */ + +class RepoIssuesPagerPresenter extends BasePresenter implements RepoIssuesPagerMvp.Presenter {} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/issues/RepoIssuesPagerView.java b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/RepoIssuesPagerView.java new file mode 100644 index 00000000..d850f4be --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/RepoIssuesPagerView.java @@ -0,0 +1,56 @@ +package com.fastaccess.ui.modules.repos.issues; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.TabLayout; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.data.dao.FragmentPagerAdapterModel; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.ui.adapter.FragmentsPagerAdapter; +import com.fastaccess.ui.base.BaseFragment; +import com.fastaccess.ui.widgets.ViewPagerView; + +import butterknife.BindView; + +/** + * Created by Kosh on 31 Dec 2016, 1:36 AM + */ + +public class RepoIssuesPagerView extends BaseFragment implements RepoIssuesPagerMvp.View { + + + public static final String TAG = RepoIssuesPagerView.class.getSimpleName(); + + @BindView(R.id.tabs) TabLayout tabs; + @BindView(R.id.pager) ViewPagerView pager; + + public static RepoIssuesPagerView newInstance(@NonNull String repoId, @NonNull String login) { + RepoIssuesPagerView view = new RepoIssuesPagerView(); + view.setArguments(Bundler.start() + .put(BundleConstant.ID, repoId) + .put(BundleConstant.EXTRA, login) + .end()); + return view; + } + + @Override protected int fragmentLayout() { + return R.layout.centered_tabbed_viewpager; + } + + @Override protected void onFragmentCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + String repoId = getArguments().getString(BundleConstant.ID); + String login = getArguments().getString(BundleConstant.EXTRA); + if (login == null || repoId == null) throw new NullPointerException("repoId || login is null???"); + pager.setAdapter(new FragmentsPagerAdapter(getChildFragmentManager(), + FragmentPagerAdapterModel.buildForRepoIssue(getContext(), login, repoId))); + tabs.setupWithViewPager(pager); + } + + @NonNull @Override public RepoIssuesPagerPresenter providePresenter() { + return new RepoIssuesPagerPresenter(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/RepoIssuesMvp.java b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/RepoIssuesMvp.java new file mode 100644 index 00000000..2daed7dd --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/RepoIssuesMvp.java @@ -0,0 +1,37 @@ +package com.fastaccess.ui.modules.repos.issues.issue; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.widget.SwipeRefreshLayout; + +import com.fastaccess.data.dao.IssueModel; +import com.fastaccess.data.dao.types.IssueState; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.ArrayList; + +/** + * Created by Kosh on 03 Dec 2016, 3:45 PM + */ + +interface RepoIssuesMvp { + + interface View extends BaseMvp.FAView, SwipeRefreshLayout.OnRefreshListener, android.view.View.OnClickListener { + void onNotifyAdapter(); + + @NonNull OnLoadMore getLoadMore(); + } + + interface Presenter extends BaseMvp.FAPresenter, + BaseViewHolder.OnItemClickListener, + BaseMvp.PaginationListener { + + void onFragmentCreated(@NonNull Bundle bundle); + + void onWorkOffline(); + + @NonNull ArrayList getIssues(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/RepoIssuesPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/RepoIssuesPresenter.java new file mode 100644 index 00000000..ad0d0674 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/RepoIssuesPresenter.java @@ -0,0 +1,120 @@ +package com.fastaccess.ui.modules.repos.issues.issue; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; + +import com.annimon.stream.Collectors; +import com.annimon.stream.Stream; +import com.fastaccess.data.dao.IssueModel; +import com.fastaccess.data.dao.PullsIssuesParser; +import com.fastaccess.data.dao.types.IssueState; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.helper.Logger; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; +import com.fastaccess.ui.modules.repos.issues.issue.details.IssuePagerView; + +import java.util.ArrayList; + +import rx.Observable; + +/** + * Created by Kosh on 03 Dec 2016, 3:48 PM + */ + +class RepoIssuesPresenter extends BasePresenter implements RepoIssuesMvp.Presenter { + + private ArrayList issues = new ArrayList<>(); + private String login; + private String repoId; + private int page; + private int previousTotal; + private int lastPage = Integer.MAX_VALUE; + private IssueState issueState; + + @Override public int getCurrentPage() { + return page; + } + + @Override public int getPreviousTotal() { + return previousTotal; + } + + @Override public void setCurrentPage(int page) { + this.page = page; + } + + @Override public void setPreviousTotal(int previousTotal) { + this.previousTotal = previousTotal; + } + + @Override public T onError(@NonNull Throwable throwable, @NonNull Observable observable) { + onWorkOffline(); + return super.onError(throwable, observable); + } + + @Override public void onCallApi(int page, @Nullable IssueState parameter) { + if (page == 1) { + lastPage = Integer.MAX_VALUE; + sendToView(view -> view.getLoadMore().reset()); + } + setCurrentPage(page); + if (page > lastPage || lastPage == 0) { + sendToView(RepoIssuesMvp.View::hideProgress); + return; + } + if (repoId == null || login == null) return; + makeRestCall(RestProvider.getIssueService().getRepositoryIssues(login, repoId, issueState.name(), page), + issues -> { + if (getCurrentPage() == 1) { + getIssues().clear(); + manageSubscription(IssueModel.save(issues.getItems(), repoId, login).subscribe()); + } + getIssues().addAll(Stream.of(issues.getItems()).filter(value -> value.getPullRequest() == null).collect(Collectors.toList())); + sendToView(RepoIssuesMvp.View::onNotifyAdapter); + }); + } + + @Override public void onFragmentCreated(@NonNull Bundle bundle) { + repoId = bundle.getString(BundleConstant.ID); + login = bundle.getString(BundleConstant.EXTRA); + issueState = (IssueState) bundle.getSerializable(BundleConstant.EXTRA_TWO); + if (!InputHelper.isEmpty(login) && !InputHelper.isEmpty(repoId)) { + onCallApi(1, null); + } + } + + @Override public void onWorkOffline() { + if (issues.isEmpty()) { + manageSubscription(IssueModel.getIssues(repoId, login, issueState) + .subscribe(issueModel -> { + issues.addAll(issueModel); + sendToView(RepoIssuesMvp.View::onNotifyAdapter); + })); + } else { + sendToView(BaseMvp.FAView::hideProgress); + } + } + + @NonNull @Override public ArrayList getIssues() { + return issues; + } + + @Override public void onItemClick(int position, View v, IssueModel item) { + Logger.e(Bundler.start().put("item", item).end().size()); + PullsIssuesParser parser = PullsIssuesParser.getForIssue(item.getHtmlUrl()); + if (parser != null) { + v.getContext().startActivity(IssuePagerView.createIntent(v.getContext(), parser.getRepoId(), + parser.getLogin(), parser.getNumber())); + } + } + + @Override public void onItemLongClick(int position, View v, IssueModel item) { + onItemClick(position, v, item); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/RepoIssuesView.java b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/RepoIssuesView.java new file mode 100644 index 00000000..70740422 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/RepoIssuesView.java @@ -0,0 +1,107 @@ +package com.fastaccess.ui.modules.repos.issues.issue; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.v4.widget.SwipeRefreshLayout; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.data.dao.types.IssueState; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.helper.Logger; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.adapter.IssuesAdapter; +import com.fastaccess.ui.base.BaseFragment; +import com.fastaccess.ui.widgets.StateLayout; +import com.fastaccess.ui.widgets.recyclerview.DynamicRecyclerView; + +import butterknife.BindView; + +/** + * Created by Kosh on 03 Dec 2016, 3:56 PM + */ + +public class RepoIssuesView extends BaseFragment implements RepoIssuesMvp.View { + @BindView(R.id.recycler) DynamicRecyclerView recycler; + @BindView(R.id.refresh) SwipeRefreshLayout refresh; + @BindView(R.id.stateLayout) StateLayout stateLayout; + private OnLoadMore onLoadMore; + private IssuesAdapter adapter; + + public static RepoIssuesView newInstance(@NonNull String repoId, @NonNull String login, @NonNull IssueState issueState) { + RepoIssuesView view = new RepoIssuesView(); + view.setArguments(Bundler.start() + .put(BundleConstant.ID, repoId) + .put(BundleConstant.EXTRA, login) + .put(BundleConstant.EXTRA_TWO, issueState) + .end()); + return view; + } + + @Override public void onNotifyAdapter() { + Logger.e(); + hideProgress(); + adapter.notifyDataSetChanged(); + } + + @Override protected int fragmentLayout() { + return R.layout.small_grid_refresh_list; + } + + @Override protected void onFragmentCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + if (getArguments() == null) { + throw new NullPointerException("Bundle is null, therefore, issues can't be proceeded."); + } + stateLayout.setOnReloadListener(this); + refresh.setOnRefreshListener(this); + recycler.setEmptyView(stateLayout, refresh); + adapter = new IssuesAdapter(getPresenter().getIssues(), true); + adapter.setListener(getPresenter()); + getLoadMore().setCurrent_page(getPresenter().getCurrentPage(), getPresenter().getPreviousTotal()); + recycler.setAdapter(adapter); + recycler.addOnScrollListener(getLoadMore()); + if (savedInstanceState == null) { + getPresenter().onFragmentCreated(getArguments()); + } else if (getPresenter().getIssues().isEmpty() && !getPresenter().isApiCalled()) { + onRefresh(); + } + } + + @NonNull @Override public RepoIssuesPresenter providePresenter() { + return new RepoIssuesPresenter(); + } + + @Override public void hideProgress() { + refresh.setRefreshing(false); + stateLayout.hideProgress(); + } + + @Override public void showProgress(@StringRes int resId) { + refresh.setRefreshing(true); + stateLayout.showProgress(); + } + + @Override public void showErrorMessage(@NonNull String message) { + hideProgress(); + stateLayout.showReload(adapter.getItemCount()); + super.showErrorMessage(message); + } + + @NonNull @Override public OnLoadMore getLoadMore() { + if (onLoadMore == null) { + onLoadMore = new OnLoadMore<>(getPresenter()); + } + return onLoadMore; + } + + @Override public void onRefresh() { + getPresenter().onCallApi(1, null); + } + + @Override public void onClick(View view) { + onRefresh(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/details/IssuePagerMvp.java b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/details/IssuePagerMvp.java new file mode 100644 index 00000000..8b5753d8 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/details/IssuePagerMvp.java @@ -0,0 +1,44 @@ +package com.fastaccess.ui.modules.repos.issues.issue.details; + +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.fastaccess.data.dao.IssueModel; +import com.fastaccess.ui.base.mvp.BaseMvp; + +/** + * Created by Kosh on 10 Dec 2016, 9:21 AM + */ + +interface IssuePagerMvp { + + interface View extends BaseMvp.FAView { + void onSetupIssue(); + + void showSuccessIssueActionMsg(boolean isClose); + + void showErrorIssueActionMsg(boolean isClose); + } + + interface Presenter extends BaseMvp.FAPresenter { + + @Nullable IssueModel getIssue(); + + void onActivityCreated(@Nullable Intent intent); + + void onWorkOffline(long issueNumber, @NonNull String repoId, @NonNull String login); + + boolean isOwner(); + + boolean isLocked(); + + void onHandleConfirmDialog(@Nullable Bundle bundle); + + void onOpenCloseIssue(); + + void onLockUnlockIssue(); + } + +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/details/IssuePagerPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/details/IssuePagerPresenter.java new file mode 100644 index 00000000..5635af94 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/details/IssuePagerPresenter.java @@ -0,0 +1,149 @@ +package com.fastaccess.ui.modules.repos.issues.issue.details; + +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.fastaccess.data.dao.IssueModel; +import com.fastaccess.data.dao.IssueRequestModel; +import com.fastaccess.data.dao.LoginModel; +import com.fastaccess.data.dao.PullsIssuesParser; +import com.fastaccess.data.dao.UserModel; +import com.fastaccess.data.dao.types.IssueState; +import com.fastaccess.data.service.IssueService; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.helper.RxHelper; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; + +import retrofit2.Response; +import rx.Observable; + +/** + * Created by Kosh on 10 Dec 2016, 9:23 AM + */ + +class IssuePagerPresenter extends BasePresenter implements IssuePagerMvp.Presenter { + private IssueModel issueModel; + private int issueNumber; + private String login; + private String repoId; + + @Nullable @Override public IssueModel getIssue() { + return issueModel; + } + + @Override public T onError(@NonNull Throwable throwable, @NonNull Observable observable) { + onWorkOffline(issueNumber, login, repoId); + return super.onError(throwable, observable); + } + + @Override public void onActivityCreated(@Nullable Intent intent) { + if (intent != null && intent.getExtras() != null) { + issueModel = intent.getExtras().getParcelable(BundleConstant.ITEM); + issueNumber = intent.getExtras().getInt(BundleConstant.ID); + login = intent.getExtras().getString(BundleConstant.EXTRA); + repoId = intent.getExtras().getString(BundleConstant.EXTRA_TWO); + if (issueModel != null) { + issueNumber = issueModel.getNumber(); + sendToView(IssuePagerMvp.View::onSetupIssue); + return; + } else if (issueNumber > 0 && !InputHelper.isEmpty(login) && !InputHelper.isEmpty(repoId)) { + makeRestCall(RestProvider.getIssueService().getIssue(login, repoId, issueNumber), + issue -> { + issueModel = issue; + issueModel.setRepoId(repoId); + issueModel.setLogin(login); + sendToView(IssuePagerMvp.View::onSetupIssue); + }); + return; + } + } + sendToView(IssuePagerMvp.View::onSetupIssue); + } + + @Override public void onWorkOffline(long issueNumber, @NonNull String repoId, @NonNull String login) { + if (issueModel == null) { + manageSubscription(IssueModel.getIssueByNumber((int) issueNumber).subscribe(issueModel1 -> { + if (issueModel1 != null) { + issueModel = issueModel1; + sendToView(IssuePagerMvp.View::onSetupIssue); + } + })); + } else { + sendToView(BaseMvp.FAView::hideProgress); + } + } + + @Override public boolean isOwner() { + if (getIssue() == null) return false; + UserModel userModel = getIssue() != null ? getIssue().getUser() : null; + LoginModel me = LoginModel.getUser(); + PullsIssuesParser parser = PullsIssuesParser.getForIssue(getIssue().getHtmlUrl()); + return userModel != null && userModel.getLogin().equalsIgnoreCase(me.getLogin()) + || (parser != null && parser.getLogin().equalsIgnoreCase(me.getLogin())); + } + + @Override public boolean isLocked() { + return getIssue() != null && getIssue().isLocked(); + } + + @Override public void onHandleConfirmDialog(@Nullable Bundle bundle) { + if (bundle != null) { + boolean proceed = bundle.getBoolean(BundleConstant.EXTRA); + boolean proceedLockUnlock = bundle.getBoolean(BundleConstant.EXTRA_TWO); + if (proceed) { + onOpenCloseIssue(); + } else if (proceedLockUnlock) { + onLockUnlockIssue(); + } + } + } + + @Override public void onOpenCloseIssue() { + IssueModel currentIssue = getIssue(); + if (currentIssue != null) { + IssueRequestModel requestModel = IssueRequestModel.clone(currentIssue); + manageSubscription(RxHelper.getObserver(RestProvider.getIssueService().editIssue(currentIssue.getUser().getLogin(), + currentIssue.getRepoId(), currentIssue.getNumber(), requestModel)) + .doOnSubscribe(() -> sendToView(view -> view.showProgress(0))) + .doOnNext(issue -> { + if (issue != null) { + sendToView(view -> view.showSuccessIssueActionMsg(currentIssue.getState() == IssueState.open)); + issue.setRepoId(issueModel.getRepoId()); + issue.setLogin(issueModel.getLogin()); + issueModel = issue; + sendToView(IssuePagerMvp.View::onSetupIssue); + } + }) + .onErrorReturn(throwable -> { + sendToView(view -> view.showErrorIssueActionMsg(currentIssue.getState() == IssueState.open)); + return null; + }) + .subscribe()); + } + } + + @Override public void onLockUnlockIssue() { + IssueModel currentIssue = getIssue(); + if (currentIssue == null) return; + String login = currentIssue.getUser().getLogin(); + String repoId = currentIssue.getRepoId(); + int number = currentIssue.getNumber(); + IssueService issueService = RestProvider.getIssueService(); + Observable> observable = RxHelper + .getObserver(isLocked() ? issueService.unlockIssue(login, repoId, number) : issueService.lockIssue(login, repoId, number)); + makeRestCall(observable, booleanResponse -> { + int code = booleanResponse.code(); + if (code == 204) { + issueModel.setLocked(!isLocked()); + sendToView(IssuePagerMvp.View::onSetupIssue); + } + sendToView(IssuePagerMvp.View::hideProgress); + }); + + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/details/IssuePagerView.java b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/details/IssuePagerView.java new file mode 100644 index 00000000..c5353663 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/details/IssuePagerView.java @@ -0,0 +1,222 @@ +package com.fastaccess.ui.modules.repos.issues.issue.details; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.FloatingActionButton; +import android.support.design.widget.TabLayout; +import android.support.v4.view.ViewPager; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.data.dao.FragmentPagerAdapterModel; +import com.fastaccess.data.dao.IssueModel; +import com.fastaccess.data.dao.UserModel; +import com.fastaccess.data.dao.types.IssueState; +import com.fastaccess.helper.ActivityHelper; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.helper.Logger; +import com.fastaccess.helper.ParseDateFormat; +import com.fastaccess.ui.adapter.FragmentsPagerAdapter; +import com.fastaccess.ui.base.BaseActivity; +import com.fastaccess.ui.modules.repos.issues.issue.details.comments.IssueCommentsView; +import com.fastaccess.ui.widgets.AvatarLayout; +import com.fastaccess.ui.widgets.FontTextView; +import com.fastaccess.ui.widgets.ForegroundImageView; +import com.fastaccess.ui.widgets.SpannableBuilder; +import com.fastaccess.ui.widgets.ViewPagerView; +import com.fastaccess.ui.widgets.dialog.MessageDialogView; + +import butterknife.BindView; +import butterknife.OnClick; + +/** + * Created by Kosh on 10 Dec 2016, 9:23 AM + */ + +public class IssuePagerView extends BaseActivity implements IssuePagerMvp.View { + + @BindView(R.id.startGist) ForegroundImageView startGist; + @BindView(R.id.forkGist) ForegroundImageView forkGist; + @BindView(R.id.avatarLayout) AvatarLayout avatarLayout; + @BindView(R.id.headerTitle) FontTextView title; + @BindView(R.id.size) FontTextView size; + @BindView(R.id.date) FontTextView date; + @BindView(R.id.tabs) TabLayout tabs; + @BindView(R.id.pager) ViewPagerView pager; + @BindView(R.id.fab) FloatingActionButton fab; + + public static Intent createIntent(@NonNull Context context, @NonNull String repoId, @NonNull String login, int number) { + Intent intent = new Intent(context, IssuePagerView.class); + intent.putExtras(Bundler.start() + .put(BundleConstant.ID, number) + .put(BundleConstant.EXTRA, login) + .put(BundleConstant.EXTRA_TWO, repoId) + .end()); + return intent; + + } + + @OnClick(R.id.headerTitle) void onTitleClick() { + if (getPresenter().getIssue() != null && !InputHelper.isEmpty(getPresenter().getIssue().getTitle())) + showMessage(getString(R.string.details), getPresenter().getIssue().getTitle()); + } + + @OnClick(R.id.fab) void onAddComment() { + IssueCommentsView view = (IssueCommentsView) pager.getAdapter().instantiateItem(pager, 1); + if (view != null) { + view.onStartNewComment(); + } + } + + @Override protected int layout() { + return R.layout.issue_pager_activity; + } + + @Override protected boolean isTransparent() { + return false; + } + + @Override protected boolean canBack() { + return true; + } + + @Override protected boolean isSecured() { + return false; + } + + @NonNull @Override public IssuePagerPresenter providePresenter() { + return new IssuePagerPresenter(); + } + + @Override protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (savedInstanceState == null) { + getPresenter().onActivityCreated(getIntent()); + } else { + onSetupIssue(); + } + startGist.setVisibility(View.GONE); + forkGist.setVisibility(View.GONE); + } + + @Override public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.issue_menu, menu); + menu.findItem(R.id.closeIssue).setVisible(getPresenter().isOwner()); + menu.findItem(R.id.lockIssue).setVisible(getPresenter().isOwner()); + return super.onCreateOptionsMenu(menu); + } + + @Override public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.share) { + if (getPresenter().getIssue() != null) ActivityHelper.shareUrl(this, getPresenter().getIssue().getHtmlUrl()); + return true; + } else if (item.getItemId() == R.id.closeIssue) { + IssueModel issueModel = getPresenter().getIssue(); + if (issueModel == null) return true; + MessageDialogView.newInstance( + issueModel.getState() == IssueState.open ? getString(R.string.close_issue) : getString(R.string.re_open_issue), + getString(R.string.confirm_message), Bundler.start().put(BundleConstant.EXTRA, true).end()) + .show(getSupportFragmentManager(), MessageDialogView.TAG); + return true; + } else if (item.getItemId() == R.id.lockIssue) { + MessageDialogView.newInstance( + getPresenter().isLocked() ? getString(R.string.unlock_issue) : getString(R.string.lock_issue), + getPresenter().isLocked() ? getString(R.string.unlock_issue_details) : getString(R.string.lock_issue_details), + Bundler.start().put(BundleConstant.EXTRA, true).end()) + .show(getSupportFragmentManager(), MessageDialogView.TAG); + } + return super.onOptionsItemSelected(item); + } + + @Override public boolean onPrepareOptionsMenu(Menu menu) { + MenuItem closeIssue = menu.findItem(R.id.closeIssue); + MenuItem lockIssue = menu.findItem(R.id.lockIssue); + boolean isOwner = getPresenter().isOwner(); + boolean isLocked = getPresenter().isLocked(); + Logger.e(isOwner); + menu.findItem(R.id.closeIssue).setVisible(isOwner); + menu.findItem(R.id.lockIssue).setVisible(isOwner); + if (isOwner) { + //noinspection ConstantConditions ( getIssue at this stage is not null but AS doesn't know. ) + closeIssue.setTitle(getPresenter().getIssue().getState() == IssueState.closed ? getString(R.string.re_open) : getString(R.string.close)); + lockIssue.setTitle(isLocked ? getString(R.string.unlock_issue) : getString(R.string.lock_issue)); + } + + return super.onPrepareOptionsMenu(menu); + } + + @Override public void onSetupIssue() { + hideProgress(); + if (getPresenter().getIssue() == null) { + finish(); + return; + } + supportInvalidateOptionsMenu(); + IssueModel issueModel = getPresenter().getIssue(); + setTitle(String.format("#%s", issueModel.getNumber())); + UserModel userModel = issueModel.getUser(); + title.setText(issueModel.getTitle()); + if (userModel != null) { + date.setVisibility(View.GONE); + size.setText(SpannableBuilder.builder().append(getString(issueModel.getState().getStatus())) + .append(" ").append(getString(R.string.by)).append(" ").append(userModel.getLogin()).append(" ") + .append(ParseDateFormat.getTimeAgo(issueModel.getCreatedAt()))); + avatarLayout.setUrl(userModel.getAvatarUrl(), userModel.getLogin()); + } + pager.setAdapter(new FragmentsPagerAdapter(getSupportFragmentManager(), FragmentPagerAdapterModel.buildForIssues(this, issueModel))); + tabs.setupWithViewPager(pager); + if (!getPresenter().isLocked() || getPresenter().isOwner()) { + pager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { + @Override public void onPageSelected(int position) { + super.onPageSelected(position); + hideShowFab(); + } + }); + } + hideShowFab(); + } + + @Override public void showSuccessIssueActionMsg(boolean isClose) { + hideProgress(); + if (isClose) { + showMessage(getString(R.string.success), getString(R.string.success_closed)); + } else { + showMessage(getString(R.string.success), getString(R.string.success_re_opened)); + } + } + + @Override public void showErrorIssueActionMsg(boolean isClose) { + hideProgress(); + if (isClose) { + showMessage(getString(R.string.error), getString(R.string.error_closing_issue)); + } else { + showMessage(getString(R.string.error), getString(R.string.error_re_opening_issue)); + } + } + + @Override public void onMessageDialogActionClicked(boolean isOk, @Nullable Bundle bundle) { + super.onMessageDialogActionClicked(isOk, bundle); + if (isOk) { + getPresenter().onHandleConfirmDialog(bundle); + } + } + + private void hideShowFab() { + if (getPresenter().isLocked() && !getPresenter().isOwner()) { + fab.hide(); + return; + } + if (pager.getCurrentItem() == 1) { + fab.show(); + } else { + fab.hide(); + } + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/details/comments/IssueCommentsMvp.java b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/details/comments/IssueCommentsMvp.java new file mode 100644 index 00000000..123bb99c --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/details/comments/IssueCommentsMvp.java @@ -0,0 +1,71 @@ +package com.fastaccess.ui.modules.repos.issues.issue.details.comments; + +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.widget.SwipeRefreshLayout; + +import com.fastaccess.data.dao.CommentsModel; +import com.fastaccess.data.dao.UserModel; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.adapter.CommentsAdapter; +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; +import com.fastaccess.ui.widgets.recyclerview.DynamicRecyclerView; + +import java.util.ArrayList; + +import retrofit2.Response; + +/** + * Created by Kosh on 20 Nov 2016, 11:10 AM + */ + +interface IssueCommentsMvp { + + interface View extends BaseMvp.FAView, SwipeRefreshLayout.OnRefreshListener, + android.view.View.OnClickListener { + + void onNotifyAdapter(); + + + @NonNull OnLoadMore getLoadMore(); + + void onEditComment(@NonNull CommentsModel item); + + void onStartNewComment(); + + void onHandleCommentDelete(@NonNull Response booleanResponse, long commId); + + void onShowDeleteMsg(long id); + + void onShowProgressDialog(); + + void onTagUser(@Nullable UserModel user); + + } + + interface Presenter extends BaseMvp.FAPresenter, + BaseMvp.PaginationListener, BaseViewHolder.OnItemClickListener { + + void onFragmentCreated(@Nullable Bundle bundle); + + @NonNull ArrayList getComments(); + + void onActivityResult(int requestCode, int resultCode, @Nullable Intent data, + @NonNull DynamicRecyclerView recycler, @NonNull CommentsAdapter adapter); + + void onWorkOffline(); + + void onHandleDeletion(@Nullable Bundle bundle); + + @NonNull String repoId(); + + @NonNull String login(); + + int number(); + } + + +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/details/comments/IssueCommentsPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/details/comments/IssueCommentsPresenter.java new file mode 100644 index 00000000..06e8d508 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/details/comments/IssueCommentsPresenter.java @@ -0,0 +1,177 @@ +package com.fastaccess.ui.modules.repos.issues.issue.details.comments; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.TextUtils; +import android.view.View; + +import com.fastaccess.data.dao.CommentsModel; +import com.fastaccess.data.dao.LoginModel; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.ui.adapter.CommentsAdapter; +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; +import com.fastaccess.ui.widgets.recyclerview.DynamicRecyclerView; + +import java.util.ArrayList; + +import rx.Observable; + +/** + * Created by Kosh on 11 Nov 2016, 12:36 PM + */ + +class IssueCommentsPresenter extends BasePresenter implements IssueCommentsMvp.Presenter { + private ArrayList comments = new ArrayList<>(); + private int page; + private int previousTotal; + private int lastPage = Integer.MAX_VALUE; + private String repoId; + private String login; + private int number; + + + @Override public int getCurrentPage() { + return page; + } + + @Override public int getPreviousTotal() { + return previousTotal; + } + + @Override public void setCurrentPage(int page) { + this.page = page; + } + + @Override public void setPreviousTotal(int previousTotal) { + this.previousTotal = previousTotal; + } + + @Override public T onError(@NonNull Throwable throwable, @NonNull Observable observable) { + + return super.onError(throwable, observable); + } + + @Override public void onCallApi(int page, @Nullable String parameter) { + if (page == 1) { + lastPage = Integer.MAX_VALUE; + sendToView(view -> view.getLoadMore().reset()); + } + if (page > lastPage || lastPage == 0) { + sendToView(IssueCommentsMvp.View::hideProgress); + return; + } + setCurrentPage(page); + makeRestCall(RestProvider.getIssueService().getIssueComments(login, repoId, number, page), + listResponse -> { + lastPage = listResponse.getLast(); + if (getCurrentPage() == 1) { + getComments().clear(); + manageSubscription(CommentsModel.saveForIssues(listResponse.getItems(), repoId(), login(), + String.valueOf(number)).subscribe()); + } + getComments().addAll(listResponse.getItems()); + sendToView(IssueCommentsMvp.View::onNotifyAdapter); + }); + } + + @Override public void onFragmentCreated(@Nullable Bundle bundle) { + if (bundle == null) throw new NullPointerException("Bundle is null?"); + repoId = bundle.getString(BundleConstant.ID); + login = bundle.getString(BundleConstant.EXTRA); + number = bundle.getInt(BundleConstant.EXTRA_TWO); + } + + @NonNull @Override public ArrayList getComments() { + return comments; + } + + @Override public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data, + @NonNull DynamicRecyclerView recycler, @NonNull CommentsAdapter adapter) { + if (resultCode == Activity.RESULT_OK && data != null) { + if (requestCode == BundleConstant.REQUEST_CODE) { + Bundle bundle = data.getExtras(); + if (bundle != null) { + boolean isNew = bundle.getBoolean(BundleConstant.EXTRA); + CommentsModel commentsModel = bundle.getParcelable(BundleConstant.ITEM); + if (isNew) { + adapter.addItem(commentsModel); + recycler.smoothScrollToPosition(adapter.getItemCount()); + } else { + int position = adapter.getItem(commentsModel); + if (position != -1) { + adapter.swapItem(commentsModel, position); + recycler.smoothScrollToPosition(position); + } else { + adapter.addItem(commentsModel); + recycler.smoothScrollToPosition(adapter.getItemCount()); + } + } + } + } + } + } + + @Override public void onWorkOffline() { + if (comments.isEmpty()) { + manageSubscription(CommentsModel.getIssueComments(repoId(), login(), String.valueOf(number)) + .subscribe(models -> { + if (models != null) { + comments.addAll(models); + sendToView(IssueCommentsMvp.View::onNotifyAdapter); + } + })); + } else { + sendToView(BaseMvp.FAView::hideProgress); + } + } + + @Override public void onHandleDeletion(@Nullable Bundle bundle) { + if (bundle != null) { + long commId = bundle.getLong(BundleConstant.EXTRA, 0); + if (commId != 0) { + makeRestCall(RestProvider.getIssueService().deleteIssueComment(login, repoId, commId), + booleanResponse -> sendToView(view -> view.onHandleCommentDelete(booleanResponse, commId))); + } + } + } + + @NonNull @Override public String repoId() { + return repoId; + } + + @NonNull @Override public String login() { + return login; + } + + @Override public int number() { + return number; + } + + @Override public void onItemClick(int position, View v, CommentsModel item) { + if (getView() != null) { + if (item.getUser() != null) { + LoginModel userModel = LoginModel.getUser(); + if (userModel != null && item.getUser().getLogin().equals(userModel.getLogin())) { + getView().onEditComment(item); + } else { + getView().onTagUser(item.getUser()); + } + } else { + getView().onTagUser(item.getUser()); + } + } + } + + @Override public void onItemLongClick(int position, View v, CommentsModel item) { + if (item.getUser() != null && TextUtils.equals(item.getUser().getLogin(), LoginModel.getUser().getLogin())) { + if (getView() != null) getView().onShowDeleteMsg(item.getId()); + } else { + onItemClick(position, v, item); + } + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/details/comments/IssueCommentsView.java b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/details/comments/IssueCommentsView.java new file mode 100644 index 00000000..0cd3b926 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/details/comments/IssueCommentsView.java @@ -0,0 +1,178 @@ +package com.fastaccess.ui.modules.repos.issues.issue.details.comments; + +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.v4.widget.SwipeRefreshLayout; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.data.dao.CommentsModel; +import com.fastaccess.data.dao.UserModel; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.adapter.CommentsAdapter; +import com.fastaccess.ui.base.BaseFragment; +import com.fastaccess.ui.modules.editor.EditorView; +import com.fastaccess.ui.widgets.StateLayout; +import com.fastaccess.ui.widgets.dialog.MessageDialogView; +import com.fastaccess.ui.widgets.recyclerview.DynamicRecyclerView; + +import butterknife.BindView; +import retrofit2.Response; + +/** + * Created by Kosh on 11 Nov 2016, 12:36 PM + */ + +public class IssueCommentsView extends BaseFragment implements IssueCommentsMvp.View { + + @BindView(R.id.recycler) DynamicRecyclerView recycler; + @BindView(R.id.refresh) SwipeRefreshLayout refresh; + @BindView(R.id.stateLayout) StateLayout stateLayout; + private CommentsAdapter adapter; + private OnLoadMore onLoadMore; + + public static IssueCommentsView newInstance(@NonNull String login, @NonNull String repoId, int number) { + IssueCommentsView view = new IssueCommentsView(); + view.setArguments(Bundler.start() + .put(BundleConstant.ID, repoId) + .put(BundleConstant.EXTRA, login) + .put(BundleConstant.EXTRA_TWO, number) + .end()); + return view; + } + + @Override protected int fragmentLayout() { + return R.layout.small_grid_refresh_list; + } + + @Override protected void onFragmentCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + if (savedInstanceState == null) getPresenter().onFragmentCreated(getArguments()); + recycler.setEmptyView(stateLayout, refresh); + refresh.setOnRefreshListener(this); + stateLayout.setOnReloadListener(this); + adapter = new CommentsAdapter(getPresenter().getComments()); + adapter.setListener(getPresenter()); + getLoadMore().setCurrent_page(getPresenter().getCurrentPage(), getPresenter().getPreviousTotal()); + recycler.setAdapter(adapter); + recycler.addOnScrollListener(getLoadMore()); + if (getPresenter().getComments().isEmpty() && !getPresenter().isApiCalled()) { + onRefresh(); + } + } + + @Override public void onRefresh() { + getPresenter().onCallApi(1, null); + } + + @Override public void onNotifyAdapter() { + hideProgress(); + adapter.notifyDataSetChanged(); + } + + @Override public void hideProgress() { + refresh.setRefreshing(false); + stateLayout.hideProgress(); + } + + @Override public void showProgress(@StringRes int resId) { + refresh.setRefreshing(true); + stateLayout.showProgress(); + } + + @Override public void showErrorMessage(@NonNull String message) { + hideProgress(); + stateLayout.showReload(adapter.getItemCount()); + super.showErrorMessage(message); + } + + @NonNull @Override public IssueCommentsPresenter providePresenter() { + return new IssueCommentsPresenter(); + } + + @NonNull @Override public OnLoadMore getLoadMore() { + if (onLoadMore == null) { + onLoadMore = new OnLoadMore<>(getPresenter()); + } + return onLoadMore; + } + + @Override public void onEditComment(@NonNull CommentsModel item) { + Intent intent = new Intent(getContext(), EditorView.class); + intent.putExtras(Bundler + .start() + .put(BundleConstant.ID, getPresenter().repoId()) + .put(BundleConstant.EXTRA_TWO, getPresenter().login()) + .put(BundleConstant.EXTRA_THREE, getPresenter().number()) + .put(BundleConstant.EXTRA_FOUR, item.getId()) + .put(BundleConstant.EXTRA, item.getBody()) + .put(BundleConstant.EXTRA_TYPE, BundleConstant.ExtraTYpe.EDIT_ISSUE_COMMENT_EXTRA) + .end()); + startActivityForResult(intent, BundleConstant.REQUEST_CODE); + } + + @Override public void onStartNewComment() { + onTagUser(null); + } + + @Override public void onHandleCommentDelete(@NonNull Response booleanResponse, long commId) { + hideProgress(); + if (booleanResponse.code() == 204) { + CommentsModel commentsModel = new CommentsModel(); + commentsModel.setId(commId); + adapter.removeItem(commentsModel); + } else { + showErrorMessage(getString(R.string.error_deleting_comment)); + } + } + + @Override public void onShowDeleteMsg(long id) { + MessageDialogView.newInstance(getString(R.string.delete), getString(R.string.confirm_message), + Bundler.start() + .put(BundleConstant.EXTRA, id) + .end()) + .show(getChildFragmentManager(), MessageDialogView.TAG); + } + + @Override public void onShowProgressDialog() { + showProgress(0); + } + + @Override public void onTagUser(@Nullable UserModel user) { + Intent intent = new Intent(getContext(), EditorView.class); + intent.putExtras(Bundler + .start() + .put(BundleConstant.ID, getPresenter().repoId()) + .put(BundleConstant.EXTRA_TWO, getPresenter().login()) + .put(BundleConstant.EXTRA_THREE, getPresenter().number()) + .put(BundleConstant.EXTRA, user != null ? "@" + user.getLogin() : "") + .put(BundleConstant.EXTRA_TYPE, BundleConstant.ExtraTYpe.NEW_ISSUE_COMMENT_EXTRA) + .end()); + startActivityForResult(intent, BundleConstant.REQUEST_CODE); + } + + @Override public void onDestroyView() { + recycler.removeOnScrollListener(getLoadMore()); + super.onDestroyView(); + } + + @Override public void onClick(View view) { + onRefresh(); + } + + @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + getPresenter().onActivityResult(requestCode, resultCode, data, recycler, adapter); + } + + @Override public void onMessageDialogActionClicked(boolean isOk, @Nullable Bundle bundle) { + super.onMessageDialogActionClicked(isOk, bundle); + if (isOk) { + getPresenter().onHandleDeletion(bundle); + } + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/details/events/IssueDetailsMvp.java b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/details/events/IssueDetailsMvp.java new file mode 100644 index 00000000..1001bcd2 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/details/events/IssueDetailsMvp.java @@ -0,0 +1,37 @@ +package com.fastaccess.ui.modules.repos.issues.issue.details.events; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.widget.SwipeRefreshLayout; + +import com.fastaccess.data.dao.IssueEventAdapterModel; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.ArrayList; + +/** + * Created by Kosh on 13 Dec 2016, 12:36 AM + */ + +interface IssueDetailsMvp { + + interface View extends BaseMvp.FAView, SwipeRefreshLayout.OnRefreshListener, android.view.View.OnClickListener { + + void onNotifyAdapter(); + + @NonNull OnLoadMore getLoadMore(); + } + + interface Presenter extends BaseMvp.FAPresenter, BaseViewHolder.OnItemClickListener, + BaseMvp.PaginationListener { + + @NonNull ArrayList getEvents(); + + void onFragmentCreated(@Nullable Bundle bundle); + + void onWorkOffline(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/details/events/IssueDetailsPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/details/events/IssueDetailsPresenter.java new file mode 100644 index 00000000..f2c5a50f --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/details/events/IssueDetailsPresenter.java @@ -0,0 +1,112 @@ +package com.fastaccess.ui.modules.repos.issues.issue.details.events; + +import android.app.Activity; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; + +import com.fastaccess.data.dao.IssueEventAdapterModel; +import com.fastaccess.data.dao.IssueEventModel; +import com.fastaccess.data.dao.IssueModel; +import com.fastaccess.helper.ActivityHelper; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Logger; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; + +import java.util.ArrayList; + +import rx.Observable; + +/** + * Created by Kosh on 13 Dec 2016, 12:38 AM + */ + +class IssueDetailsPresenter extends BasePresenter implements IssueDetailsMvp.Presenter { + private int page; + private int previousTotal; + private int lastPage = Integer.MAX_VALUE; + private ArrayList events = new ArrayList<>(); + private IssueModel issueModel; + + @Override public void onFragmentCreated(@Nullable Bundle bundle) { + if (bundle == null) throw new NullPointerException("Bundle is null?"); + issueModel = bundle.getParcelable(BundleConstant.ITEM); + if (events.isEmpty()) { + events.add(0, new IssueEventAdapterModel(IssueEventAdapterModel.HEADER, issueModel)); + sendToView(IssueDetailsMvp.View::onNotifyAdapter); + } + } + + @Override public T onError(@NonNull Throwable throwable, @NonNull Observable observable) { + onWorkOffline(); + return super.onError(throwable, observable); + } + + @Override public void onWorkOffline() { + //TODO + } + + @NonNull @Override public ArrayList getEvents() { + return events; + } + + @Override public void onItemClick(int position, View v, IssueEventAdapterModel item) { + Logger.e(item.getType()); + if (item.getType() != IssueEventAdapterModel.HEADER) { + IssueEventModel issueEventModel = item.getIssueEvent(); + if (issueEventModel.getCommitUrl() != null) { + Activity activity = ActivityHelper.getActivity(v.getContext()); + if (activity != null) { + ActivityHelper.startCustomTab(activity, issueEventModel.getCommitUrl()); + } + } + } + } + + @Override public void onItemLongClick(int position, View v, IssueEventAdapterModel item) { + //TODO + } + + @Override public int getCurrentPage() { + return page; + } + + @Override public int getPreviousTotal() { + return previousTotal; + } + + @Override public void setCurrentPage(int page) { + this.page = page; + } + + @Override public void setPreviousTotal(int previousTotal) { + this.previousTotal = previousTotal; + } + + @Override public void onCallApi(int page, @Nullable Object parameter) { + if (page == 1) { + lastPage = Integer.MAX_VALUE; + sendToView(view -> view.getLoadMore().reset()); + } + if (page > lastPage || lastPage == 0) { + sendToView(IssueDetailsMvp.View::hideProgress); + return; + } + setCurrentPage(page); + String login = issueModel.getLogin(); + String repoID = issueModel.getRepoId(); + int number = issueModel.getNumber(); + makeRestCall(RestProvider.getIssueService().getTimeline(login, repoID, number, page), + response -> { + lastPage = response.getLast(); + if (getCurrentPage() == 1) { + getEvents().subList(1, getEvents().size()).clear(); + //TODO SAVE + } + getEvents().addAll(IssueEventAdapterModel.addEvents(response.getItems())); + sendToView(IssueDetailsMvp.View::onNotifyAdapter); + }); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/details/events/IssueDetailsView.java b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/details/events/IssueDetailsView.java new file mode 100644 index 00000000..358cf763 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/issue/details/events/IssueDetailsView.java @@ -0,0 +1,97 @@ +package com.fastaccess.ui.modules.repos.issues.issue.details.events; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.data.dao.IssueModel; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.adapter.IssueTimelineAdapter; +import com.fastaccess.ui.base.BaseFragment; +import com.fastaccess.ui.widgets.AppbarRefreshLayout; +import com.fastaccess.ui.widgets.StateLayout; +import com.fastaccess.ui.widgets.recyclerview.DynamicRecyclerView; + +import butterknife.BindView; + +/** + * Created by Kosh on 13 Dec 2016, 12:40 AM + */ + +public class IssueDetailsView extends BaseFragment implements IssueDetailsMvp.View { + @BindView(R.id.recycler) DynamicRecyclerView recycler; + @BindView(R.id.refresh) AppbarRefreshLayout refresh; + @BindView(R.id.stateLayout) StateLayout stateLayout; + private IssueTimelineAdapter adapter; + private OnLoadMore onLoadMore; + + public static IssueDetailsView newInstance(@NonNull IssueModel issueModel) { + IssueDetailsView view = new IssueDetailsView(); + view.setArguments(Bundler.start().put(BundleConstant.ITEM, issueModel).end()); + return view; + } + + @Override protected int fragmentLayout() { + return R.layout.small_grid_refresh_list; + } + + @Override protected void onFragmentCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + if (savedInstanceState == null) getPresenter().onFragmentCreated(getArguments()); + recycler.setEmptyView(stateLayout, refresh); + refresh.setOnRefreshListener(this); + stateLayout.setOnReloadListener(this); + adapter = new IssueTimelineAdapter(getPresenter().getEvents()); + adapter.setListener(getPresenter()); + getLoadMore().setCurrent_page(getPresenter().getCurrentPage(), getPresenter().getPreviousTotal()); + recycler.setAdapter(adapter); + recycler.addOnScrollListener(getLoadMore()); + if (getPresenter().getEvents().size() == 1 && !getPresenter().isApiCalled()) { + onRefresh(); + } + } + + @NonNull @Override public IssueDetailsPresenter providePresenter() { + return new IssueDetailsPresenter(); + } + + @Override public void onRefresh() { + getPresenter().onCallApi(1, null); + } + + @Override public void onNotifyAdapter() { + hideProgress(); + adapter.notifyDataSetChanged(); + } + + @Override public void showProgress(@StringRes int resId) { + refresh.setRefreshing(true); + stateLayout.showProgress(); + } + + @Override public void hideProgress() { + refresh.setRefreshing(false); + stateLayout.hideProgress(); + } + + @Override public void showErrorMessage(@NonNull String msgRes) { + hideProgress(); + stateLayout.showReload(adapter.getItemCount()); + super.showErrorMessage(msgRes); + } + + @SuppressWarnings("unchecked") @NonNull @Override public OnLoadMore getLoadMore() { + if (onLoadMore == null) { + onLoadMore = new OnLoadMore(getPresenter()); + } + return onLoadMore; + } + + @Override public void onClick(View view) { + onRefresh(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/RepoPullRequestPagerMvp.java b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/RepoPullRequestPagerMvp.java new file mode 100644 index 00000000..e6d215bd --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/RepoPullRequestPagerMvp.java @@ -0,0 +1,14 @@ +package com.fastaccess.ui.modules.repos.pull_requests; + +import com.fastaccess.ui.base.mvp.BaseMvp; + +/** + * Created by Kosh on 31 Dec 2016, 1:35 AM + */ + +interface RepoPullRequestPagerMvp { + + interface View extends BaseMvp.FAView {} + + interface Presenter extends BaseMvp.FAPresenter {} +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/RepoPullRequestPagerPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/RepoPullRequestPagerPresenter.java new file mode 100644 index 00000000..588b6914 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/RepoPullRequestPagerPresenter.java @@ -0,0 +1,9 @@ +package com.fastaccess.ui.modules.repos.pull_requests; + +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; + +/** + * Created by Kosh on 31 Dec 2016, 1:36 AM + */ + +class RepoPullRequestPagerPresenter extends BasePresenter implements RepoPullRequestPagerMvp.Presenter {} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/RepoPullRequestPagerView.java b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/RepoPullRequestPagerView.java new file mode 100644 index 00000000..6594661d --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/RepoPullRequestPagerView.java @@ -0,0 +1,56 @@ +package com.fastaccess.ui.modules.repos.pull_requests; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.TabLayout; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.data.dao.FragmentPagerAdapterModel; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.ui.adapter.FragmentsPagerAdapter; +import com.fastaccess.ui.base.BaseFragment; +import com.fastaccess.ui.widgets.ViewPagerView; + +import butterknife.BindView; + +/** + * Created by Kosh on 31 Dec 2016, 1:36 AM + */ + +public class RepoPullRequestPagerView extends BaseFragment implements + RepoPullRequestPagerMvp.View { + + public static final String TAG = RepoPullRequestPagerView.class.getSimpleName(); + + @BindView(R.id.tabs) TabLayout tabs; + @BindView(R.id.pager) ViewPagerView pager; + + public static RepoPullRequestPagerView newInstance(@NonNull String repoId, @NonNull String login) { + RepoPullRequestPagerView view = new RepoPullRequestPagerView(); + view.setArguments(Bundler.start() + .put(BundleConstant.ID, repoId) + .put(BundleConstant.EXTRA, login) + .end()); + return view; + } + + @Override protected int fragmentLayout() { + return R.layout.centered_tabbed_viewpager; + } + + @Override protected void onFragmentCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + String repoId = getArguments().getString(BundleConstant.ID); + String login = getArguments().getString(BundleConstant.EXTRA); + if (login == null || repoId == null) throw new NullPointerException("repoId || login is null???"); + pager.setAdapter(new FragmentsPagerAdapter(getChildFragmentManager(), + FragmentPagerAdapterModel.buildForRepoPullRequest(getContext(), login, repoId))); + tabs.setupWithViewPager(pager); + } + + @NonNull @Override public RepoPullRequestPagerPresenter providePresenter() { + return new RepoPullRequestPagerPresenter(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/RepoPullRequestMvp.java b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/RepoPullRequestMvp.java new file mode 100644 index 00000000..0d50a5d1 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/RepoPullRequestMvp.java @@ -0,0 +1,36 @@ +package com.fastaccess.ui.modules.repos.pull_requests.pull_request; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.widget.SwipeRefreshLayout; + +import com.fastaccess.data.dao.PullRequestModel; +import com.fastaccess.data.dao.types.IssueState; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.ArrayList; + +/** + * Created by Kosh on 03 Dec 2016, 3:45 PM + */ + +interface RepoPullRequestMvp { + + interface View extends BaseMvp.FAView, SwipeRefreshLayout.OnRefreshListener, android.view.View.OnClickListener { + void onNotifyAdapter(); + + @NonNull OnLoadMore getLoadMore(); + } + + interface Presenter extends BaseMvp.FAPresenter, + BaseViewHolder.OnItemClickListener, + BaseMvp.PaginationListener { + void onFragmentCreated(@NonNull Bundle bundle); + + void onWorkOffline(); + + @NonNull ArrayList getPullRequests(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/RepoPullRequestPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/RepoPullRequestPresenter.java new file mode 100644 index 00000000..0f1e2544 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/RepoPullRequestPresenter.java @@ -0,0 +1,120 @@ +package com.fastaccess.ui.modules.repos.pull_requests.pull_request; + +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; + +import com.fastaccess.data.dao.PullRequestModel; +import com.fastaccess.data.dao.PullsIssuesParser; +import com.fastaccess.data.dao.types.IssueState; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.helper.Logger; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; +import com.fastaccess.ui.modules.repos.pull_requests.pull_request.details.PullRequestPagerView; + +import java.util.ArrayList; + +import rx.Observable; + +/** + * Created by Kosh on 03 Dec 2016, 3:48 PM + */ + +class RepoPullRequestPresenter extends BasePresenter implements RepoPullRequestMvp.Presenter { + + private ArrayList pullRequests = new ArrayList<>(); + private String login; + private String repoId; + private int page; + private int previousTotal; + private int lastPage = Integer.MAX_VALUE; + private IssueState issueState; + + @Override public int getCurrentPage() { + return page; + } + + @Override public int getPreviousTotal() { + return previousTotal; + } + + @Override public void setCurrentPage(int page) { + this.page = page; + } + + @Override public void setPreviousTotal(int previousTotal) { + this.previousTotal = previousTotal; + } + + @Override public T onError(@NonNull Throwable throwable, @NonNull Observable observable) { + onWorkOffline(); + return super.onError(throwable, observable); + } + + @Override public void onCallApi(int page, @Nullable IssueState parameter) { + if (page == 1) { + lastPage = Integer.MAX_VALUE; + sendToView(view -> view.getLoadMore().reset()); + } + setCurrentPage(page); + if (page > lastPage || lastPage == 0) { + sendToView(RepoPullRequestMvp.View::hideProgress); + return; + } + if (repoId == null || login == null) return; + makeRestCall(RestProvider.getPullRequestSerice().getPullRequests(login, repoId, issueState.name(), page), + response -> { + lastPage = response.getLast(); + if (getCurrentPage() == 1) { + getPullRequests().clear(); + manageSubscription(PullRequestModel.save(response.getItems(), login, repoId).subscribe()); + } + getPullRequests().addAll(response.getItems()); + sendToView(RepoPullRequestMvp.View::onNotifyAdapter); + }); + } + + @Override public void onFragmentCreated(@NonNull Bundle bundle) { + repoId = bundle.getString(BundleConstant.ID); + login = bundle.getString(BundleConstant.EXTRA); + issueState = (IssueState) bundle.getSerializable(BundleConstant.EXTRA_TWO); + if (!InputHelper.isEmpty(login) && !InputHelper.isEmpty(repoId)) { + onCallApi(1, null); + } + } + + @Override public void onWorkOffline() { + if (pullRequests.isEmpty()) { + manageSubscription(PullRequestModel.getPullRequests(repoId, login, issueState) + .subscribe(pulls -> { + pullRequests.addAll(pulls); + sendToView(RepoPullRequestMvp.View::onNotifyAdapter); + })); + } else { + sendToView(BaseMvp.FAView::hideProgress); + } + } + + @NonNull public ArrayList getPullRequests() { + return pullRequests; + } + + @Override public void onItemClick(int position, View v, PullRequestModel item) { + Logger.e(Bundler.start().put("item", item).end().size()); + PullsIssuesParser parser = PullsIssuesParser.getForPullRequest(item.getHtmlUrl()); + if (parser != null) { + Intent intent = PullRequestPagerView.createIntent(v.getContext(), parser.getRepoId(), parser.getLogin(), parser.getNumber()); + v.getContext().startActivity(intent); + } + } + + @Override public void onItemLongClick(int position, View v, PullRequestModel item) { + onItemClick(position, v, item); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/RepoPullRequestView.java b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/RepoPullRequestView.java new file mode 100644 index 00000000..85b3d1fd --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/RepoPullRequestView.java @@ -0,0 +1,107 @@ +package com.fastaccess.ui.modules.repos.pull_requests.pull_request; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.v4.widget.SwipeRefreshLayout; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.data.dao.types.IssueState; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.helper.Logger; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.adapter.PullRequestAdapter; +import com.fastaccess.ui.base.BaseFragment; +import com.fastaccess.ui.widgets.StateLayout; +import com.fastaccess.ui.widgets.recyclerview.DynamicRecyclerView; + +import butterknife.BindView; + +/** + * Created by Kosh on 03 Dec 2016, 3:56 PM + */ + +public class RepoPullRequestView extends BaseFragment implements RepoPullRequestMvp.View { + @BindView(R.id.recycler) DynamicRecyclerView recycler; + @BindView(R.id.refresh) SwipeRefreshLayout refresh; + @BindView(R.id.stateLayout) StateLayout stateLayout; + private OnLoadMore onLoadMore; + private PullRequestAdapter adapter; + + public static RepoPullRequestView newInstance(@NonNull String repoId, @NonNull String login, @NonNull IssueState issueState) { + RepoPullRequestView view = new RepoPullRequestView(); + view.setArguments(Bundler.start() + .put(BundleConstant.ID, repoId) + .put(BundleConstant.EXTRA, login) + .put(BundleConstant.EXTRA_TWO, issueState) + .end()); + return view; + } + + @Override public void onNotifyAdapter() { + Logger.e(); + hideProgress(); + adapter.notifyDataSetChanged(); + } + + @Override protected int fragmentLayout() { + return R.layout.small_grid_refresh_list; + } + + @Override protected void onFragmentCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + if (getArguments() == null) { + throw new NullPointerException("Bundle is null, therefore, issues can't be proceeded."); + } + stateLayout.setOnReloadListener(this); + refresh.setOnRefreshListener(this); + recycler.setEmptyView(stateLayout, refresh); + adapter = new PullRequestAdapter(getPresenter().getPullRequests(), true); + adapter.setListener(getPresenter()); + getLoadMore().setCurrent_page(getPresenter().getCurrentPage(), getPresenter().getPreviousTotal()); + recycler.setAdapter(adapter); + recycler.addOnScrollListener(getLoadMore()); + if (savedInstanceState == null) { + getPresenter().onFragmentCreated(getArguments()); + } else if (getPresenter().getPullRequests().isEmpty() && !getPresenter().isApiCalled()) { + onRefresh(); + } + } + + @NonNull @Override public RepoPullRequestPresenter providePresenter() { + return new RepoPullRequestPresenter(); + } + + @Override public void hideProgress() { + refresh.setRefreshing(false); + stateLayout.hideProgress(); + } + + @Override public void showProgress(@StringRes int resId) { + refresh.setRefreshing(true); + stateLayout.showProgress(); + } + + @Override public void showErrorMessage(@NonNull String message) { + hideProgress(); + stateLayout.showReload(adapter.getItemCount()); + super.showErrorMessage(message); + } + + @NonNull @Override public OnLoadMore getLoadMore() { + if (onLoadMore == null) { + onLoadMore = new OnLoadMore<>(getPresenter()); + } + return onLoadMore; + } + + @Override public void onRefresh() { + getPresenter().onCallApi(1, null); + } + + @Override public void onClick(View view) { + onRefresh(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/PullRequestPagerMvp.java b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/PullRequestPagerMvp.java new file mode 100644 index 00000000..1e8cf495 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/PullRequestPagerMvp.java @@ -0,0 +1,53 @@ +package com.fastaccess.ui.modules.repos.pull_requests.pull_request.details; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.fastaccess.data.dao.PullRequestModel; +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.widgets.SpannableBuilder; + +/** + * Created by Kosh on 10 Dec 2016, 9:21 AM + */ + +interface PullRequestPagerMvp { + + interface View extends BaseMvp.FAView { + + void onSetupIssue(); + + void showSuccessIssueActionMsg(boolean isClose); + + void showErrorIssueActionMsg(boolean isClose); + } + + interface Presenter extends BaseMvp.FAPresenter { + + @Nullable PullRequestModel getPullRequest(); + + void onActivityCreated(@Nullable Intent intent); + + void onWorkOffline(long issueNumber, @NonNull String repoId, @NonNull String login); + + boolean isOwner(); + + boolean isRepoOwner(); + + boolean isLocked(); + + boolean isMergeable(); + + void onHandleConfirmDialog(@Nullable Bundle bundle); + + void onLockUnlockConversations(); + + @NonNull SpannableBuilder getMergeBy(@NonNull PullRequestModel pullRequest, @NonNull Context context); + + void onMerge(); + } + +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/PullRequestPagerPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/PullRequestPagerPresenter.java new file mode 100644 index 00000000..21da86f3 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/PullRequestPagerPresenter.java @@ -0,0 +1,156 @@ +package com.fastaccess.ui.modules.repos.pull_requests.pull_request.details; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.fastaccess.R; +import com.fastaccess.data.dao.LoginModel; +import com.fastaccess.data.dao.MergeRequestModel; +import com.fastaccess.data.dao.PullRequestModel; +import com.fastaccess.data.dao.PullsIssuesParser; +import com.fastaccess.data.dao.UserModel; +import com.fastaccess.data.service.IssueService; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.helper.RxHelper; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; +import com.fastaccess.ui.widgets.SpannableBuilder; + +import retrofit2.Response; +import rx.Observable; + +/** + * Created by Kosh on 10 Dec 2016, 9:23 AM + */ + +class PullRequestPagerPresenter extends BasePresenter implements PullRequestPagerMvp.Presenter { + private PullRequestModel pullRequest; + private int issueNumber; + private String login; + private String repoId; + + @Nullable @Override public PullRequestModel getPullRequest() { + return pullRequest; + } + + @Override public T onError(@NonNull Throwable throwable, @NonNull Observable observable) { + onWorkOffline(issueNumber, repoId, login); + return super.onError(throwable, observable); + } + + @Override public void onActivityCreated(@Nullable Intent intent) { + if (intent != null && intent.getExtras() != null) { + issueNumber = intent.getExtras().getInt(BundleConstant.ID); + login = intent.getExtras().getString(BundleConstant.EXTRA); + repoId = intent.getExtras().getString(BundleConstant.EXTRA_TWO); + if (pullRequest != null) { + sendToView(PullRequestPagerMvp.View::onSetupIssue); + return; + } else if (issueNumber > 0 && !InputHelper.isEmpty(login) && !InputHelper.isEmpty(repoId)) { + makeRestCall(RestProvider.getPullRequestSerice().getPullRequest(login, repoId, issueNumber) + , pullRequestModelResponse -> { + pullRequest = pullRequestModelResponse; + pullRequest.setRepoId(repoId); + pullRequest.setLogin(login); + sendToView(PullRequestPagerMvp.View::onSetupIssue); + manageSubscription(pullRequest.save().subscribe()); + }); + return; + } + } + sendToView(PullRequestPagerMvp.View::onSetupIssue); + } + + @Override public void onWorkOffline(long issueNumber, @NonNull String repoId, @NonNull String login) { + //TODO + } + + @Override public boolean isOwner() { + boolean isOwner; + PullRequestModel pullRequestModel = getPullRequest(); + if (pullRequestModel == null) return false; + if (pullRequestModel.getAssignee() != null) { + isOwner = pullRequestModel.getAssignee().getLogin().equals(LoginModel.getUser().getLogin()); + } else { + isOwner = pullRequestModel.getBase() != null && + getPullRequest().getBase().getUser().getLogin().equals(LoginModel.getUser().getLogin()); + } + return isOwner; + } + + @Override public boolean isRepoOwner() { + if (getPullRequest() == null) return false; + UserModel userModel = getPullRequest() != null ? getPullRequest().getUser() : null; + LoginModel me = LoginModel.getUser(); + PullsIssuesParser parser = PullsIssuesParser.getForPullRequest(getPullRequest().getHtmlUrl()); + return userModel != null && userModel.getLogin().equalsIgnoreCase(me.getLogin()) + || (parser != null && parser.getLogin().equalsIgnoreCase(me.getLogin())); + } + + @Override public boolean isLocked() { + return getPullRequest() != null && getPullRequest().isLocked(); + } + + @Override public boolean isMergeable() { + return getPullRequest() != null && getPullRequest().isMergeable() && !getPullRequest().isMerged(); + } + + @Override public void onHandleConfirmDialog(@Nullable Bundle bundle) { + if (bundle != null) { + boolean proceedLockUnlock = bundle.getBoolean(BundleConstant.EXTRA); + if (proceedLockUnlock) { + onLockUnlockConversations(); + } + } + } + + @Override public void onLockUnlockConversations() { + PullRequestModel currentPullRequest = getPullRequest(); + if (currentPullRequest == null) return; + IssueService service = RestProvider.getIssueService(); + Observable> observable = RxHelper + .getObserver(isLocked() ? service.unlockIssue(login, repoId, issueNumber) : + service.lockIssue(login, repoId, issueNumber)); + makeRestCall(observable, booleanResponse -> { + int code = booleanResponse.code(); + if (code == 204) { + pullRequest.setLocked(!isLocked()); + sendToView(PullRequestPagerMvp.View::onSetupIssue); + } + }); + } + + @NonNull @Override public SpannableBuilder getMergeBy(@NonNull PullRequestModel pullRequest, @NonNull Context context) { + return PullRequestModel.getMergeBy(pullRequest, context); + } + + @Override public void onMerge() { + if (isMergeable() && (isOwner() || isRepoOwner())) {//double the checking + MergeRequestModel mergeRequestModel = new MergeRequestModel(); +// mergeRequestModel.setBase(String.valueOf(getPullRequest().getBase().getId())); +// mergeRequestModel.setHead(String.valueOf(getPullRequest().getHead().getId())); +// mergeRequestModel.setSha(getPullRequest().getBase().getSha()); +// mergeRequestModel.setCommitMessage("Hello World"); + manageSubscription( + RxHelper.getObserver(RestProvider.getPullRequestSerice().mergePullRequest(login, repoId, issueNumber, mergeRequestModel)) + .doOnSubscribe(() -> sendToView(view -> view.showProgress(0))) + .doOnNext(mergeResponseModel -> { + if (mergeResponseModel.isMerged()) { + sendToView(view -> view.showMessage(R.string.success, R.string.success_merge)); + } else { + sendToView(view -> view.showErrorMessage(mergeResponseModel.getMessage())); + } + }) + .onErrorReturn(throwable -> { + sendToView(view -> view.showErrorMessage(throwable.getMessage())); + return null; + }) + .subscribe() + ); + } + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/PullRequestPagerView.java b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/PullRequestPagerView.java new file mode 100644 index 00000000..48c192d5 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/PullRequestPagerView.java @@ -0,0 +1,213 @@ +package com.fastaccess.ui.modules.repos.pull_requests.pull_request.details; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.FloatingActionButton; +import android.support.design.widget.TabLayout; +import android.support.v4.view.ViewPager; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.data.dao.FragmentPagerAdapterModel; +import com.fastaccess.data.dao.PullRequestModel; +import com.fastaccess.data.dao.UserModel; +import com.fastaccess.helper.ActivityHelper; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.ui.adapter.FragmentsPagerAdapter; +import com.fastaccess.ui.base.BaseActivity; +import com.fastaccess.ui.modules.repos.issues.issue.details.comments.IssueCommentsView; +import com.fastaccess.ui.widgets.AvatarLayout; +import com.fastaccess.ui.widgets.FontTextView; +import com.fastaccess.ui.widgets.ForegroundImageView; +import com.fastaccess.ui.widgets.SpannableBuilder; +import com.fastaccess.ui.widgets.ViewPagerView; +import com.fastaccess.ui.widgets.dialog.MessageDialogView; + +import butterknife.BindView; +import butterknife.OnClick; + +/** + * Created by Kosh on 10 Dec 2016, 9:23 AM + */ + +public class PullRequestPagerView extends BaseActivity implements PullRequestPagerMvp.View { + + @BindView(R.id.startGist) ForegroundImageView startGist; + @BindView(R.id.forkGist) ForegroundImageView forkGist; + @BindView(R.id.avatarLayout) AvatarLayout avatarLayout; + @BindView(R.id.headerTitle) FontTextView title; + @BindView(R.id.size) FontTextView size; + @BindView(R.id.date) FontTextView date; + @BindView(R.id.tabs) TabLayout tabs; + @BindView(R.id.pager) ViewPagerView pager; + @BindView(R.id.fab) FloatingActionButton fab; + + public static Intent createIntent(@NonNull Context context, @NonNull String repoId, @NonNull String login, int number) { + Intent intent = new Intent(context, PullRequestPagerView.class); + intent.putExtras(Bundler.start() + .put(BundleConstant.ID, number) + .put(BundleConstant.EXTRA, login) + .put(BundleConstant.EXTRA_TWO, repoId) + .end()); + return intent; + + } + + @OnClick(R.id.headerTitle) void onTitleClick() { + if (getPresenter().getPullRequest() != null && !InputHelper.isEmpty(getPresenter().getPullRequest().getTitle())) + showMessage(getString(R.string.details), getPresenter().getPullRequest().getTitle()); + } + + @OnClick(R.id.fab) void onAddComment() { + IssueCommentsView view = (IssueCommentsView) pager.getAdapter().instantiateItem(pager, 2); + if (view != null) { + view.onStartNewComment(); + } + } + + @Override protected int layout() { + return R.layout.issue_pager_activity; + } + + @Override protected boolean isTransparent() { + return false; + } + + @Override protected boolean canBack() { + return true; + } + + @Override protected boolean isSecured() { + return false; + } + + @NonNull @Override public PullRequestPagerPresenter providePresenter() { + return new PullRequestPagerPresenter(); + } + + @Override protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (savedInstanceState == null) { + getPresenter().onActivityCreated(getIntent()); + } else { + onSetupIssue(); + } + tabs.setTabMode(TabLayout.MODE_SCROLLABLE); + startGist.setVisibility(View.GONE); + forkGist.setVisibility(View.GONE); + } + + @Override public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.pull_request_menu, menu); + menu.findItem(R.id.merge).setVisible(getPresenter().isOwner() && getPresenter().isMergeable()); + menu.findItem(R.id.lockIssue).setVisible(getPresenter().isOwner() || getPresenter().isRepoOwner()); + return super.onCreateOptionsMenu(menu); + } + + @Override public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.share) { + if (getPresenter().getPullRequest() != null) ActivityHelper.shareUrl(this, getPresenter().getPullRequest().getHtmlUrl()); + return true; + } else if (item.getItemId() == R.id.lockIssue) { + MessageDialogView.newInstance( + getPresenter().isLocked() ? getString(R.string.unlock_issue) : getString(R.string.lock_issue), + getPresenter().isLocked() ? getString(R.string.unlock_issue_details) : getString(R.string.lock_issue_details), + Bundler.start().put(BundleConstant.EXTRA, true).end()) + .show(getSupportFragmentManager(), MessageDialogView.TAG); + } else if (item.getItemId() == R.id.merge) { + getPresenter().onMerge(); + } + return super.onOptionsItemSelected(item); + } + + @Override public boolean onPrepareOptionsMenu(Menu menu) { + MenuItem lockIssue = menu.findItem(R.id.lockIssue); + boolean isRepoOwner = getPresenter().isRepoOwner(); + boolean isOwner = getPresenter().isOwner(); + boolean isLocked = getPresenter().isLocked(); + boolean isMergable = getPresenter().isMergeable(); + lockIssue.setVisible(isRepoOwner); + if (isRepoOwner) { + lockIssue.setTitle(isLocked ? getString(R.string.unlock_issue) : getString(R.string.lock_issue)); + } + menu.findItem(R.id.merge).setVisible(isMergable && (isOwner || isRepoOwner)); + return super.onPrepareOptionsMenu(menu); + } + + @Override public void onSetupIssue() { + hideProgress(); + if (getPresenter().getPullRequest() == null) { + finish(); + return; + } + supportInvalidateOptionsMenu(); + PullRequestModel pullRequest = getPresenter().getPullRequest(); + setTitle(String.format("#%s", pullRequest.getNumber())); + UserModel userModel = pullRequest.getUser(); + if (userModel != null) { + title.setText(SpannableBuilder.builder().append(userModel.getLogin()).append("/").append(pullRequest.getTitle())); + date.setVisibility(View.GONE); + boolean isMerge = !InputHelper.isEmpty(pullRequest.getMergedAt()); + int status = !isMerge ? pullRequest.getState().getStatus() : R.string.merged; + size.setText(getPresenter().getMergeBy(pullRequest, getApplicationContext())); + avatarLayout.setUrl(userModel.getAvatarUrl(), userModel.getLogin()); + } else { + title.setText(SpannableBuilder.builder().append(pullRequest.getTitle())); + } + pager.setAdapter(new FragmentsPagerAdapter(getSupportFragmentManager(), FragmentPagerAdapterModel.buildForPullRequest(this, pullRequest))); + tabs.setupWithViewPager(pager); + if (!getPresenter().isLocked() || getPresenter().isOwner()) { + pager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { + @Override public void onPageSelected(int position) { + super.onPageSelected(position); + hideShowFab(); + } + }); + } + hideShowFab(); + } + + @Override public void showSuccessIssueActionMsg(boolean isClose) { + hideProgress(); + if (isClose) { + showMessage(getString(R.string.success), getString(R.string.success_closed)); + } else { + showMessage(getString(R.string.success), getString(R.string.success_re_opened)); + } + } + + @Override public void showErrorIssueActionMsg(boolean isClose) { + hideProgress(); + if (isClose) { + showMessage(getString(R.string.error), getString(R.string.error_closing_issue)); + } else { + showMessage(getString(R.string.error), getString(R.string.error_re_opening_issue)); + } + } + + @Override public void onMessageDialogActionClicked(boolean isOk, @Nullable Bundle bundle) { + super.onMessageDialogActionClicked(isOk, bundle); + if (isOk) { + getPresenter().onHandleConfirmDialog(bundle); + } + } + + private void hideShowFab() { + if (getPresenter().isLocked() && !getPresenter().isOwner()) { + fab.hide(); + return; + } + if (pager.getCurrentItem() == 2) { + fab.show(); + } else { + fab.hide(); + } + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/commits/PullRequestCommitsMvp.java b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/commits/PullRequestCommitsMvp.java new file mode 100644 index 00000000..e0ed596a --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/commits/PullRequestCommitsMvp.java @@ -0,0 +1,35 @@ +package com.fastaccess.ui.modules.repos.pull_requests.pull_request.details.commits; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.widget.SwipeRefreshLayout; + +import com.fastaccess.data.dao.CommitModel; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.ArrayList; + +/** + * Created by Kosh on 03 Dec 2016, 3:45 PM + */ + +interface PullRequestCommitsMvp { + + interface View extends BaseMvp.FAView, SwipeRefreshLayout.OnRefreshListener, android.view.View.OnClickListener { + void onNotifyAdapter(); + + @NonNull OnLoadMore getLoadMore(); + } + + interface Presenter extends BaseMvp.FAPresenter, + BaseViewHolder.OnItemClickListener, + BaseMvp.PaginationListener { + void onFragmentCreated(@NonNull Bundle bundle); + + @NonNull ArrayList getCommits(); + + void onWorkOffline(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/commits/PullRequestCommitsPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/commits/PullRequestCommitsPresenter.java new file mode 100644 index 00000000..bf294c5a --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/commits/PullRequestCommitsPresenter.java @@ -0,0 +1,110 @@ +package com.fastaccess.ui.modules.repos.pull_requests.pull_request.details.commits; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; + +import com.fastaccess.data.dao.CommitModel; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; +import com.fastaccess.ui.modules.repos.code.commit.details.CommitPagerView; + +import java.util.ArrayList; + +import rx.Observable; + +/** + * Created by Kosh on 03 Dec 2016, 3:48 PM + */ + +class PullRequestCommitsPresenter extends BasePresenter implements PullRequestCommitsMvp.Presenter { + + private ArrayList commits = new ArrayList<>(); + private String login; + private String repoId; + private long number; + private int page; + private int previousTotal; + private int lastPage = Integer.MAX_VALUE; + + @Override public int getCurrentPage() { + return page; + } + + @Override public int getPreviousTotal() { + return previousTotal; + } + + @Override public void setCurrentPage(int page) { + this.page = page; + } + + @Override public void setPreviousTotal(int previousTotal) { + this.previousTotal = previousTotal; + } + + @Override public T onError(@NonNull Throwable throwable, @NonNull Observable observable) { + onWorkOffline(); + return super.onError(throwable, observable); + } + + @Override public void onCallApi(int page, @Nullable Object parameter) { + if (page == 1) { + lastPage = Integer.MAX_VALUE; + sendToView(view -> view.getLoadMore().reset()); + } + setCurrentPage(page); + if (page > lastPage || lastPage == 0) { + sendToView(PullRequestCommitsMvp.View::hideProgress); + return; + } + if (repoId == null || login == null) return; + makeRestCall(RestProvider.getPullRequestSerice().getPullRequestCommits(login, repoId, number, page), + response -> { + lastPage = response.getLast(); + if (getCurrentPage() == 1) { + getCommits().clear(); + manageSubscription(CommitModel.save(response.getItems(), repoId, login, number).subscribe()); + } + getCommits().addAll(response.getItems()); + sendToView(PullRequestCommitsMvp.View::onNotifyAdapter); + }); + } + + @Override public void onFragmentCreated(@NonNull Bundle bundle) { + repoId = bundle.getString(BundleConstant.ID); + login = bundle.getString(BundleConstant.EXTRA); + number = bundle.getLong(BundleConstant.EXTRA_TWO); + if (!InputHelper.isEmpty(login) && !InputHelper.isEmpty(repoId)) { + onCallApi(1, null); + } + } + + @NonNull @Override public ArrayList getCommits() { + return commits; + } + + @Override public void onWorkOffline() { + if (commits.isEmpty()) { + manageSubscription(CommitModel.getCommits(repoId, login, number) + .subscribe(models -> { + commits.addAll(models); + sendToView(PullRequestCommitsMvp.View::onNotifyAdapter); + })); + } else { + sendToView(BaseMvp.FAView::hideProgress); + } + } + + @Override public void onItemClick(int position, View v, CommitModel item) { + CommitPagerView.createIntentForOffline(v.getContext(), item); + } + + @Override public void onItemLongClick(int position, View v, CommitModel item) { + onItemClick(position, v, item); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/commits/PullRequestCommitsView.java b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/commits/PullRequestCommitsView.java new file mode 100644 index 00000000..bfdb0fdf --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/commits/PullRequestCommitsView.java @@ -0,0 +1,106 @@ +package com.fastaccess.ui.modules.repos.pull_requests.pull_request.details.commits; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.v4.widget.SwipeRefreshLayout; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.adapter.CommitsAdapter; +import com.fastaccess.ui.base.BaseFragment; +import com.fastaccess.ui.widgets.StateLayout; +import com.fastaccess.ui.widgets.recyclerview.DynamicRecyclerView; + +import butterknife.BindView; + +/** + * Created by Kosh on 03 Dec 2016, 3:56 PM + */ + +public class PullRequestCommitsView extends BaseFragment + implements PullRequestCommitsMvp.View { + + @BindView(R.id.recycler) DynamicRecyclerView recycler; + @BindView(R.id.refresh) SwipeRefreshLayout refresh; + @BindView(R.id.stateLayout) StateLayout stateLayout; + private OnLoadMore onLoadMore; + private CommitsAdapter adapter; + + public static PullRequestCommitsView newInstance(@NonNull String repoId, @NonNull String login, long number) { + PullRequestCommitsView view = new PullRequestCommitsView(); + view.setArguments(Bundler.start() + .put(BundleConstant.ID, repoId) + .put(BundleConstant.EXTRA, login) + .put(BundleConstant.EXTRA_TWO, number) + .end()); + return view; + } + + @Override public void onNotifyAdapter() { + hideProgress(); + adapter.notifyDataSetChanged(); + } + + @Override protected int fragmentLayout() { + return R.layout.small_grid_refresh_list; + } + + @Override protected void onFragmentCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + if (getArguments() == null) { + throw new NullPointerException("Bundle is null, therefore, PullRequestCommitsView can't be proceeded."); + } + stateLayout.setOnReloadListener(this); + refresh.setOnRefreshListener(this); + recycler.setEmptyView(stateLayout, refresh); + adapter = new CommitsAdapter(getPresenter().getCommits()); + adapter.setListener(getPresenter()); + getLoadMore().setCurrent_page(getPresenter().getCurrentPage(), getPresenter().getPreviousTotal()); + recycler.setAdapter(adapter); + recycler.addOnScrollListener(getLoadMore()); + if (savedInstanceState == null) { + getPresenter().onFragmentCreated(getArguments()); + } else if (getPresenter().getCommits().isEmpty() && !getPresenter().isApiCalled()) { + onRefresh(); + } + } + + @NonNull @Override public PullRequestCommitsPresenter providePresenter() { + return new PullRequestCommitsPresenter(); + } + + @Override public void showProgress(@StringRes int resId) { + refresh.setRefreshing(true); + stateLayout.showProgress(); + } + + @Override public void hideProgress() { + refresh.setRefreshing(false); + stateLayout.hideProgress(); + } + + @Override public void showErrorMessage(@NonNull String msgRes) { + hideProgress(); + stateLayout.showReload(adapter.getItemCount()); + super.showErrorMessage(msgRes); + } + + @SuppressWarnings("unchecked") @NonNull @Override public OnLoadMore getLoadMore() { + if (onLoadMore == null) { + onLoadMore = new OnLoadMore(getPresenter()); + } + return onLoadMore; + } + + @Override public void onRefresh() { + getPresenter().onCallApi(1, null); + } + + @Override public void onClick(View view) { + onRefresh(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/events/PullRequestDetailsMvp.java b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/events/PullRequestDetailsMvp.java new file mode 100644 index 00000000..82a601c0 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/events/PullRequestDetailsMvp.java @@ -0,0 +1,34 @@ +package com.fastaccess.ui.modules.repos.pull_requests.pull_request.details.events; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.widget.SwipeRefreshLayout; + +import com.fastaccess.data.dao.PullRequestAdapterModel; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.ArrayList; + +/** + * Created by Kosh on 13 Dec 2016, 12:36 AM + */ + +interface PullRequestDetailsMvp { + + interface View extends BaseMvp.FAView, SwipeRefreshLayout.OnRefreshListener, android.view.View.OnClickListener { + void onNotifyAdapter(); + + @NonNull OnLoadMore getLoadMore(); + } + + interface Presenter extends BaseMvp.FAPresenter, BaseViewHolder.OnItemClickListener, + BaseMvp.PaginationListener { + + @NonNull ArrayList getEvents(); + + void onFragmentCreated(@Nullable Bundle bundle); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/events/PullRequestDetailsPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/events/PullRequestDetailsPresenter.java new file mode 100644 index 00000000..1ec0ec9e --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/events/PullRequestDetailsPresenter.java @@ -0,0 +1,101 @@ +package com.fastaccess.ui.modules.repos.pull_requests.pull_request.details.events; + +import android.app.Activity; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; + +import com.fastaccess.data.dao.IssueEventAdapterModel; +import com.fastaccess.data.dao.IssueEventModel; +import com.fastaccess.data.dao.PullRequestAdapterModel; +import com.fastaccess.data.dao.PullRequestModel; +import com.fastaccess.helper.ActivityHelper; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Logger; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; + +import java.util.ArrayList; + +/** + * Created by Kosh on 13 Dec 2016, 12:38 AM + */ + +class PullRequestDetailsPresenter extends BasePresenter implements PullRequestDetailsMvp.Presenter { + private int page; + private int previousTotal; + private int lastPage = Integer.MAX_VALUE; + private ArrayList events = new ArrayList<>(); + private PullRequestModel pullRequest; + + @Override public void onFragmentCreated(@Nullable Bundle bundle) { + if (bundle == null) throw new NullPointerException("Bundle is null?"); + pullRequest = bundle.getParcelable(BundleConstant.ITEM); + if (events.isEmpty()) { + events.add(0, new PullRequestAdapterModel(PullRequestAdapterModel.HEADER, pullRequest)); + sendToView(PullRequestDetailsMvp.View::onNotifyAdapter); + } + } + + @NonNull @Override public ArrayList getEvents() { + return events; + } + + @Override public void onItemClick(int position, View v, PullRequestAdapterModel item) { + Logger.e(item.getType()); + if (item.getType() != IssueEventAdapterModel.HEADER) { + IssueEventModel issueEventModel = item.getIssueEvent(); + if (issueEventModel.getCommitUrl() != null) { + Activity activity = ActivityHelper.getActivity(v.getContext()); + if (activity != null) { + ActivityHelper.startCustomTab(activity, issueEventModel.getCommitUrl()); + } + } + } + } + + @Override public void onItemLongClick(int position, View v, PullRequestAdapterModel item) { + //TODO + } + + @Override public int getCurrentPage() { + return page; + } + + @Override public int getPreviousTotal() { + return previousTotal; + } + + @Override public void setCurrentPage(int page) { + this.page = page; + } + + @Override public void setPreviousTotal(int previousTotal) { + this.previousTotal = previousTotal; + } + + @Override public void onCallApi(int page, @Nullable Object parameter) { + if (page == 1) { + lastPage = Integer.MAX_VALUE; + sendToView(view -> view.getLoadMore().reset()); + } + if (page > lastPage || lastPage == 0) { + sendToView(PullRequestDetailsMvp.View::hideProgress); + return; + } + setCurrentPage(page); + String login = pullRequest.getLogin(); + String repoID = pullRequest.getRepoId(); + int number = pullRequest.getNumber(); + makeRestCall(RestProvider.getIssueService().getTimeline(login, repoID, number, page), + response -> { + lastPage = response.getLast(); + if (getCurrentPage() == 1) { + getEvents().subList(1, getEvents().size()).clear(); + } + getEvents().addAll(PullRequestAdapterModel.addEvents(response.getItems())); + sendToView(PullRequestDetailsMvp.View::onNotifyAdapter); + }); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/events/PullRequestDetailsView.java b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/events/PullRequestDetailsView.java new file mode 100644 index 00000000..eb22b61d --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/events/PullRequestDetailsView.java @@ -0,0 +1,98 @@ +package com.fastaccess.ui.modules.repos.pull_requests.pull_request.details.events; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.data.dao.PullRequestModel; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.adapter.PullRequestTimelineAdapter; +import com.fastaccess.ui.base.BaseFragment; +import com.fastaccess.ui.widgets.AppbarRefreshLayout; +import com.fastaccess.ui.widgets.StateLayout; +import com.fastaccess.ui.widgets.recyclerview.DynamicRecyclerView; + +import butterknife.BindView; + +/** + * Created by Kosh on 13 Dec 2016, 12:40 AM + */ + +public class PullRequestDetailsView extends BaseFragment implements PullRequestDetailsMvp + .View { + @BindView(R.id.recycler) DynamicRecyclerView recycler; + @BindView(R.id.refresh) AppbarRefreshLayout refresh; + @BindView(R.id.stateLayout) StateLayout stateLayout; + private PullRequestTimelineAdapter adapter; + private OnLoadMore onLoadMore; + + public static PullRequestDetailsView newInstance(@NonNull PullRequestModel issueModel) { + PullRequestDetailsView view = new PullRequestDetailsView(); + view.setArguments(Bundler.start().put(BundleConstant.ITEM, issueModel).end()); + return view; + } + + @Override protected int fragmentLayout() { + return R.layout.small_grid_refresh_list; + } + + @Override protected void onFragmentCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + if (savedInstanceState == null) getPresenter().onFragmentCreated(getArguments()); + recycler.setEmptyView(stateLayout, refresh); + refresh.setOnRefreshListener(this); + stateLayout.setOnReloadListener(this); + adapter = new PullRequestTimelineAdapter(getPresenter().getEvents()); + adapter.setListener(getPresenter()); + getLoadMore().setCurrent_page(getPresenter().getCurrentPage(), getPresenter().getPreviousTotal()); + recycler.setAdapter(adapter); + recycler.addOnScrollListener(getLoadMore()); + if (getPresenter().getEvents().size() == 1 && !getPresenter().isApiCalled()) { + onRefresh(); + } + } + + @NonNull @Override public PullRequestDetailsPresenter providePresenter() { + return new PullRequestDetailsPresenter(); + } + + @Override public void onRefresh() { + getPresenter().onCallApi(1, null); + } + + @Override public void onNotifyAdapter() { + hideProgress(); + adapter.notifyDataSetChanged(); + } + + @Override public void hideProgress() { + refresh.setRefreshing(false); + stateLayout.hideProgress(); + } + + @Override public void showProgress(@StringRes int msgId) { + refresh.setRefreshing(true); + stateLayout.showProgress(); + } + + @Override public void showErrorMessage(@NonNull String message) { + hideProgress(); + stateLayout.showReload(adapter.getItemCount()); + super.showErrorMessage(message); + } + + @SuppressWarnings("unchecked") @NonNull @Override public OnLoadMore getLoadMore() { + if (onLoadMore == null) { + onLoadMore = new OnLoadMore(getPresenter()); + } + return onLoadMore; + } + + @Override public void onClick(View view) { + onRefresh(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/search/SearchMvp.java b/app/src/main/java/com/fastaccess/ui/modules/search/SearchMvp.java new file mode 100644 index 00000000..cbe9448a --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/search/SearchMvp.java @@ -0,0 +1,30 @@ +package com.fastaccess.ui.modules.search; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.view.ViewPager; +import android.widget.AutoCompleteTextView; + +import com.fastaccess.data.dao.SearchHistoryModel; +import com.fastaccess.ui.base.mvp.BaseMvp; + +import java.util.ArrayList; + +/** + * Created by Kosh on 08 Dec 2016, 8:19 PM + */ + +interface SearchMvp { + + interface View extends BaseMvp.FAView { + void onNotifyAdapter(@Nullable SearchHistoryModel query); + } + + interface Presenter extends BaseMvp.FAPresenter { + + @NonNull ArrayList getHints(); + + void onSearchClicked(@NonNull ViewPager viewPager, @NonNull AutoCompleteTextView editText); + + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/search/SearchPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/search/SearchPresenter.java new file mode 100644 index 00000000..4503619f --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/search/SearchPresenter.java @@ -0,0 +1,62 @@ +package com.fastaccess.ui.modules.search; + +import android.support.annotation.NonNull; +import android.support.v4.view.ViewPager; +import android.widget.AutoCompleteTextView; + +import com.annimon.stream.Stream; +import com.fastaccess.R; +import com.fastaccess.data.dao.SearchHistoryModel; +import com.fastaccess.helper.AppHelper; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; +import com.fastaccess.ui.modules.search.code.SearchCodeView; +import com.fastaccess.ui.modules.search.issues.SearchIssuesView; +import com.fastaccess.ui.modules.search.repos.SearchReposView; +import com.fastaccess.ui.modules.search.users.SearchUsersView; + +import java.util.ArrayList; + + +/** + * Created by Kosh on 08 Dec 2016, 8:20 PM + */ +class SearchPresenter extends BasePresenter implements SearchMvp.Presenter { + private ArrayList hints = new ArrayList<>(); + + @Override protected void onAttachView(@NonNull SearchMvp.View view) { + super.onAttachView(view); + manageSubscription(SearchHistoryModel.getHistory() + .subscribe(strings -> { + if (strings != null) hints.addAll(strings); + view.onNotifyAdapter(null); + })); + } + + @NonNull @Override public ArrayList getHints() { + return hints; + } + + @Override public void onSearchClicked(@NonNull ViewPager viewPager, @NonNull AutoCompleteTextView editText) { + boolean isEmpty = InputHelper.isEmpty(editText) || InputHelper.toString(editText).length() < 3; + editText.setError(isEmpty ? editText.getResources().getString(R.string.minimum_three_chars) : null); + if (!isEmpty) { + editText.dismissDropDown(); + AppHelper.hideKeyboard(editText); + String query = InputHelper.toString(editText); + SearchReposView repos = (SearchReposView) viewPager.getAdapter().instantiateItem(viewPager, 0); + SearchUsersView users = (SearchUsersView) viewPager.getAdapter().instantiateItem(viewPager, 1); + SearchIssuesView issues = (SearchIssuesView) viewPager.getAdapter().instantiateItem(viewPager, 2); + SearchCodeView code = (SearchCodeView) viewPager.getAdapter().instantiateItem(viewPager, 3); + repos.onSetSearchQuery(query); + users.onSetSearchQuery(query); + issues.onSetSearchQuery(query); + code.onSetSearchQuery(query); + boolean noneMatch = Stream.of(hints).map(searchHistoryModel -> !searchHistoryModel.getText().equalsIgnoreCase(query)).findFirst().get(); + if (noneMatch) { + manageSubscription(new SearchHistoryModel(query).save().subscribe()); + sendToView(view -> view.onNotifyAdapter(new SearchHistoryModel(query))); + } + } + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/search/SearchView.java b/app/src/main/java/com/fastaccess/ui/modules/search/SearchView.java new file mode 100644 index 00000000..24084b62 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/search/SearchView.java @@ -0,0 +1,108 @@ +package com.fastaccess.ui.modules.search; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.AppBarLayout; +import android.support.design.widget.TabLayout; +import android.text.Editable; +import android.view.KeyEvent; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.widget.ArrayAdapter; + +import com.fastaccess.R; +import com.fastaccess.data.dao.FragmentPagerAdapterModel; +import com.fastaccess.data.dao.SearchHistoryModel; +import com.fastaccess.helper.AnimHelper; +import com.fastaccess.helper.AppHelper; +import com.fastaccess.ui.adapter.FragmentsPagerAdapter; +import com.fastaccess.ui.base.BaseActivity; +import com.fastaccess.ui.widgets.FontAutoCompleteEditText; +import com.fastaccess.ui.widgets.ForegroundImageView; +import com.fastaccess.ui.widgets.ViewPagerView; + +import butterknife.BindView; +import butterknife.OnClick; +import butterknife.OnEditorAction; +import butterknife.OnTextChanged; + +/** + * Created by Kosh on 08 Dec 2016, 8:22 PM + */ + +public class SearchView extends BaseActivity implements SearchMvp.View { + + @BindView(R.id.searchEditText) FontAutoCompleteEditText searchEditText; + @BindView(R.id.clear) ForegroundImageView clear; + @BindView(R.id.tabs) TabLayout tabs; + @BindView(R.id.appbar) AppBarLayout appbar; + @BindView(R.id.pager) ViewPagerView pager; + + private ArrayAdapter adapter; + + @OnTextChanged(value = R.id.searchEditText, callback = OnTextChanged.Callback.AFTER_TEXT_CHANGED) + void onTextChange(Editable s) { + String text = s.toString(); + if (text.length() == 0) { + AnimHelper.animateVisibility(clear, false); + } else { + AnimHelper.animateVisibility(clear, true); + } + } + + @OnEditorAction(R.id.searchEditText) boolean onEditor(int actionId, KeyEvent keyEvent) { + if (keyEvent != null && keyEvent.getAction() == KeyEvent.KEYCODE_SEARCH) { + getPresenter().onSearchClicked(pager, searchEditText); + } else if (actionId == EditorInfo.IME_ACTION_SEARCH) { + getPresenter().onSearchClicked(pager, searchEditText); + } + return false; + } + + @OnClick(value = {R.id.clear}) void onClear(View view) { + if (view.getId() == R.id.clear) { + AppHelper.hideKeyboard(searchEditText); + searchEditText.setText(""); + } + } + + @Override protected int layout() { + return R.layout.search_layout; + } + + @Override protected boolean isTransparent() { + return false; + } + + @Override protected boolean canBack() { + return true; + } + + @Override protected boolean isSecured() { + return false; + } + + @NonNull @Override public SearchPresenter providePresenter() { + return new SearchPresenter(); + } + + @Override protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setTitle(""); + pager.setAdapter(new FragmentsPagerAdapter(getSupportFragmentManager(), FragmentPagerAdapterModel.buildForSearch(this))); + tabs.setupWithViewPager(pager); + searchEditText.setAdapter(getAdapter()); + searchEditText.setOnItemClickListener((parent, view, position, id) -> getPresenter().onSearchClicked(pager, searchEditText)); + } + + @Override public void onNotifyAdapter(@Nullable SearchHistoryModel query) { + if (query == null) getAdapter().notifyDataSetChanged(); + else getAdapter().add(query); + } + + private ArrayAdapter getAdapter() { + if (adapter == null) adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, getPresenter().getHints()); + return adapter; + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/search/code/SearchCodeMvp.java b/app/src/main/java/com/fastaccess/ui/modules/search/code/SearchCodeMvp.java new file mode 100644 index 00000000..abe664e9 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/search/code/SearchCodeMvp.java @@ -0,0 +1,36 @@ +package com.fastaccess.ui.modules.search.code; + +import android.support.annotation.NonNull; +import android.support.v4.widget.SwipeRefreshLayout; + +import com.fastaccess.data.dao.SearchCodeModel; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.ArrayList; + +/** + * Created by Kosh on 03 Dec 2016, 3:45 PM + */ + +interface SearchCodeMvp { + + interface View extends BaseMvp.FAView, SwipeRefreshLayout.OnRefreshListener, android.view.View.OnClickListener { + void onNotifyAdapter(); + + void onSetSearchQuery(@NonNull String query); + + @NonNull OnLoadMore getLoadMore(); + + void onItemClicked(@NonNull SearchCodeModel item); + } + + interface Presenter extends BaseMvp.FAPresenter, + BaseViewHolder.OnItemClickListener, + BaseMvp.PaginationListener { + + @NonNull ArrayList getCodes(); + + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/search/code/SearchCodePresenter.java b/app/src/main/java/com/fastaccess/ui/modules/search/code/SearchCodePresenter.java new file mode 100644 index 00000000..035317b9 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/search/code/SearchCodePresenter.java @@ -0,0 +1,78 @@ +package com.fastaccess.ui.modules.search.code; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; + +import com.fastaccess.data.dao.SearchCodeModel; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; + +import java.util.ArrayList; + +/** + * Created by Kosh on 03 Dec 2016, 3:48 PM + */ + +class SearchCodePresenter extends BasePresenter implements SearchCodeMvp.Presenter { + + private ArrayList codes = new ArrayList<>(); + private int page; + private int previousTotal; + private int lastPage = Integer.MAX_VALUE; + + @Override public int getCurrentPage() { + return page; + } + + @Override public int getPreviousTotal() { + return previousTotal; + } + + @Override public void setCurrentPage(int page) { + this.page = page; + } + + @Override public void setPreviousTotal(int previousTotal) { + this.previousTotal = previousTotal; + } + + @Override public void onCallApi(int page, @Nullable String parameter) { + if (page == 1) { + lastPage = Integer.MAX_VALUE; + sendToView(view -> view.getLoadMore().reset()); + } + setCurrentPage(page); + if (page > lastPage || lastPage == 0) { + sendToView(SearchCodeMvp.View::hideProgress); + return; + } + if (parameter == null) { + return; + } + makeRestCall(RestProvider.getSearchService().searchCode(parameter, page), + response -> { + if (response.isIncompleteResults()) return; + lastPage = response.getLast(); + if (getCurrentPage() == 1) { + getCodes().clear(); + } + getCodes().addAll(response.getItems()); + sendToView(SearchCodeMvp.View::onNotifyAdapter); + }); + } + + @NonNull @Override public ArrayList getCodes() { + return codes; + } + + @Override public void onItemClick(int position, View v, SearchCodeModel item) { + if (getView() != null) { + getView().onItemClicked(item); + } + } + + @Override public void onItemLongClick(int position, View v, SearchCodeModel item) { + onItemClick(position, v, item); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/search/code/SearchCodeView.java b/app/src/main/java/com/fastaccess/ui/modules/search/code/SearchCodeView.java new file mode 100644 index 00000000..52a3705e --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/search/code/SearchCodeView.java @@ -0,0 +1,117 @@ +package com.fastaccess.ui.modules.search.code; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.v4.widget.SwipeRefreshLayout; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.data.dao.SearchCodeModel; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.adapter.SearchCodeAdapter; +import com.fastaccess.ui.base.BaseFragment; +import com.fastaccess.ui.modules.code.CodeViewerView; +import com.fastaccess.ui.widgets.StateLayout; +import com.fastaccess.ui.widgets.recyclerview.DynamicRecyclerView; + +import butterknife.BindView; +import icepick.State; + +/** + * Created by Kosh on 03 Dec 2016, 3:56 PM + */ + +public class SearchCodeView extends BaseFragment implements SearchCodeMvp.View { + + @State String searchQuery; + @BindView(R.id.recycler) DynamicRecyclerView recycler; + @BindView(R.id.refresh) SwipeRefreshLayout refresh; + @BindView(R.id.stateLayout) StateLayout stateLayout; + private OnLoadMore onLoadMore; + private SearchCodeAdapter adapter; + + public static SearchCodeView newInstance() { + return new SearchCodeView(); + } + + @Override public void onNotifyAdapter() { + hideProgress(); + adapter.notifyDataSetChanged(); + } + + @Override protected int fragmentLayout() { + return R.layout.small_grid_refresh_list; + } + + @Override protected void onFragmentCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + if (savedInstanceState == null) { + stateLayout.hideProgress(); + } + getLoadMore().setCurrent_page(getPresenter().getCurrentPage(), getPresenter().getPreviousTotal()); + stateLayout.setOnReloadListener(this); + refresh.setOnRefreshListener(this); + recycler.setEmptyView(stateLayout, refresh); + adapter = new SearchCodeAdapter(getPresenter().getCodes()); + adapter.setListener(getPresenter()); + recycler.setAdapter(adapter); + if (!InputHelper.isEmpty(searchQuery) && getPresenter().getCodes().isEmpty() && !getPresenter().isApiCalled()) { + onRefresh(); + } + } + + @NonNull @Override public SearchCodePresenter providePresenter() { + return new SearchCodePresenter(); + } + + @Override public void hideProgress() { + refresh.setRefreshing(false); + stateLayout.hideProgress(); + } + + @Override public void showProgress(@StringRes int resId) { + refresh.setRefreshing(true); + stateLayout.showProgress(); + } + + @Override public void showErrorMessage(@NonNull String message) { + hideProgress(); + stateLayout.showReload(adapter.getItemCount()); + super.showErrorMessage(message); + } + + @Override public void onSetSearchQuery(@NonNull String query) { + this.searchQuery = query; + getLoadMore().reset(); + getPresenter().getCodes().clear(); + onNotifyAdapter(); + recycler.scrollToPosition(0); + if (!InputHelper.isEmpty(query)) { + recycler.removeOnScrollListener(getLoadMore()); + recycler.addOnScrollListener(getLoadMore()); + onRefresh(); + } + } + + @NonNull @Override public OnLoadMore getLoadMore() { + if (onLoadMore == null) { + onLoadMore = new OnLoadMore<>(getPresenter(), searchQuery); + } + onLoadMore.setParameter(searchQuery); + return onLoadMore; + } + + @Override public void onItemClicked(@NonNull SearchCodeModel item) { + CodeViewerView.startActivity(getContext(), item.getUrl()); + } + + @Override public void onRefresh() { + getPresenter().onCallApi(1, searchQuery); + } + + @Override public void onClick(View view) { + onRefresh(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/search/issues/SearchIssuesMvp.java b/app/src/main/java/com/fastaccess/ui/modules/search/issues/SearchIssuesMvp.java new file mode 100644 index 00000000..3b2d8b96 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/search/issues/SearchIssuesMvp.java @@ -0,0 +1,34 @@ +package com.fastaccess.ui.modules.search.issues; + +import android.support.annotation.NonNull; +import android.support.v4.widget.SwipeRefreshLayout; + +import com.fastaccess.data.dao.IssueModel; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.ArrayList; + +/** + * Created by Kosh on 03 Dec 2016, 3:45 PM + */ + +interface SearchIssuesMvp { + + interface View extends BaseMvp.FAView, SwipeRefreshLayout.OnRefreshListener, android.view.View.OnClickListener { + void onNotifyAdapter(); + + void onSetSearchQuery(@NonNull String query); + + @NonNull OnLoadMore getLoadMore(); + } + + interface Presenter extends BaseMvp.FAPresenter, + BaseViewHolder.OnItemClickListener, + BaseMvp.PaginationListener { + + @NonNull ArrayList getIssues(); + + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/search/issues/SearchIssuesPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/search/issues/SearchIssuesPresenter.java new file mode 100644 index 00000000..cbf03fad --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/search/issues/SearchIssuesPresenter.java @@ -0,0 +1,90 @@ +package com.fastaccess.ui.modules.search.issues; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; + +import com.fastaccess.data.dao.IssueModel; +import com.fastaccess.data.dao.PullsIssuesParser; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; +import com.fastaccess.ui.modules.repos.issues.issue.details.IssuePagerView; +import com.fastaccess.ui.modules.repos.pull_requests.pull_request.details.PullRequestPagerView; + +import java.util.ArrayList; + +/** + * Created by Kosh on 03 Dec 2016, 3:48 PM + */ + +class SearchIssuesPresenter extends BasePresenter implements SearchIssuesMvp.Presenter { + + private ArrayList issues = new ArrayList<>(); + private int page; + private int previousTotal; + private int lastPage = Integer.MAX_VALUE; + + @Override public int getCurrentPage() { + return page; + } + + @Override public int getPreviousTotal() { + return previousTotal; + } + + @Override public void setCurrentPage(int page) { + this.page = page; + } + + @Override public void setPreviousTotal(int previousTotal) { + this.previousTotal = previousTotal; + } + + @Override public void onCallApi(int page, @Nullable String parameter) { + if (page == 1) { + lastPage = Integer.MAX_VALUE; + sendToView(view -> view.getLoadMore().reset()); + } + setCurrentPage(page); + if (page > lastPage || lastPage == 0) { + sendToView(SearchIssuesMvp.View::hideProgress); + return; + } + if (parameter == null) { + return; + } + makeRestCall(RestProvider.getSearchService().searchIssues(parameter, page), + response -> { + lastPage = response.getLast(); + if (getCurrentPage() == 1) { + getIssues().clear(); + } + getIssues().addAll(response.getItems()); + sendToView(SearchIssuesMvp.View::onNotifyAdapter); + }); + } + + @NonNull @Override public ArrayList getIssues() { + return issues; + } + + @Override public void onItemClick(int position, View v, IssueModel item) { + if(item.getPullRequest() == null){ + PullsIssuesParser parser = PullsIssuesParser.getForIssue(item.getHtmlUrl()); + if (parser != null) { + v.getContext().startActivity(IssuePagerView.createIntent(v.getContext(), parser.getRepoId(), + parser.getLogin(), parser.getNumber())); + } + }else { + PullsIssuesParser parser = PullsIssuesParser.getForPullRequest(item.getHtmlUrl()); + if (parser != null) { + v.getContext().startActivity(PullRequestPagerView.createIntent(v.getContext(), parser.getRepoId(), + parser.getLogin(), parser.getNumber())); + } + } + } + + @Override public void onItemLongClick(int position, View v, IssueModel item) { + onItemClick(position, v, item); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/search/issues/SearchIssuesView.java b/app/src/main/java/com/fastaccess/ui/modules/search/issues/SearchIssuesView.java new file mode 100644 index 00000000..30a9824b --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/search/issues/SearchIssuesView.java @@ -0,0 +1,111 @@ +package com.fastaccess.ui.modules.search.issues; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.v4.widget.SwipeRefreshLayout; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.adapter.IssuesAdapter; +import com.fastaccess.ui.base.BaseFragment; +import com.fastaccess.ui.widgets.StateLayout; +import com.fastaccess.ui.widgets.recyclerview.DynamicRecyclerView; + +import butterknife.BindView; +import icepick.State; + +/** + * Created by Kosh on 03 Dec 2016, 3:56 PM + */ + +public class SearchIssuesView extends BaseFragment implements SearchIssuesMvp.View { + + @State String searchQuery; + @BindView(R.id.recycler) DynamicRecyclerView recycler; + @BindView(R.id.refresh) SwipeRefreshLayout refresh; + @BindView(R.id.stateLayout) StateLayout stateLayout; + private OnLoadMore onLoadMore; + private IssuesAdapter adapter; + + public static SearchIssuesView newInstance() { + return new SearchIssuesView(); + } + + @Override public void onNotifyAdapter() { + hideProgress(); + adapter.notifyDataSetChanged(); + } + + @Override protected int fragmentLayout() { + return R.layout.small_grid_refresh_list; + } + + @Override protected void onFragmentCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + if (savedInstanceState == null) { + stateLayout.hideProgress(); + } + getLoadMore().setCurrent_page(getPresenter().getCurrentPage(), getPresenter().getPreviousTotal()); + stateLayout.setOnReloadListener(this); + refresh.setOnRefreshListener(this); + recycler.setEmptyView(stateLayout, refresh); + adapter = new IssuesAdapter(getPresenter().getIssues()); + adapter.setListener(getPresenter()); + recycler.setAdapter(adapter); + if (!InputHelper.isEmpty(searchQuery) && getPresenter().getIssues().isEmpty() && !getPresenter().isApiCalled()) { + onRefresh(); + } + } + + @NonNull @Override public SearchIssuesPresenter providePresenter() { + return new SearchIssuesPresenter(); + } + + @Override public void hideProgress() { + refresh.setRefreshing(false); + stateLayout.hideProgress(); + } + + @Override public void showProgress(@StringRes int resId) { + refresh.setRefreshing(true); + stateLayout.showProgress(); + } + + @Override public void showErrorMessage(@NonNull String message) { + hideProgress(); + stateLayout.showReload(adapter.getItemCount()); + super.showErrorMessage(message); + } + + @Override public void onSetSearchQuery(@NonNull String query) { + this.searchQuery = query; + getLoadMore().reset(); + getPresenter().getIssues().clear(); + onNotifyAdapter(); + recycler.scrollToPosition(0); + if (!InputHelper.isEmpty(query)) { + recycler.removeOnScrollListener(getLoadMore()); + recycler.addOnScrollListener(getLoadMore()); + onRefresh(); + } + } + + @NonNull @Override public OnLoadMore getLoadMore() { + if (onLoadMore == null) { + onLoadMore = new OnLoadMore<>(getPresenter(), searchQuery); + } + onLoadMore.setParameter(searchQuery); + return onLoadMore; + } + + @Override public void onRefresh() { + getPresenter().onCallApi(1, searchQuery); + } + + @Override public void onClick(View view) { + onRefresh(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/search/repos/SearchReposMvp.java b/app/src/main/java/com/fastaccess/ui/modules/search/repos/SearchReposMvp.java new file mode 100644 index 00000000..b6323cef --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/search/repos/SearchReposMvp.java @@ -0,0 +1,35 @@ +package com.fastaccess.ui.modules.search.repos; + +import android.support.annotation.NonNull; +import android.support.v4.widget.SwipeRefreshLayout; + +import com.fastaccess.data.dao.RepoModel; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.ArrayList; + +/** + * Created by Kosh on 03 Dec 2016, 3:45 PM + */ + +interface SearchReposMvp { + + interface View extends BaseMvp.FAView, SwipeRefreshLayout.OnRefreshListener, android.view.View.OnClickListener { + void onNotifyAdapter(); + + + void onSetSearchQuery(@NonNull String query); + + @NonNull OnLoadMore getLoadMore(); + } + + interface Presenter extends BaseMvp.FAPresenter, + BaseViewHolder.OnItemClickListener, + BaseMvp.PaginationListener { + + @NonNull ArrayList getRepos(); + + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/search/repos/SearchReposPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/search/repos/SearchReposPresenter.java new file mode 100644 index 00000000..14596c76 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/search/repos/SearchReposPresenter.java @@ -0,0 +1,77 @@ +package com.fastaccess.ui.modules.search.repos; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; + +import com.fastaccess.data.dao.NameParser; +import com.fastaccess.data.dao.RepoModel; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; +import com.fastaccess.ui.modules.repos.RepoPagerView; + +import java.util.ArrayList; + +/** + * Created by Kosh on 03 Dec 2016, 3:48 PM + */ + +class SearchReposPresenter extends BasePresenter implements SearchReposMvp.Presenter { + + private ArrayList repos = new ArrayList<>(); + private int page; + private int previousTotal; + private int lastPage = Integer.MAX_VALUE; + + @Override public int getCurrentPage() { + return page; + } + + @Override public int getPreviousTotal() { + return previousTotal; + } + + @Override public void setCurrentPage(int page) { + this.page = page; + } + + @Override public void setPreviousTotal(int previousTotal) { + this.previousTotal = previousTotal; + } + + @Override public void onCallApi(int page, @Nullable String parameter) { + if (page == 1) { + lastPage = Integer.MAX_VALUE; + sendToView(view -> view.getLoadMore().reset()); + } + setCurrentPage(page); + if (page > lastPage || lastPage == 0) { + sendToView(SearchReposMvp.View::hideProgress); + return; + } + if (parameter == null) { + return; + } + makeRestCall(RestProvider.getSearchService().searchRepositories(parameter, page), + response -> { + lastPage = response.getLast(); + if (getCurrentPage() == 1) { + getRepos().clear(); + } + getRepos().addAll(response.getItems()); + sendToView(SearchReposMvp.View::onNotifyAdapter); + }); + } + + @NonNull @Override public ArrayList getRepos() { + return repos; + } + + @Override public void onItemClick(int position, View v, RepoModel item) { + RepoPagerView.startRepoPager(v.getContext(), new NameParser(item.getHtmlUrl())); + } + + @Override public void onItemLongClick(int position, View v, RepoModel item) { + onItemClick(position, v, item); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/search/repos/SearchReposView.java b/app/src/main/java/com/fastaccess/ui/modules/search/repos/SearchReposView.java new file mode 100644 index 00000000..a32db34f --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/search/repos/SearchReposView.java @@ -0,0 +1,111 @@ +package com.fastaccess.ui.modules.search.repos; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.v4.widget.SwipeRefreshLayout; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.adapter.ReposAdapter; +import com.fastaccess.ui.base.BaseFragment; +import com.fastaccess.ui.widgets.StateLayout; +import com.fastaccess.ui.widgets.recyclerview.DynamicRecyclerView; + +import butterknife.BindView; +import icepick.State; + +/** + * Created by Kosh on 03 Dec 2016, 3:56 PM + */ + +public class SearchReposView extends BaseFragment implements SearchReposMvp.View { + + @State String searchQuery; + @BindView(R.id.recycler) DynamicRecyclerView recycler; + @BindView(R.id.refresh) SwipeRefreshLayout refresh; + @BindView(R.id.stateLayout) StateLayout stateLayout; + private OnLoadMore onLoadMore; + private ReposAdapter adapter; + + public static SearchReposView newInstance() { + return new SearchReposView(); + } + + @Override public void onNotifyAdapter() { + hideProgress(); + adapter.notifyDataSetChanged(); + } + + @Override protected int fragmentLayout() { + return R.layout.small_grid_refresh_list; + } + + @Override protected void onFragmentCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + if (savedInstanceState == null) { + stateLayout.hideProgress(); + } + getLoadMore().setCurrent_page(getPresenter().getCurrentPage(), getPresenter().getPreviousTotal()); + stateLayout.setOnReloadListener(this); + refresh.setOnRefreshListener(this); + recycler.setEmptyView(stateLayout, refresh); + adapter = new ReposAdapter(getPresenter().getRepos(), false, true); + adapter.setListener(getPresenter()); + recycler.setAdapter(adapter); + if (!InputHelper.isEmpty(searchQuery) && getPresenter().getRepos().isEmpty() && !getPresenter().isApiCalled()) { + onRefresh(); + } + } + + @NonNull @Override public SearchReposPresenter providePresenter() { + return new SearchReposPresenter(); + } + + @Override public void hideProgress() { + refresh.setRefreshing(false); + stateLayout.hideProgress(); + } + + @Override public void showProgress(@StringRes int resId) { + refresh.setRefreshing(true); + stateLayout.showProgress(); + } + + @Override public void showErrorMessage(@NonNull String message) { + hideProgress(); + stateLayout.showReload(adapter.getItemCount()); + super.showErrorMessage(message); + } + + @Override public void onSetSearchQuery(@NonNull String query) { + this.searchQuery = query; + getLoadMore().reset(); + getPresenter().getRepos().clear(); + onNotifyAdapter(); + recycler.scrollToPosition(0); + if (!InputHelper.isEmpty(query)) { + recycler.removeOnScrollListener(getLoadMore()); + recycler.addOnScrollListener(getLoadMore()); + onRefresh(); + } + } + + @NonNull @Override public OnLoadMore getLoadMore() { + if (onLoadMore == null) { + onLoadMore = new OnLoadMore<>(getPresenter(), searchQuery); + } + onLoadMore.setParameter(searchQuery); + return onLoadMore; + } + + @Override public void onRefresh() { + getPresenter().onCallApi(1, searchQuery); + } + + @Override public void onClick(View view) { + onRefresh(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/search/users/SearchUsersMvp.java b/app/src/main/java/com/fastaccess/ui/modules/search/users/SearchUsersMvp.java new file mode 100644 index 00000000..370a5dd2 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/search/users/SearchUsersMvp.java @@ -0,0 +1,34 @@ +package com.fastaccess.ui.modules.search.users; + +import android.support.annotation.NonNull; +import android.support.v4.widget.SwipeRefreshLayout; + +import com.fastaccess.data.dao.UserModel; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; + +import java.util.ArrayList; + +/** + * Created by Kosh on 03 Dec 2016, 3:45 PM + */ + +interface SearchUsersMvp { + + interface View extends BaseMvp.FAView, SwipeRefreshLayout.OnRefreshListener, android.view.View.OnClickListener { + void onNotifyAdapter(); + + void onSetSearchQuery(@NonNull String query); + + @NonNull OnLoadMore getLoadMore(); + } + + interface Presenter extends BaseMvp.FAPresenter, + BaseViewHolder.OnItemClickListener, + BaseMvp.PaginationListener { + + @NonNull ArrayList getUsers(); + + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/search/users/SearchUsersPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/search/users/SearchUsersPresenter.java new file mode 100644 index 00000000..c0abca59 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/search/users/SearchUsersPresenter.java @@ -0,0 +1,74 @@ +package com.fastaccess.ui.modules.search.users; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; + +import com.fastaccess.data.dao.UserModel; +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; + +import java.util.ArrayList; + +/** + * Created by Kosh on 03 Dec 2016, 3:48 PM + */ + +class SearchUsersPresenter extends BasePresenter implements SearchUsersMvp.Presenter { + + private ArrayList users = new ArrayList<>(); + private int page; + private int previousTotal; + private int lastPage = Integer.MAX_VALUE; + + @Override public int getCurrentPage() { + return page; + } + + @Override public int getPreviousTotal() { + return previousTotal; + } + + @Override public void setCurrentPage(int page) { + this.page = page; + } + + @Override public void setPreviousTotal(int previousTotal) { + this.previousTotal = previousTotal; + } + + @Override public void onCallApi(int page, @Nullable String parameter) { + if (page == 1) { + lastPage = Integer.MAX_VALUE; + sendToView(view -> view.getLoadMore().reset()); + } + setCurrentPage(page); + if (page > lastPage || lastPage == 0) { + sendToView(SearchUsersMvp.View::hideProgress); + return; + } + if (parameter == null) { + return; + } + makeRestCall(RestProvider.getSearchService().searchUsers(parameter, page), + response -> { + if (response.isIncompleteResults()) return; + lastPage = response.getLast(); + if (getCurrentPage() == 1) { + getUsers().clear(); + } + getUsers().addAll(response.getItems()); + sendToView(SearchUsersMvp.View::onNotifyAdapter); + }); + } + + @NonNull @Override public ArrayList getUsers() { + return users; + } + + @Override public void onItemClick(int position, View v, UserModel item) { + + } + + @Override public void onItemLongClick(int position, View v, UserModel item) {} +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/search/users/SearchUsersView.java b/app/src/main/java/com/fastaccess/ui/modules/search/users/SearchUsersView.java new file mode 100644 index 00000000..bd875890 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/search/users/SearchUsersView.java @@ -0,0 +1,110 @@ +package com.fastaccess.ui.modules.search.users; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.v4.widget.SwipeRefreshLayout; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.provider.rest.loadmore.OnLoadMore; +import com.fastaccess.ui.adapter.UsersAdapter; +import com.fastaccess.ui.base.BaseFragment; +import com.fastaccess.ui.widgets.StateLayout; +import com.fastaccess.ui.widgets.recyclerview.DynamicRecyclerView; + +import butterknife.BindView; +import icepick.State; + +/** + * Created by Kosh on 03 Dec 2016, 3:56 PM + */ + +public class SearchUsersView extends BaseFragment implements SearchUsersMvp.View { + @State String searchQuery; + @BindView(R.id.recycler) DynamicRecyclerView recycler; + @BindView(R.id.refresh) SwipeRefreshLayout refresh; + @BindView(R.id.stateLayout) StateLayout stateLayout; + private OnLoadMore onLoadMore; + private UsersAdapter adapter; + + public static SearchUsersView newInstance() { + return new SearchUsersView(); + } + + @Override public void onNotifyAdapter() { + hideProgress(); + adapter.notifyDataSetChanged(); + } + + @Override protected int fragmentLayout() { + return R.layout.small_grid_refresh_list; + } + + @Override protected void onFragmentCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + if (savedInstanceState == null) { + stateLayout.hideProgress(); + } + getLoadMore().setCurrent_page(getPresenter().getCurrentPage(), getPresenter().getPreviousTotal()); + stateLayout.setOnReloadListener(this); + refresh.setOnRefreshListener(this); + recycler.setEmptyView(stateLayout, refresh); + adapter = new UsersAdapter(getPresenter().getUsers()); + adapter.setListener(getPresenter()); + recycler.setAdapter(adapter); + if (!InputHelper.isEmpty(searchQuery) && getPresenter().getUsers().isEmpty() && !getPresenter().isApiCalled()) { + onRefresh(); + } + } + + @NonNull @Override public SearchUsersPresenter providePresenter() { + return new SearchUsersPresenter(); + } + + @Override public void hideProgress() { + refresh.setRefreshing(false); + stateLayout.hideProgress(); + } + + @Override public void showProgress(@StringRes int resId) { + refresh.setRefreshing(true); + stateLayout.showProgress(); + } + + @Override public void showErrorMessage(@NonNull String message) { + hideProgress(); + stateLayout.showReload(adapter.getItemCount()); + super.showErrorMessage(message); + } + + @Override public void onSetSearchQuery(@NonNull String query) { + this.searchQuery = query; + getLoadMore().reset(); + getPresenter().getUsers().clear(); + onNotifyAdapter(); + recycler.scrollToPosition(0); + if (!InputHelper.isEmpty(query)) { + recycler.removeOnScrollListener(getLoadMore()); + recycler.addOnScrollListener(getLoadMore()); + onRefresh(); + } + } + + @NonNull @Override public OnLoadMore getLoadMore() { + if (onLoadMore == null) { + onLoadMore = new OnLoadMore<>(getPresenter(), searchQuery); + } + onLoadMore.setParameter(searchQuery); + return onLoadMore; + } + + @Override public void onRefresh() { + getPresenter().onCallApi(1, searchQuery); + } + + @Override public void onClick(View view) { + onRefresh(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/user/UserPagerMvp.java b/app/src/main/java/com/fastaccess/ui/modules/user/UserPagerMvp.java new file mode 100644 index 00000000..88e72b05 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/user/UserPagerMvp.java @@ -0,0 +1,27 @@ +package com.fastaccess.ui.modules.user; + +import android.support.annotation.NonNull; + +import com.fastaccess.ui.base.mvp.BaseMvp; + +/** + * Created by Kosh on 04 Dec 2016, 1:11 PM + */ + +interface UserPagerMvp { + + interface View extends BaseMvp.FAView { + void onInvalidateMenuItem(); + } + + interface Presenter extends BaseMvp.FAPresenter { + void onCheckFollowStatus(@NonNull String login); + + boolean isSuccessResponse(); + + boolean isFollowing(); + + void onFollowMenuItemClicked(@NonNull String login); + } + +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/user/UserPagerPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/user/UserPagerPresenter.java new file mode 100644 index 00000000..d2a19382 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/user/UserPagerPresenter.java @@ -0,0 +1,49 @@ +package com.fastaccess.ui.modules.user; + +import android.support.annotation.NonNull; + +import com.fastaccess.provider.rest.RestProvider; +import com.fastaccess.ui.base.mvp.presenter.BasePresenter; + +import rx.Observable; + +/** + * Created by Kosh on 03 Dec 2016, 8:00 AM + */ + +class UserPagerPresenter extends BasePresenter implements UserPagerMvp.Presenter { + private boolean isSuccessResponse; + private boolean isFollowing; + + @Override public void onCheckFollowStatus(@NonNull String login) { + makeRestCall(RestProvider.getUserService().getFollowStatus(login), + booleanResponse -> { + isSuccessResponse = true; + isFollowing = booleanResponse.code() == 204; + sendToView(UserPagerMvp.View::onInvalidateMenuItem); + }); + } + + @Override public boolean isSuccessResponse() { + return isSuccessResponse; + } + + @Override public boolean isFollowing() { + return isFollowing; + } + + @Override public void onFollowMenuItemClicked(@NonNull String login) { + makeRestCall(!isFollowing ? RestProvider.getUserService().followUser(login) : RestProvider.getUserService().unfollowUser(login), + booleanResponse -> { + if (booleanResponse.code() == 204) { + isFollowing = !isFollowing; + sendToView(UserPagerMvp.View::onInvalidateMenuItem); + } + }); + } + + @Override public T onError(@NonNull Throwable throwable, @NonNull Observable observable) { + sendToView(UserPagerMvp.View::onInvalidateMenuItem); + return super.onError(throwable, observable); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/user/UserPagerView.java b/app/src/main/java/com/fastaccess/ui/modules/user/UserPagerView.java new file mode 100644 index 00000000..96ec015c --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/user/UserPagerView.java @@ -0,0 +1,128 @@ +package com.fastaccess.ui.modules.user; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.StringRes; +import android.support.design.widget.TabLayout; +import android.support.v4.view.MenuItemCompat; +import android.view.Menu; +import android.view.MenuItem; +import android.widget.ProgressBar; + +import com.fastaccess.R; +import com.fastaccess.data.dao.FragmentPagerAdapterModel; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.ui.adapter.FragmentsPagerAdapter; +import com.fastaccess.ui.base.BaseActivity; +import com.fastaccess.ui.widgets.ViewPagerView; + +import butterknife.BindView; +import icepick.State; + +/** + * Created by Kosh on 03 Dec 2016, 8:00 AM + */ + +public class UserPagerView extends BaseActivity implements UserPagerMvp.View { + + + @BindView(R.id.tabs) TabLayout tabs; + @BindView(R.id.pager) ViewPagerView pager; + @State String login; + + public static void startActivity(@NonNull Context context, @NonNull String login) { + context.startActivity(createIntent(context, login)); + } + + public static Intent createIntent(@NonNull Context context, @NonNull String login) { + Intent intent = new Intent(context, UserPagerView.class); + intent.putExtras(Bundler.start().put(BundleConstant.EXTRA, login).end()); + return intent; + } + + @Override protected int layout() { + return R.layout.tabbed_pager_layout; + } + + @Override protected boolean isTransparent() { + return false; + } + + @Override protected boolean canBack() { + return true; + } + + @Override protected boolean isSecured() { + return false; + } + + @NonNull @Override public UserPagerPresenter providePresenter() { + return new UserPagerPresenter(); + } + + @Override protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (savedInstanceState == null) { + login = getIntent().getExtras().getString(BundleConstant.EXTRA); + } + if (InputHelper.isEmpty(login)) { + finish(); + return; + } + setTitle(login); + onInvalidateMenuItem(); + FragmentsPagerAdapter adapter = new FragmentsPagerAdapter(getSupportFragmentManager(), + FragmentPagerAdapterModel.buildForProfile(this, login)); + tabs.setTabGravity(TabLayout.GRAVITY_FILL); + tabs.setTabMode(TabLayout.MODE_SCROLLABLE); + pager.setAdapter(adapter); + tabs.setupWithViewPager(pager); + if (savedInstanceState == null) { + getPresenter().onCheckFollowStatus(login); + } + } + + @Override public void showProgress(@StringRes int resId) { + + } + + @Override public void hideProgress() { + super.hideProgress(); + } + + @Override public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.follow_menu, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.follow) { + if (item.getActionView() == null) { + MenuItemCompat.setActionView(item, new ProgressBar(this)); + getPresenter().onFollowMenuItemClicked(login); + } + } + return super.onOptionsItemSelected(item); + } + + @Override public boolean onPrepareOptionsMenu(Menu menu) { + if (getPresenter().isSuccessResponse()) { + MenuItemCompat.setActionView(menu.findItem(R.id.follow), null); + menu.findItem(R.id.follow) + .setVisible(true) + .setTitle(getPresenter().isFollowing() ? getString(R.string.unfollow) : getString(R.string.follow)); + + } else { + MenuItemCompat.setActionView(menu.findItem(R.id.follow), new ProgressBar(this)); + } + return super.onPrepareOptionsMenu(menu); + } + + @Override public void onInvalidateMenuItem() { + supportInvalidateOptionsMenu(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/widgets/AppbarRefreshLayout.java b/app/src/main/java/com/fastaccess/ui/widgets/AppbarRefreshLayout.java new file mode 100644 index 00000000..dda6ef7c --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/widgets/AppbarRefreshLayout.java @@ -0,0 +1,23 @@ +package com.fastaccess.ui.widgets; + +import android.content.Context; +import android.support.v4.widget.SwipeRefreshLayout; +import android.util.AttributeSet; + +import com.fastaccess.R; + + +/** + * Created by kosh on 7/30/2015. CopyRights @ Innov8tif + */ +public class AppbarRefreshLayout extends SwipeRefreshLayout { + + public AppbarRefreshLayout(Context context) { + super(context, null); + } + + public AppbarRefreshLayout(Context context, AttributeSet attrs) { + super(context, attrs); + setColorSchemeResources(R.color.material_amber_700, R.color.material_blue_700, R.color.material_purple_700, R.color.material_lime_700); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/widgets/AutoLinearLayout.java b/app/src/main/java/com/fastaccess/ui/widgets/AutoLinearLayout.java new file mode 100644 index 00000000..59f86710 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/widgets/AutoLinearLayout.java @@ -0,0 +1,495 @@ +package com.fastaccess.ui.widgets; + +import android.content.Context; +import android.content.res.TypedArray; +import android.support.v4.view.GravityCompat; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.View; +import android.widget.FrameLayout; + +import com.fastaccess.R; + +import java.util.ArrayList; + +public class AutoLinearLayout extends FrameLayout { + + private int mOrientation; + private int mGravity = Gravity.TOP | GravityCompat.START; + + public final static int HORIZONTAL = 0; + public final static int VERTICAL = 1; + + private ArrayList mListPositions = new ArrayList<>(); + + public AutoLinearLayout(Context context) { + super(context); + init(context, null, 0, 0); + } + + public AutoLinearLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs, 0, 0); + } + + public AutoLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs, defStyleAttr, 0); + } + + public AutoLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(context, attrs, defStyleAttr, defStyleRes); + } + + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (mOrientation == VERTICAL) { + measureVertical(widthMeasureSpec, heightMeasureSpec); + } else { + measureHorizontal(widthMeasureSpec, heightMeasureSpec); + } + } + + @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + mListPositions.clear(); + if (mOrientation == VERTICAL) + layoutVertical(left, top, right, bottom); + else + layoutHorizontal(left, top, right, bottom); + } + + private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AutoLinearLayout, defStyleAttr, defStyleRes); + try { + mOrientation = a.getInt(R.styleable.AutoLinearLayout_auto_orientation, HORIZONTAL); + int gravity = a.getInt(R.styleable.AutoLinearLayout_auto_gravity, -1); + if (gravity >= 0) { + setGravity(gravity); + } + } finally { + a.recycle(); + } + } + + private void measureHorizontal(int widthMeasureSpec, int heightMeasureSpec) { + int wSize = MeasureSpec.getSize(widthMeasureSpec) - (getPaddingLeft() + getPaddingRight()); + + //Scrollview case + if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.UNSPECIFIED) + wSize = Integer.MAX_VALUE; + + int count = getChildCount(); + int rowWidth = 0; + int totalHeight = 0; + int rowMaxHeight = 0; + int childWidth; + int childHeight; + int maxRowWidth = getPaddingLeft() + getPaddingRight(); + + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child.getVisibility() != GONE) { + measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin; + childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin; + //keep max height value stored + rowMaxHeight = Math.max(rowMaxHeight, childHeight); + + //exceed max width start new row and update total height + if (childWidth + rowWidth > wSize) { + totalHeight += rowMaxHeight; + maxRowWidth = Math.max(maxRowWidth, rowWidth); + rowWidth = childWidth; + rowMaxHeight = childHeight; + } else { + rowWidth += childWidth; + } + } + } + //plus last child height and width + if (rowWidth != 0) { + maxRowWidth = Math.max(maxRowWidth, rowWidth); + totalHeight += rowMaxHeight; + } + + //set width to max value + if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.UNSPECIFIED) + wSize = maxRowWidth + (getPaddingLeft() + getPaddingRight()); + + setMeasuredDimension(resolveSize(wSize, widthMeasureSpec), + resolveSize(totalHeight + getPaddingTop() + getPaddingBottom(), heightMeasureSpec)); + } + + private void measureVertical(int widthMeasureSpec, int heightMeasureSpec) { + int hSize = MeasureSpec.getSize(heightMeasureSpec) - (getPaddingTop() + getPaddingBottom()); + + int count = getChildCount(); + int columnHeight = 0; + int totalWidth = 0, maxColumnHeight = 0; + int columnMaxWidth = 0; + int childWidth; + int childHeight; + + //Scrollview case + if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.UNSPECIFIED) + hSize = Integer.MAX_VALUE; + + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child.getVisibility() != GONE) { + measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin; + childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin; + //keep max width value stored + columnMaxWidth = Math.max(columnMaxWidth, childWidth); + + //exceed max height start new column and update total width + if (childHeight + columnHeight > hSize) { + totalWidth += columnMaxWidth; + maxColumnHeight = Math.max(maxColumnHeight, columnHeight); + columnHeight = childHeight; + columnMaxWidth = childWidth; + } else { + columnHeight += childHeight; + } + } + } + //plus last child width + if (columnHeight != 0) { + maxColumnHeight = Math.max(maxColumnHeight, columnHeight); + totalWidth += columnMaxWidth; + } + + //set height to max value + if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.UNSPECIFIED) + hSize = maxColumnHeight + (getPaddingTop() + getPaddingBottom()); + + setMeasuredDimension(resolveSize(totalWidth + getPaddingRight() + getPaddingLeft(), + widthMeasureSpec), resolveSize(hSize, heightMeasureSpec)); + } + + /** + * Arranges the children in columns. Takes care about child margin, padding, gravity and + * child layout gravity. + * + * @param left + * parent left + * @param top + * parent top + * @param right + * parent right + * @param bottom + * parent bottom + */ + void layoutVertical(int left, int top, int right, int bottom) { + final int count = getChildCount(); + if (count == 0) + return; + + final int width = right - getPaddingLeft() - left - getPaddingRight(); + final int height = bottom - getPaddingTop() - top - getPaddingBottom(); + + int childTop = getPaddingTop(); + int childLeft = getPaddingLeft(); + + int totalHorizontal = getPaddingLeft() + getPaddingRight(); + int totalVertical = 0; + int column = 0; + int maxChildWidth = 0; + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child != null && child.getVisibility() != View.GONE) { + //if child is not updated yet call measure + if (child.getMeasuredHeight() == 0 || child.getMeasuredWidth() == 0) + child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), + MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST)); + + LayoutParams lp = (LayoutParams) child.getLayoutParams(); + final int childWidth = child.getMeasuredWidth(); + final int childHeight = child.getMeasuredHeight(); + //if there is not enough space jump to another column + if (childTop + childHeight + lp.topMargin + lp.bottomMargin > height + getPaddingTop()) { + //before change column update positions if the gravity is present + updateChildPositionVertical(height, totalVertical, column, maxChildWidth); + childTop = getPaddingTop(); + childLeft += maxChildWidth; + maxChildWidth = 0; + column++; + totalVertical = 0; + } + + childTop += lp.topMargin; + mListPositions.add(new ViewPosition(childLeft, childTop, column)); + //check max child width + int currentWidth = childWidth + lp.leftMargin + lp.rightMargin; + if (maxChildWidth < currentWidth) + maxChildWidth = currentWidth; + //get ready for next child + childTop += childHeight + lp.bottomMargin; + totalVertical += childHeight + lp.topMargin + lp.bottomMargin; + } + } + + //update positions for last column + updateChildPositionVertical(height, totalVertical, column, maxChildWidth); + totalHorizontal += childLeft + maxChildWidth; + //final update for horizontal gravities and layout views + updateChildPositionHorizontal(width, totalHorizontal, column, 0); + //mListPositions.clear(); + } + + /** + * Arranges the children in rows. Takes care about child margin, padding, gravity and + * child layout gravity. Analog to vertical. + * + * @param left + * parent left + * @param top + * parent top + * @param right + * parent right + * @param bottom + * parent bottom + */ + void layoutHorizontal(int left, int top, int right, int bottom) { + final int count = getChildCount(); + if (count == 0) + return; + + final int width = right - getPaddingLeft() - left - getPaddingRight(); + final int height = bottom - getPaddingTop() - top - getPaddingBottom(); + + int childTop = getPaddingTop(); + int childLeft = getPaddingLeft(); + + int totalHorizontal = 0; + int totalVertical = getPaddingTop() + getPaddingBottom(); + int row = 0; + int maxChildHeight = 0; + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + + if (child != null && child.getVisibility() != View.GONE) { + if (child.getMeasuredHeight() == 0 || child.getMeasuredWidth() == 0) + child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), + MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST)); + + LayoutParams lp = (LayoutParams) child.getLayoutParams(); + final int childWidth = child.getMeasuredWidth(); + final int childHeight = child.getMeasuredHeight(); + + if (childLeft + childWidth + lp.leftMargin + lp.rightMargin > width + getPaddingLeft()) { + updateChildPositionHorizontal(width, totalHorizontal, row, maxChildHeight); + childLeft = getPaddingLeft(); + childTop += maxChildHeight; + maxChildHeight = 0; + row++; + totalHorizontal = 0; + } + + childLeft += lp.leftMargin; + mListPositions.add(new ViewPosition(childLeft, childTop, row)); + + int currentHeight = childHeight + lp.topMargin + lp.bottomMargin; + if (maxChildHeight < currentHeight) + maxChildHeight = currentHeight; + + childLeft += childWidth + lp.rightMargin; + totalHorizontal += childWidth + lp.rightMargin + lp.leftMargin; + } + } + + updateChildPositionHorizontal(width, totalHorizontal, row, maxChildHeight); + totalVertical += childTop + maxChildHeight; + updateChildPositionVertical(height, totalVertical, row, 0); + //mListPositions.clear(); + } + + /** + * Updates children positions. Takes cares about gravity and layout gravity. + * Finally layout children to parent if needed. + * + * @param parentHeight + * parent parentHeight + * @param totalSize + * total vertical size used by children in a column + * @param column + * column number + * @param maxChildWidth + * the biggest child width + */ + private void updateChildPositionVertical(int parentHeight, int totalSize, int column, int maxChildWidth) { + for (int i = 0; i < mListPositions.size(); i++) { + ViewPosition pos = mListPositions.get(i); + final View child = getChildAt(i); + //(android:gravity) + //update children position inside parent layout + if (mOrientation == HORIZONTAL || pos.position == column) { + updateTopPositionByGravity(pos, parentHeight - totalSize, mGravity); + } + //(android:layout_gravity) + //update children position inside their space + if (mOrientation == VERTICAL && pos.position == column) { + LayoutParams lp = (LayoutParams) child.getLayoutParams(); + int size = maxChildWidth - child.getMeasuredWidth() - lp.leftMargin - lp.rightMargin; + updateLeftPositionByGravity(pos, size, lp.gravity); + } + //update children into layout parent + if (mOrientation == HORIZONTAL) + layout(child, pos); + } + } + + /** + * Updates children positions. Takes cares about gravity and layout gravity. + * Finally layout children to parent if needed. Analog to vertical. + * + * @param parentWidth + * parent parentWidth + * @param totalSize + * total horizontal size used by children in a row + * @param row + * row number + * @param maxChildHeight + * the biggest child height + */ + private void updateChildPositionHorizontal(int parentWidth, int totalSize, int row, int maxChildHeight) { + for (int i = 0; i < mListPositions.size(); i++) { + ViewPosition pos = mListPositions.get(i); + final View child = getChildAt(i); + + if (mOrientation == VERTICAL || pos.position == row) { + updateLeftPositionByGravity(pos, parentWidth - totalSize, mGravity); + } + + if (mOrientation == HORIZONTAL && pos.position == row) { + LayoutParams lp = (LayoutParams) child.getLayoutParams(); + int size = maxChildHeight - child.getMeasuredHeight() - lp.topMargin - lp.bottomMargin; + updateTopPositionByGravity(pos, size, lp.gravity); + } + + if (mOrientation == VERTICAL) + layout(child, pos); + } + } + + private void updateLeftPositionByGravity(ViewPosition pos, int size, int gravity) { + switch (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) { + case GravityCompat.END: + pos.left += (size > 0) ? size : 0; + break; + + case Gravity.CENTER_HORIZONTAL: + pos.left += ((size > 0) ? size : 0) / 2; + break; + } + } + + private void updateTopPositionByGravity(ViewPosition pos, int size, int gravity) { + switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) { + case Gravity.BOTTOM: + pos.top += (size > 0) ? size : 0; + break; + + case Gravity.CENTER_VERTICAL: + pos.top += ((size > 0) ? size : 0) / 2; + break; + } + } + + private void layout(View child, ViewPosition pos) { + LayoutParams lp = (LayoutParams) child.getLayoutParams(); + + if (mOrientation == HORIZONTAL) + child.layout(pos.left, pos.top + lp.topMargin, pos.left + child.getMeasuredWidth(), pos.top + + child.getMeasuredHeight() + lp.topMargin); + else + child.layout(pos.left + lp.leftMargin, pos.top, pos.left + child.getMeasuredWidth() + + lp.leftMargin, pos.top + child.getMeasuredHeight()); + } + + /** + * Describes how the child views are positioned. Defaults to GRAVITY_TOP. If + * this layout has a VERTICAL orientation, this controls where all the child + * views are placed if there is extra vertical space. If this layout has a + * HORIZONTAL orientation, this controls the alignment of the children. + * + * @param gravity + * See {@link Gravity} + */ + public void setGravity(int gravity) { + if (mGravity != gravity) { + if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) { + gravity |= GravityCompat.START; + } + + if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) { + gravity |= Gravity.TOP; + } + + mGravity = gravity; + requestLayout(); + } + } + + public void setHorizontalGravity(int horizontalGravity) { + final int gravity = horizontalGravity & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK; + if ((mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) != gravity) { + mGravity = (mGravity & ~GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK) | gravity; + requestLayout(); + } + } + + public void setVerticalGravity(int verticalGravity) { + final int gravity = verticalGravity & Gravity.VERTICAL_GRAVITY_MASK; + if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != gravity) { + mGravity = (mGravity & ~Gravity.VERTICAL_GRAVITY_MASK) | gravity; + requestLayout(); + } + } + + /** + * Should the layout be a column or a row. + * + * @param orientation + * Pass HORIZONTAL or VERTICAL. Default value is HORIZONTAL. + */ + public void setOrientation(int orientation) { + if (mOrientation != orientation) { + mOrientation = orientation; + requestLayout(); + } + } + + /** + * Returns the current orientation. + * + * @return either {@link #HORIZONTAL} or {@link #VERTICAL} + */ + public int getOrientation() { + return mOrientation; + } + + /** + * Helper inner class that stores child position + */ + private static class ViewPosition { + int left; + int top; + int position; //row or column + + ViewPosition(int l, int t, int p) { + this.left = l; + this.top = t; + this.position = p; + } + + @Override + public String toString() { + return "left-" + left + " top" + top + " pos" + position; + } + } + +} diff --git a/app/src/main/java/com/fastaccess/ui/widgets/AvatarLayout.java b/app/src/main/java/com/fastaccess/ui/widgets/AvatarLayout.java new file mode 100644 index 00000000..938bcb1c --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/widgets/AvatarLayout.java @@ -0,0 +1,106 @@ +package com.fastaccess.ui.widgets; + +import android.content.Context; +import android.graphics.Bitmap; +import android.support.annotation.AttrRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StyleRes; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ProgressBar; +import android.widget.Toast; + +import com.fastaccess.R; +import com.fastaccess.helper.InputHelper; +import com.fastaccess.ui.modules.user.UserPagerView; +import com.nostra13.universalimageloader.core.ImageLoader; +import com.nostra13.universalimageloader.core.assist.FailReason; +import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import butterknife.OnLongClick; +import de.hdodenhof.circleimageview.CircleImageView; + +/** + * Created by Kosh on 14 Nov 2016, 7:59 PM + */ + +public class AvatarLayout extends FrameLayout implements ImageLoadingListener { + + @BindView(R.id.avatar) CircleImageView avatar; + @BindView(R.id.avatarProgress) ProgressBar avatarProgress; + private String login; + private Toast toast; + + @OnClick(R.id.avatar) void onClick(View view) { + if (InputHelper.isEmpty(login)) return; + UserPagerView.startActivity(view.getContext(), login); + } + + @OnLongClick(R.id.avatar) boolean onLongClick(View view) { + if (InputHelper.isEmpty(login)) return false; + if (toast != null) toast.cancel(); + toast = Toast.makeText(getContext(), view.getContentDescription(), Toast.LENGTH_SHORT); + toast.setGravity(Gravity.CENTER, 0, 0); + toast.show(); + return true; + } + + public AvatarLayout(@NonNull Context context) { + super(context); + } + + public AvatarLayout(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public AvatarLayout(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public AvatarLayout(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override protected void onAttachedToWindow() { + super.onAttachedToWindow(); + } + + @Override protected void onFinishInflate() { + super.onFinishInflate(); + inflate(getContext(), R.layout.avatar_layout, this); + if (isInEditMode()) return; + ButterKnife.bind(this); + } + + @Override public void onLoadingStarted(String imageUri, View view) { + avatarProgress.setVisibility(VISIBLE); + } + + @Override public void onLoadingFailed(String imageUri, View view, FailReason failReason) { + avatarProgress.setVisibility(GONE); + avatar.setImageResource(R.drawable.ic_github_black); + } + + @Override public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { + avatarProgress.setVisibility(GONE); + } + + @Override public void onLoadingCancelled(String imageUri, View view) {} + + public void setUrl(@Nullable String url, @Nullable String login) { + this.login = login; + avatar.setContentDescription(login); + if (url != null) { + ImageLoader.getInstance().displayImage(url, avatar, this); + } else { + ImageLoader.getInstance().displayImage(null, avatar); + avatarProgress.setVisibility(GONE); + } + } +} diff --git a/app/src/main/java/com/fastaccess/ui/widgets/DiffLineSpan.java b/app/src/main/java/com/fastaccess/ui/widgets/DiffLineSpan.java new file mode 100644 index 00000000..b7121cbc --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/widgets/DiffLineSpan.java @@ -0,0 +1,32 @@ +package com.fastaccess.ui.widgets; + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.text.style.LineBackgroundSpan; + +public class DiffLineSpan implements LineBackgroundSpan { + private static Rect mTmpRect = new Rect(); + private final int color; + private final int padding; + + public DiffLineSpan(int color, int padding) { + this.color = color; + this.padding = padding; + } + + @Override + public void drawBackground(Canvas c, Paint p, int left, int right, int top, int baseline, int bottom, CharSequence text, int start, + int end, int lnum) { + // expand canvas bounds by padding + Rect clipBounds = c.getClipBounds(); + clipBounds.inset(-padding, 0); + //c.clipRect(clipBounds, Region.Op.REPLACE); + + final int paintColor = p.getColor(); + p.setColor(color); + mTmpRect.set(left - padding, top, right + padding, bottom); + c.drawRect(mTmpRect, p); + p.setColor(paintColor); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/widgets/FontAutoCompleteEditText.java b/app/src/main/java/com/fastaccess/ui/widgets/FontAutoCompleteEditText.java new file mode 100644 index 00000000..6aa00c89 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/widgets/FontAutoCompleteEditText.java @@ -0,0 +1,47 @@ +package com.fastaccess.ui.widgets; + +import android.content.Context; +import android.support.annotation.ColorRes; +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.AppCompatAutoCompleteTextView; +import android.util.AttributeSet; +import android.view.inputmethod.EditorInfo; + +import com.fastaccess.helper.TypeFaceHelper; +import com.fastaccess.helper.ViewHelper; + +/** + * Created by Kosh on 8/18/2015. copyrights are reserved + */ +public class FontAutoCompleteEditText extends AppCompatAutoCompleteTextView { + + public FontAutoCompleteEditText(Context context) { + super(context); + init(); + } + + public FontAutoCompleteEditText(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + + } + + public FontAutoCompleteEditText(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + private void init() { + if (isInEditMode()) return; + if (isInEditMode()) return; + setInputType(getInputType() | EditorInfo.IME_FLAG_NO_EXTRACT_UI | EditorInfo.IME_FLAG_NO_FULLSCREEN); + setImeOptions(getImeOptions() | EditorInfo.IME_FLAG_NO_FULLSCREEN); + TypeFaceHelper.applyTypeface(this); + } + + public void setTextColor(@ColorRes int normalColor, @ColorRes int pressedColor) { + int nColor = ContextCompat.getColor(getContext(), normalColor); + int pColor = ContextCompat.getColor(getContext(), pressedColor); + setTextColor(ViewHelper.textSelector(nColor, pColor)); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/widgets/FontButton.java b/app/src/main/java/com/fastaccess/ui/widgets/FontButton.java new file mode 100644 index 00000000..da926d6d --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/widgets/FontButton.java @@ -0,0 +1,50 @@ +package com.fastaccess.ui.widgets; + +import android.content.Context; +import android.support.annotation.ColorRes; +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.AppCompatButton; +import android.util.AttributeSet; + +import com.fastaccess.helper.TypeFaceHelper; +import com.fastaccess.helper.ViewHelper; + + +/** + * Created by Kosh on 8/18/2015. copyrights are reserved + */ +public class FontButton extends AppCompatButton { + + public FontButton(Context context) { + super(context); + init(); + } + + public FontButton(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public FontButton(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + private void init() { + if (isInEditMode()) return; + TypeFaceHelper.applyTypeface(this); + } + + public void setBackground(@ColorRes int normalColor, @ColorRes int pressedColor) { + int nColor = ContextCompat.getColor(getContext(), normalColor); + int pColor = ContextCompat.getColor(getContext(), pressedColor); + setBackgroundDrawable(ViewHelper.getDrawableSelector(nColor, pColor)); + } + + public void setTextColor(@ColorRes int normalColor, @ColorRes int pressedColor) { + int nColor = ContextCompat.getColor(getContext(), normalColor); + int pColor = ContextCompat.getColor(getContext(), pressedColor); + setTextColor(ViewHelper.textSelector(nColor, pColor)); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/ui/widgets/FontCheckbox.java b/app/src/main/java/com/fastaccess/ui/widgets/FontCheckbox.java new file mode 100644 index 00000000..abe05601 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/widgets/FontCheckbox.java @@ -0,0 +1,43 @@ +package com.fastaccess.ui.widgets; + +import android.content.Context; +import android.support.annotation.ColorRes; +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.AppCompatCheckBox; +import android.util.AttributeSet; + +import com.fastaccess.helper.TypeFaceHelper; +import com.fastaccess.helper.ViewHelper; + + +/** + * Created by Kosh on 8/18/2015. copyrights are reserved + */ +public class FontCheckbox extends AppCompatCheckBox { + + public FontCheckbox(Context context) { + super(context); + init(); + } + + public FontCheckbox(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public FontCheckbox(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + private void init() { + if (isInEditMode()) return; + TypeFaceHelper.applyTypeface(this); + } + + public void setTextColor(@ColorRes int normalColor, @ColorRes int pressedColor) { + int nColor = ContextCompat.getColor(getContext(), normalColor); + int pColor = ContextCompat.getColor(getContext(), pressedColor); + setTextColor(ViewHelper.textSelector(nColor, pColor)); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/widgets/FontEditText.java b/app/src/main/java/com/fastaccess/ui/widgets/FontEditText.java new file mode 100644 index 00000000..c53a0ad6 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/widgets/FontEditText.java @@ -0,0 +1,46 @@ +package com.fastaccess.ui.widgets; + +import android.content.Context; +import android.support.annotation.ColorRes; +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.AppCompatEditText; +import android.util.AttributeSet; +import android.view.inputmethod.EditorInfo; + +import com.fastaccess.helper.TypeFaceHelper; +import com.fastaccess.helper.ViewHelper; + +/** + * Created by Kosh on 8/18/2015. copyrights are reserved + */ +public class FontEditText extends AppCompatEditText { + + public FontEditText(Context context) { + super(context); + init(); + } + + public FontEditText(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + + } + + public FontEditText(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + private void init() { + if (isInEditMode()) return; + setInputType(getInputType() | EditorInfo.IME_FLAG_NO_EXTRACT_UI | EditorInfo.IME_FLAG_NO_FULLSCREEN); + setImeOptions(getImeOptions() | EditorInfo.IME_FLAG_NO_FULLSCREEN); + TypeFaceHelper.applyTypeface(this); + } + + public void setTextColor(@ColorRes int normalColor, @ColorRes int pressedColor) { + int nColor = ContextCompat.getColor(getContext(), normalColor); + int pColor = ContextCompat.getColor(getContext(), pressedColor); + setTextColor(ViewHelper.textSelector(nColor, pColor)); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/widgets/FontRadioButton.java b/app/src/main/java/com/fastaccess/ui/widgets/FontRadioButton.java new file mode 100644 index 00000000..22952277 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/widgets/FontRadioButton.java @@ -0,0 +1,43 @@ +package com.fastaccess.ui.widgets; + +import android.content.Context; +import android.support.annotation.ColorRes; +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.AppCompatRadioButton; +import android.util.AttributeSet; + +import com.fastaccess.helper.TypeFaceHelper; +import com.fastaccess.helper.ViewHelper; + + +/** + * Created by Kosh on 8/18/2015. copyrights are reserved + */ +public class FontRadioButton extends AppCompatRadioButton { + + public FontRadioButton(Context context) { + super(context); + init(); + } + + public FontRadioButton(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public FontRadioButton(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + private void init() { + if (isInEditMode()) return; + TypeFaceHelper.applyTypeface(this); + } + + public void setTextColor(@ColorRes int normalColor, @ColorRes int pressedColor) { + int nColor = ContextCompat.getColor(getContext(), normalColor); + int pColor = ContextCompat.getColor(getContext(), pressedColor); + setTextColor(ViewHelper.textSelector(nColor, pColor)); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/widgets/FontTextView.java b/app/src/main/java/com/fastaccess/ui/widgets/FontTextView.java new file mode 100644 index 00000000..d6545cea --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/widgets/FontTextView.java @@ -0,0 +1,81 @@ +package com.fastaccess.ui.widgets; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.os.Parcelable; +import android.support.annotation.ColorInt; +import android.support.annotation.ColorRes; +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.AppCompatTextView; +import android.util.AttributeSet; + +import com.fastaccess.R; +import com.fastaccess.helper.TypeFaceHelper; +import com.fastaccess.helper.ViewHelper; + +import icepick.Icepick; +import icepick.State; + + +/** + * Created by Kosh on 8/18/2015. copyrights are reserved + */ +public class FontTextView extends AppCompatTextView { + + @State int tintColor = -1; + + public FontTextView(Context context) { + this(context, null); + } + + public FontTextView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public FontTextView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs); + } + + @Override public Parcelable onSaveInstanceState() { + return Icepick.saveInstanceState(this, super.onSaveInstanceState()); + } + + @Override public void onRestoreInstanceState(Parcelable state) { + super.onRestoreInstanceState(Icepick.restoreInstanceState(this, state)); + tintDrawables(tintColor); + } + + private void init(Context context, AttributeSet attributeSet) { + if (attributeSet != null) { + TypedArray tp = context.obtainStyledAttributes(attributeSet, R.styleable.FontTextView); + try { + int color = tp.getColor(R.styleable.FontTextView_drawableColor, -1); + tintDrawables(color); + } finally { + tp.recycle(); + } + } + if (isInEditMode()) return; + TypeFaceHelper.applyTypeface(this); + } + + public void tintDrawables(@ColorInt int color) { + if (color != -1) { + this.tintColor = color; + Drawable[] drawables = getCompoundDrawablesRelative(); + for (Drawable drawable : drawables) { + if (drawable == null) continue; + ViewHelper.tintDrawable(drawable, color); + } + } + } + + public void setTextColor(@ColorRes int normalColor, @ColorRes int pressedColor) { + int nColor = ContextCompat.getColor(getContext(), normalColor); + int pColor = ContextCompat.getColor(getContext(), pressedColor); + setTextColor(ViewHelper.textSelector(nColor, pColor)); + } + +} diff --git a/app/src/main/java/com/fastaccess/ui/widgets/ForegroundImageView.java b/app/src/main/java/com/fastaccess/ui/widgets/ForegroundImageView.java new file mode 100644 index 00000000..39e94192 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/widgets/ForegroundImageView.java @@ -0,0 +1,123 @@ +package com.fastaccess.ui.widgets; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.support.annotation.ColorInt; +import android.support.annotation.ColorRes; +import android.support.annotation.DrawableRes; +import android.support.annotation.NonNull; +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.AppCompatImageView; +import android.util.AttributeSet; +import android.view.Gravity; +import android.widget.Toast; + +import com.fastaccess.R; +import com.fastaccess.helper.ViewHelper; + + +public class ForegroundImageView extends AppCompatImageView { + private Drawable foreground; + private Toast toast; + + public ForegroundImageView(Context context) { + this(context, null); + } + + public ForegroundImageView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + setOnLongClickListener(view -> { + if (toast != null) toast.cancel(); + toast = Toast.makeText(getContext(), getContentDescription(), Toast.LENGTH_SHORT); + toast.setGravity(Gravity.CENTER, 0, 0); + toast.show(); + return true; + }); + } + + public ForegroundImageView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ForegroundImageView); + Drawable foreground = a.getDrawable(R.styleable.ForegroundImageView_android_foreground); + if (foreground != null) { + setForeground(foreground); + } + a.recycle(); + } + + public void setForegroundResource(@DrawableRes int drawableResId) { + if (isInEditMode()) return; + setForeground(ContextCompat.getDrawable(getContext(), drawableResId)); + } + + public void setForeground(Drawable drawable) { + if (foreground == drawable) { + return; + } + if (foreground != null) { + foreground.setCallback(null); + unscheduleDrawable(foreground); + } + + foreground = drawable; + + if (drawable != null) { + drawable.setCallback(this); + if (drawable.isStateful()) { + drawable.setState(getDrawableState()); + } + } + requestLayout(); + invalidate(); + } + + @Override protected boolean verifyDrawable(@NonNull Drawable who) { + return super.verifyDrawable(who) || who == foreground; + } + + @Override public void jumpDrawablesToCurrentState() { + super.jumpDrawablesToCurrentState(); + if (foreground != null) foreground.jumpToCurrentState(); + } + + @Override protected void drawableStateChanged() { + super.drawableStateChanged(); + if (foreground != null && foreground.isStateful()) { + foreground.setState(getDrawableState()); + } + } + + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + if (foreground != null) { + foreground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight()); + invalidate(); + } + } + + @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + if (foreground != null) { + foreground.setBounds(0, 0, w, h); + invalidate(); + } + } + + @Override public void draw(Canvas canvas) { + super.draw(canvas); + + if (foreground != null) { + foreground.draw(canvas); + } + } + + public void tintDrawable(@ColorRes int colorRes) { + tintDrawableFromColor(ContextCompat.getColor(getContext(), colorRes)); + } + + public void tintDrawableFromColor(@ColorInt int color) { + ViewHelper.tintDrawable(getDrawable(), color); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/ui/widgets/ForegroundRelativeLayout.java b/app/src/main/java/com/fastaccess/ui/widgets/ForegroundRelativeLayout.java new file mode 100644 index 00000000..21bc5549 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/widgets/ForegroundRelativeLayout.java @@ -0,0 +1,100 @@ +package com.fastaccess.ui.widgets; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; +import android.util.AttributeSet; +import android.view.ViewOutlineProvider; +import android.widget.RelativeLayout; + +import com.fastaccess.R; + +/** + * An extension to {@link RelativeLayout} which has a foreground drawable. + */ +public class ForegroundRelativeLayout extends RelativeLayout { + + private Drawable foreground; + + public ForegroundRelativeLayout(Context context, AttributeSet attrs) { + super(context, attrs); + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ForegroundView); + final Drawable d = a.getDrawable(R.styleable.ForegroundView_android_foreground); + if (d != null) { + setForeground(d); + } + a.recycle(); + setOutlineProvider(ViewOutlineProvider.BOUNDS); + } + + @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + if (foreground != null) { + foreground.setBounds(0, 0, w, h); + } + } + + @Override public boolean hasOverlappingRendering() { + return false; + } + + @Override protected boolean verifyDrawable(@NonNull Drawable who) { + return super.verifyDrawable(who) || (who == foreground); + } + + @Override public void jumpDrawablesToCurrentState() { + super.jumpDrawablesToCurrentState(); + if (foreground != null) foreground.jumpToCurrentState(); + } + + @Override protected void drawableStateChanged() { + if (isInEditMode()) return; + super.drawableStateChanged(); + if (foreground != null && foreground.isStateful()) { + foreground.setState(getDrawableState()); + } + } + + public Drawable getForeground() { + return foreground; + } + + public void setForeground(Drawable drawable) { + if (foreground != drawable) { + if (foreground != null) { + foreground.setCallback(null); + unscheduleDrawable(foreground); + } + + foreground = drawable; + + if (foreground != null) { + foreground.setBounds(getLeft(), getTop(), getRight(), getBottom()); + setWillNotDraw(false); + foreground.setCallback(this); + if (foreground.isStateful()) { + foreground.setState(getDrawableState()); + } + } else { + setWillNotDraw(true); + } + invalidate(); + } + } + + @Override public void draw(Canvas canvas) { + super.draw(canvas); + if (foreground != null) { + foreground.draw(canvas); + } + } + + @Override public void drawableHotspotChanged(float x, float y) { + super.drawableHotspotChanged(x, y); + if (foreground != null) { + foreground.setHotspot(x, y); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/ui/widgets/NestedCoordinatorLayout.java b/app/src/main/java/com/fastaccess/ui/widgets/NestedCoordinatorLayout.java new file mode 100644 index 00000000..96b65b29 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/widgets/NestedCoordinatorLayout.java @@ -0,0 +1,110 @@ +package com.fastaccess.ui.widgets; + +import android.content.Context; +import android.support.design.widget.CoordinatorLayout; +import android.support.v4.view.NestedScrollingChild; +import android.support.v4.view.NestedScrollingChildHelper; +import android.util.AttributeSet; +import android.view.View; + +/** + * Created by Kosh on 13 Aug 2016, 1:11 PM + */ + +public class NestedCoordinatorLayout extends CoordinatorLayout implements NestedScrollingChild { + + private NestedScrollingChildHelper mChildHelper; + + public NestedCoordinatorLayout(Context context) { + super(context); + mChildHelper = new NestedScrollingChildHelper(this); + setNestedScrollingEnabled(true); + } + + public NestedCoordinatorLayout(Context context, AttributeSet attrs) { + super(context, attrs); + mChildHelper = new NestedScrollingChildHelper(this); + setNestedScrollingEnabled(true); + } + + public NestedCoordinatorLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + mChildHelper = new NestedScrollingChildHelper(this); + setNestedScrollingEnabled(true); + } + + @Override public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { + /* Enable the scrolling behavior of our own children */ + boolean tHandled = super.onStartNestedScroll(child, target, nestedScrollAxes); + /* Enable the scrolling behavior of the parent's other children */ + return startNestedScroll(nestedScrollAxes) || tHandled; + } + + @Override public void onStopNestedScroll(View target) { + /* Disable the scrolling behavior of our own children */ + super.onStopNestedScroll(target); + /* Disable the scrolling behavior of the parent's other children */ + stopNestedScroll(); + } + + @Override public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) { + int[][] tConsumed = new int[2][2]; + super.onNestedPreScroll(target, dx, dy, tConsumed[0]); + dispatchNestedPreScroll(dx, dy, tConsumed[1], null); + consumed[0] = tConsumed[0][0] + tConsumed[1][0]; + consumed[1] = tConsumed[0][1] + tConsumed[1][1]; + } + + @Override public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { + super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed); + dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, null); + } + + @Override public boolean onNestedPreFling(View target, float velocityX, float velocityY) { + boolean tHandled = super.onNestedPreFling(target, velocityX, velocityY); + return dispatchNestedPreFling(velocityX, velocityY) || tHandled; + } + + @Override public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) { + boolean tHandled = super.onNestedFling(target, velocityX, velocityY, consumed); + return dispatchNestedFling(velocityX, velocityY, consumed) || tHandled; + } + + @Override public void setNestedScrollingEnabled(boolean enabled) { + mChildHelper.setNestedScrollingEnabled(enabled); + } + + @Override public boolean isNestedScrollingEnabled() { + return mChildHelper.isNestedScrollingEnabled(); + } + + @Override public boolean startNestedScroll(int axes) { + return mChildHelper.startNestedScroll(axes); + } + + @Override public void stopNestedScroll() { + mChildHelper.stopNestedScroll(); + } + + @Override public boolean hasNestedScrollingParent() { + return mChildHelper.hasNestedScrollingParent(); + } + + @Override public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) { + return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, + dyUnconsumed, offsetInWindow); + } + + @Override public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { + return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); + } + + @Override public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { + return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed); + } + + @Override public boolean dispatchNestedPreFling(float velocityX, float velocityY) { + return mChildHelper.dispatchNestedPreFling(velocityX, velocityY); + } +} + diff --git a/app/src/main/java/com/fastaccess/ui/widgets/SpannableBuilder.java b/app/src/main/java/com/fastaccess/ui/widgets/SpannableBuilder.java new file mode 100644 index 00000000..1f50c022 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/widgets/SpannableBuilder.java @@ -0,0 +1,94 @@ +package com.fastaccess.ui.widgets; + +import android.support.annotation.ColorInt; +import android.text.SpannableStringBuilder; +import android.text.style.BackgroundColorSpan; +import android.text.style.ForegroundColorSpan; +import android.text.style.StyleSpan; +import android.text.style.URLSpan; +import android.view.View; + +import com.fastaccess.helper.InputHelper; + +import static android.graphics.Typeface.BOLD; + +/** + * Created by Kosh on 15 Nov 2016, 9:26 PM + */ + +public class SpannableBuilder extends SpannableStringBuilder { + + private SpannableBuilder() {} + + public static SpannableBuilder builder() { + return new SpannableBuilder(); + } + + public SpannableBuilder append(final CharSequence text, final Object span) { + if (!InputHelper.isEmpty(text)) { + append(text); + if (span != null) { + final int length = length(); + setSpan(span, length - text.length(), length, SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + return this; + } + + @Override public SpannableBuilder append(char text) { + if (text != 0) super.append(text); + return this; + } + + @Override public SpannableBuilder append(CharSequence text) { + if (text != null) super.append(text); + return this; + } + + public SpannableBuilder append(final char text, final Object span) { + append(text); + if (!InputHelper.isEmpty(span)) { + final int length = length(); + setSpan(span, length - 1, length, SPAN_EXCLUSIVE_EXCLUSIVE); + } + return this; + } + + public SpannableBuilder bold(final CharSequence text) { + if (!InputHelper.isEmpty(text)) return append(text, new StyleSpan(BOLD)); + return this; + } + + public SpannableBuilder background(final CharSequence text, final int color) { + if (!InputHelper.isEmpty(text)) + return append(text, new BackgroundColorSpan(color)); + return this; + } + + public SpannableBuilder foreground(final CharSequence text, @ColorInt final int color) { + if (!InputHelper.isEmpty(text)) return append(text, new ForegroundColorSpan(color)); + return this; + } + + public SpannableBuilder foreground(final char text, @ColorInt final int color) { + return append(text, new ForegroundColorSpan(color)); + } + + public SpannableBuilder url(final CharSequence text, final View.OnClickListener listener) { + if (!InputHelper.isEmpty(text)) + return append(text, new URLSpan(text.toString()) { + @Override + public void onClick(View widget) { + listener.onClick(widget); + } + }); + return this; + } + + public SpannableBuilder url(final CharSequence text) { + if (!InputHelper.isEmpty(text)) return append(text, new URLSpan(text.toString())); + return this; + } + +} + diff --git a/app/src/main/java/com/fastaccess/ui/widgets/StateLayout.java b/app/src/main/java/com/fastaccess/ui/widgets/StateLayout.java new file mode 100644 index 00000000..310013d6 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/widgets/StateLayout.java @@ -0,0 +1,162 @@ +package com.fastaccess.ui.widgets; + +import android.content.Context; +import android.os.Parcelable; +import android.support.annotation.NonNull; +import android.support.annotation.StringRes; +import android.util.AttributeSet; +import android.widget.FrameLayout; +import android.widget.ProgressBar; + +import com.fastaccess.R; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import icepick.Icepick; +import icepick.State; + +/** + * Created by Kosh on 20 Nov 2016, 12:21 AM + */ +public class StateLayout extends FrameLayout { + + private static final int SHOW_PROGRESS_STATE = 1; + private static final int HIDE_PROGRESS_STATE = 2; + private static final int HIDE_RELOAD_STATE = 3; + private static final int SHOW_RELOAD_STATE = 4; + private static final int HIDDEN = 5; + private static final int SHOWEN = 6; + private OnClickListener onReloadListener; + + @BindView(R.id.empty_text) FontTextView emptyText; + @BindView(R.id.reload) FontButton reload; + @BindView(R.id.progressBar) ProgressBar progressBar; + + @State int layoutState = HIDDEN; + @State String emptyTextValue; + @State int adapterSize; + + @OnClick(R.id.reload) void onReload() { + if (onReloadListener != null && !progressBar.isShown()) { + onReloadListener.onClick(reload); + } + } + + public StateLayout(Context context) { + super(context); + } + + public StateLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public StateLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public void showProgress() { + layoutState = SHOW_PROGRESS_STATE; + setVisibility(VISIBLE); + emptyText.setVisibility(GONE); + reload.setVisibility(GONE); + progressBar.setVisibility(VISIBLE); + } + + public void hideProgress() { + layoutState = HIDE_PROGRESS_STATE; + emptyText.setVisibility(VISIBLE); + reload.setVisibility(VISIBLE); + progressBar.setVisibility(GONE); + setVisibility(GONE); + } + + public void hideReload() { + layoutState = HIDE_RELOAD_STATE; + reload.setVisibility(GONE); + emptyText.setVisibility(GONE); + setVisibility(GONE); + } + + public void showReload(int adapterCount) { + this.adapterSize = adapterCount; + hideProgress(); + if (adapterCount == 0) { + layoutState = SHOW_RELOAD_STATE; + reload.setVisibility(VISIBLE); + emptyText.setVisibility(VISIBLE); + setVisibility(VISIBLE); + } + } + + public void setEmptyText(@StringRes int resId) { + setEmptyText(getResources().getString(resId)); + } + + public void setEmptyText(@NonNull String text) { + this.emptyTextValue = text; + emptyText.setText(text); + } + + public void setOnReloadListener(OnClickListener onReloadListener) { + this.onReloadListener = onReloadListener; + } + + @Override public void setVisibility(int visibility) { + super.setVisibility(visibility); + if (visibility == GONE || visibility == INVISIBLE) { + layoutState = HIDDEN; + } else { + layoutState = SHOWEN; + } + } + + @Override protected void onFinishInflate() { + super.onFinishInflate(); + inflate(getContext(), R.layout.empty_layout, this); + if (isInEditMode()) return; + ButterKnife.bind(this); + emptyText.setFreezesText(true); + setNestedScrollingEnabled(true); + } + + @Override protected void onDetachedFromWindow() { + onReloadListener = null; + super.onDetachedFromWindow(); + } + + @Override public Parcelable onSaveInstanceState() { + return Icepick.saveInstanceState(this, super.onSaveInstanceState()); + } + + @Override public void onRestoreInstanceState(Parcelable state) { + super.onRestoreInstanceState(Icepick.restoreInstanceState(this, state)); + onHandleLayoutState(); + } + + private void onHandleLayoutState() { + setEmptyText(emptyTextValue); + switch (layoutState) { + case SHOW_PROGRESS_STATE: + showProgress(); + break; + case HIDE_PROGRESS_STATE: + hideProgress(); + break; + case HIDE_RELOAD_STATE: + hideReload(); + break; + case SHOW_RELOAD_STATE: + showReload(adapterSize); + break; + case HIDDEN: + setVisibility(GONE); + break; + case SHOWEN: + setVisibility(VISIBLE); + showReload(adapterSize); + break; + } + } +} + diff --git a/app/src/main/java/com/fastaccess/ui/widgets/SwitchView.java b/app/src/main/java/com/fastaccess/ui/widgets/SwitchView.java new file mode 100644 index 00000000..f7c74ef6 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/widgets/SwitchView.java @@ -0,0 +1,35 @@ +package com.fastaccess.ui.widgets; + +import android.content.Context; +import android.support.v7.widget.SwitchCompat; +import android.util.AttributeSet; + +import com.fastaccess.helper.TypeFaceHelper; + + +/** + * Created by Kosh on 8/18/2015. copyrights are reserved + */ +public class SwitchView extends SwitchCompat { + + public SwitchView(Context context) { + super(context); + init(); + } + + public SwitchView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + + } + + public SwitchView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + private void init() { + if (isInEditMode()) return; + TypeFaceHelper.applyTypeface(this); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/widgets/ViewPagerView.java b/app/src/main/java/com/fastaccess/ui/widgets/ViewPagerView.java new file mode 100644 index 00000000..3692e306 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/widgets/ViewPagerView.java @@ -0,0 +1,58 @@ +package com.fastaccess.ui.widgets; + +import android.content.Context; +import android.content.res.TypedArray; +import android.support.v4.view.PagerAdapter; +import android.support.v4.view.ViewPager; +import android.util.AttributeSet; +import android.view.MotionEvent; + +import static android.R.attr.enabled; + + +/** + * Created by kosh20111 on 10/8/2015. + *

+ * Viewpager that has scrolling animation by default + */ +public class ViewPagerView extends ViewPager { + + private boolean isEnabled; + + public ViewPagerView(Context context) { + super(context); + } + + public ViewPagerView(Context context, AttributeSet attrs) { + super(context, attrs); + int[] attrsArray = {enabled}; + TypedArray array = context.obtainStyledAttributes(attrs, attrsArray); + isEnabled = array.getBoolean(0, true); + array.recycle(); + } + + @Override public boolean isEnabled() { + return isEnabled; + } + + @Override public void setEnabled(boolean enabled) { + this.isEnabled = enabled; + requestLayout(); + } + + @Override public boolean onTouchEvent(MotionEvent event) { + return !isEnabled() || super.onTouchEvent(event); + } + + @Override public boolean onInterceptTouchEvent(MotionEvent event) { + return isEnabled() && super.onInterceptTouchEvent(event); + } + + @Override public void setAdapter(PagerAdapter adapter) { + super.setAdapter(adapter); + if (isInEditMode()) return; + if (adapter != null) { + setOffscreenPageLimit(adapter.getCount()); + } + } +} diff --git a/app/src/main/java/com/fastaccess/ui/widgets/dialog/ListDialogView.java b/app/src/main/java/com/fastaccess/ui/widgets/dialog/ListDialogView.java new file mode 100644 index 00000000..2b9944fb --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/widgets/dialog/ListDialogView.java @@ -0,0 +1,86 @@ +package com.fastaccess.ui.widgets.dialog; + +import android.content.Context; +import android.os.Bundle; +import android.os.Parcelable; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.helper.BundleConstant; +import com.fastaccess.helper.Bundler; +import com.fastaccess.ui.adapter.SimpleListAdapter; +import com.fastaccess.ui.base.BaseBottomSheetDialog; +import com.fastaccess.ui.widgets.FontTextView; +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; +import com.fastaccess.ui.widgets.recyclerview.DynamicRecyclerView; + +import java.util.ArrayList; + +import butterknife.BindView; + +/** + * Created by Kosh on 31 Dec 2016, 3:19 PM + */ + +public class ListDialogView extends BaseBottomSheetDialog implements BaseViewHolder.OnItemClickListener { + + public interface onSimpleItemSelection { + void onItemSelected(O item); + } + + @BindView(R.id.title) FontTextView title; + @BindView(R.id.recycler) DynamicRecyclerView recycler; + + private onSimpleItemSelection onSimpleItemSelection; + + @Override protected int layoutRes() { + return R.layout.simple_list_dialog; + } + + @Override public void onAttach(Context context) { + super.onAttach(context); + if (getParentFragment() != null && getParentFragment() instanceof onSimpleItemSelection) { + onSimpleItemSelection = (onSimpleItemSelection) getParentFragment(); + } else if (context instanceof onSimpleItemSelection) { + onSimpleItemSelection = (onSimpleItemSelection) context; + } + } + + @Override public void onDetach() { + super.onDetach(); + onSimpleItemSelection = null; + } + + @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + ArrayList objects = getArguments().getParcelableArrayList(BundleConstant.ITEM); + String titleText = getArguments().getString(BundleConstant.EXTRA); + title.setText(titleText); + if (objects != null) { + SimpleListAdapter adapter = new SimpleListAdapter<>(objects, this); + recycler.setAdapter(adapter); + } else { + dismiss(); + } + } + + @SuppressWarnings("unchecked") @Override public void onItemClick(int position, View v, O item) { + if (onSimpleItemSelection != null) { + onSimpleItemSelection.onItemSelected(item); + } + dismiss(); + } + + @Override public void onItemLongClick(int position, View v, O item) { + onItemClick(position, v, item); + } + + public void initArguments(@NonNull String title, @NonNull ArrayList objects) { + setArguments(Bundler.start() + .put(BundleConstant.EXTRA, title) + .putParcelableArrayList(BundleConstant.ITEM, objects) + .end()); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/widgets/dialog/MessageDialogView.java b/app/src/main/java/com/fastaccess/ui/widgets/dialog/MessageDialogView.java new file mode 100644 index 00000000..228624e1 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/widgets/dialog/MessageDialogView.java @@ -0,0 +1,121 @@ +package com.fastaccess.ui.widgets.dialog; + +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.helper.Bundler; +import com.fastaccess.provider.markdown.MarkDownProvider; +import com.fastaccess.ui.base.BaseBottomSheetDialog; +import com.fastaccess.ui.widgets.FontTextView; + +import butterknife.BindView; +import butterknife.OnClick; + +/** + * Created by Kosh on 16 Sep 2016, 2:15 PM + */ + +public class MessageDialogView extends BaseBottomSheetDialog { + + public static final String TAG = MessageDialogView.class.getSimpleName(); + + public interface MessageDialogViewActionCallback { + void onMessageDialogActionClicked(boolean isOk, @Nullable Bundle bundle); + + void onDialogDismissed(); + } + + @BindView(R.id.title) FontTextView title; + @BindView(R.id.message) FontTextView message; + + @Nullable private MessageDialogViewActionCallback callback; + + @Override public void onAttach(Context context) { + super.onAttach(context); + if (getParentFragment() != null && getParentFragment() instanceof MessageDialogViewActionCallback) { + callback = (MessageDialogViewActionCallback) getParentFragment(); + } else if (context instanceof MessageDialogViewActionCallback) { + callback = (MessageDialogViewActionCallback) context; + } + } + + @Override public void onDetach() { + super.onDetach(); + callback = null; + } + + @OnClick({R.id.cancel, R.id.ok}) public void onClick(View view) { + if (callback != null) { + isAlreadyHidden = true; + callback.onMessageDialogActionClicked(view.getId() == R.id.ok, getArguments().getBundle("bundle")); + } + dismiss(); + } + + @Override protected int layoutRes() { + return R.layout.message_dialog; + } + + @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + Bundle bundle = getArguments(); + title.setText(bundle.getString("bundleTitle")); + String msg = bundle.getString("bundleMsg"); + if (bundle.getBoolean("isMarkDown")) { + if (msg != null) { + MarkDownProvider.setMdText(message, msg); + return; + } + } + message.setText(msg); + } + + @Override protected void onDismissedByScrolling() { + super.onDismissedByScrolling(); + if (callback != null) callback.onDialogDismissed(); + } + + @Override protected void onHidden() { + if (callback != null) callback.onDialogDismissed(); + super.onHidden(); + } + + @NonNull public static MessageDialogView newInstance(@NonNull String bundleTitle, @NonNull String bundleMsg) { + return newInstance(bundleTitle, bundleMsg, null); + } + + @NonNull public static MessageDialogView newInstance(@NonNull String bundleTitle, @NonNull String bundleMsg, boolean isMarkDown) { + return newInstance(bundleTitle, bundleMsg, isMarkDown, null); + } + + @NonNull public static MessageDialogView newInstance(@NonNull String bundleTitle, @NonNull String bundleMsg, boolean isMarkDown, + @Nullable Bundle bundle) { + MessageDialogView messageDialogView = new MessageDialogView(); + messageDialogView.setArguments(getBundle(bundleTitle, bundleMsg, isMarkDown, bundle)); + return messageDialogView; + } + + @NonNull public static MessageDialogView newInstance(@NonNull String bundleTitle, @NonNull String bundleMsg, @Nullable Bundle bundle) { + return newInstance(bundleTitle, bundleMsg, false, bundle); + } + + public static Bundle getBundle(String bundleTitle, String bundleMsg, boolean isMarkDown, Bundle bundle) { + return Bundler + .start() + .put("bundleTitle", bundleTitle) + .put("bundleMsg", bundleMsg) + .put("bundle", bundle) + .put("isMarkDown", isMarkDown) + .end(); + } + + public void initMessage() { + Bundle bundle = getArguments(); + title.setText(bundle.getString("bundleTitle")); + message.setText(bundle.getString("bundleMsg")); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/widgets/dialog/ProgressDialogFragment.java b/app/src/main/java/com/fastaccess/ui/widgets/dialog/ProgressDialogFragment.java new file mode 100644 index 00000000..51a5161f --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/widgets/dialog/ProgressDialogFragment.java @@ -0,0 +1,41 @@ +package com.fastaccess.ui.widgets.dialog; + +import android.app.Dialog; +import android.app.ProgressDialog; +import android.content.res.Resources; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.StringRes; +import android.support.v4.app.DialogFragment; + +import com.fastaccess.helper.Bundler; + +/** + * Created by Kosh on 09 Dec 2016, 5:18 PM + */ + +public class ProgressDialogFragment extends DialogFragment { + + public static final String TAG = ProgressDialogFragment.class.getSimpleName(); + + public static ProgressDialogFragment newInstance(@NonNull Resources resources, @StringRes int msgId, boolean isCancelable) { + return newInstance(resources.getString(msgId), isCancelable); + } + + public static ProgressDialogFragment newInstance(@NonNull String msg, boolean isCancelable) { + ProgressDialogFragment fragment = new ProgressDialogFragment(); + fragment.setArguments(Bundler.start() + .put("msg", msg) + .put("isCancelable", isCancelable) + .end()); + return fragment; + } + + @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { + ProgressDialog progressDialog = new ProgressDialog(getContext()); + progressDialog.setMessage(getArguments().getString("msg")); + progressDialog.setCancelable(getArguments().getBoolean("isCancelable")); + setCancelable(getArguments().getBoolean("isCancelable")); + return progressDialog; + } +} diff --git a/app/src/main/java/com/fastaccess/ui/widgets/recyclerview/BaseRecyclerAdapter.java b/app/src/main/java/com/fastaccess/ui/widgets/recyclerview/BaseRecyclerAdapter.java new file mode 100644 index 00000000..e189a1b3 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/widgets/recyclerview/BaseRecyclerAdapter.java @@ -0,0 +1,144 @@ +package com.fastaccess.ui.widgets.recyclerview; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.RecyclerView; +import android.view.ViewGroup; + +import com.fastaccess.helper.AnimHelper; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by Kosh on 17 May 2016, 7:10 PM + */ +public abstract class BaseRecyclerAdapter> extends RecyclerView.Adapter { + + @NonNull private List data; + @Nullable private P listener; + private int lastKnowingPosition = -1; + private boolean enableAnimation = true; + + public BaseRecyclerAdapter() { + this(new ArrayList<>()); + } + + public BaseRecyclerAdapter(@NonNull List data) { + this(data, null); + } + + public BaseRecyclerAdapter(@NonNull List data, @Nullable P listener) { + this.data = data; + this.listener = listener; + } + + protected abstract VH viewHolder(ViewGroup parent, int viewType); + + protected abstract void onBindView(VH holder, int position); + + @NonNull public List getData() { + return data; + } + + public M getItem(int position) { + return data.get(position); + } + + public int getItem(M t) { + return data.indexOf(t); + } + + @Override public VH onCreateViewHolder(ViewGroup parent, int viewType) { + return viewHolder(parent, viewType); + } + + @Override public void onBindViewHolder(VH holder, int position) { + animate(holder); + onBindView(holder, position); + } + + @Override public int getItemCount() { + return data.size(); + } + + private void animate(VH holder) { + int position = holder.getLayoutPosition(); + if (isEnableAnimation() && position > lastKnowingPosition) { + AnimHelper.startBeatsAnimation(holder.itemView); + lastKnowingPosition = position; + } + } + + public void insertItems(List items) { + data.clear(); + addItems(items); + } + + public void addItem(M item) { + data.add(item); + notifyItemInserted(getItemCount() - 1); + } + + @SuppressWarnings("WeakerAccess") public void addItems(List items) { + data.addAll(items); + notifyDataSetChanged(); + } + + @SuppressWarnings("WeakerAccess") public void removeItem(int position) { + data.remove(position); + notifyItemRemoved(position); + } + + public void removeItem(M item) { + int position = data.indexOf(item); + removeItem(position); + } + + public void removeItems(List items) { +// int prevSize = data.size(); + data.removeAll(items); + notifyDataSetChanged(); +// notifyItemRangeRemoved(prevSize, Math.abs(data.size() - prevSize)); + } + + public void swapItem(M model) { + int index = getItem(model); + swapItem(model, index); + } + + public void swapItem(M model, int position) { + data.set(position, model); + notifyDataSetChanged(); + } + + public void subList(int fromPosition, int toPosition) { + toPosition = data.size() > toPosition ? toPosition : data.size(); + data.subList(fromPosition, toPosition).clear(); + notifyItemRangeRemoved(fromPosition, toPosition); + } + + public void clear() { + data.clear(); + notifyItemRangeRemoved(0, getItemCount()); + } + + public void setEnableAnimation(boolean enableAnimation) { + this.enableAnimation = enableAnimation; + notifyDataSetChanged(); + } + + @SuppressWarnings("WeakerAccess") public boolean isEnableAnimation() { + return enableAnimation; + } + + @SuppressWarnings("WeakerAccess") @Nullable public P getListener() { + return listener; + } + + public void setListener(@Nullable P listener) { + this.listener = listener; + notifyDataSetChanged(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/widgets/recyclerview/BaseViewHolder.java b/app/src/main/java/com/fastaccess/ui/widgets/recyclerview/BaseViewHolder.java new file mode 100644 index 00000000..80a20b5c --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/widgets/recyclerview/BaseViewHolder.java @@ -0,0 +1,60 @@ +package com.fastaccess.ui.widgets.recyclerview; + +import android.support.annotation.LayoutRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import butterknife.ButterKnife; + +/** + * Created by Kosh on 17 May 2016, 7:13 PM + */ +public abstract class BaseViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener { + + public interface OnItemClickListener { + void onItemClick(int position, View v, T item); + + void onItemLongClick(int position, View v, T item); + } + + protected BaseRecyclerAdapter adapter; + + public static View getView(ViewGroup parent, @LayoutRes int layoutRes) { + return LayoutInflater.from(parent.getContext()).inflate(layoutRes, parent, false); + } + + public BaseViewHolder(@NonNull View itemView) { + this(itemView, null); + } + + public BaseViewHolder(@NonNull View itemView, @Nullable BaseRecyclerAdapter adapter) { + super(itemView); + ButterKnife.bind(this, itemView); + this.adapter = adapter; + itemView.setOnClickListener(this); + itemView.setOnLongClickListener(this); + } + + @SuppressWarnings("unchecked") @Override public void onClick(View v) { + int position = getAdapterPosition(); + if (adapter != null && adapter.getListener() != null) { + if (position != RecyclerView.NO_POSITION && position < adapter.getItemCount()) + adapter.getListener().onItemClick(position, v, adapter.getItem(position)); + } + } + + @SuppressWarnings("unchecked") @Override public boolean onLongClick(View v) { + int position = getAdapterPosition(); + if (adapter != null && adapter.getListener() != null) { + if (position != RecyclerView.NO_POSITION && position < adapter.getItemCount()) + adapter.getListener().onItemLongClick(position, v, adapter.getItem(position)); + } + return true; + } + + public abstract void bind(@NonNull T t); +} diff --git a/app/src/main/java/com/fastaccess/ui/widgets/recyclerview/BottomPaddingDecoration.java b/app/src/main/java/com/fastaccess/ui/widgets/recyclerview/BottomPaddingDecoration.java new file mode 100644 index 00000000..50412a58 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/widgets/recyclerview/BottomPaddingDecoration.java @@ -0,0 +1,61 @@ +package com.fastaccess.ui.widgets.recyclerview; + +import android.content.Context; +import android.graphics.Rect; +import android.support.annotation.NonNull; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.StaggeredGridLayoutManager; +import android.view.View; + +import com.fastaccess.R; +import com.fastaccess.helper.ViewHelper; + +class BottomPaddingDecoration extends RecyclerView.ItemDecoration { + private int bottomPadding; + + private BottomPaddingDecoration(int bottomOffset) { + bottomPadding = bottomOffset; + } + + private BottomPaddingDecoration(@NonNull Context context) { + this(ViewHelper.toPx(context, context.getResources().getDimensionPixelSize(R.dimen.fab_spacing))); + } + + public static BottomPaddingDecoration with(int bottomPadding) { + return new BottomPaddingDecoration(bottomPadding); + } + + public static BottomPaddingDecoration with(@NonNull Context context) { + return new BottomPaddingDecoration(context); + } + + @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { + super.getItemOffsets(outRect, view, parent, state); + int dataSize = state.getItemCount(); + int position = parent.getChildAdapterPosition(view); + if (parent.getLayoutManager() instanceof LinearLayoutManager) { + if (dataSize > 0 && position == dataSize - 1) { + outRect.set(0, 0, 0, bottomPadding); + } else { + outRect.set(0, 0, 0, 0); + } + } else if (parent.getLayoutManager() instanceof StaggeredGridLayoutManager) { + StaggeredGridLayoutManager grid = (StaggeredGridLayoutManager) parent.getLayoutManager(); + if ((dataSize - position) <= grid.getSpanCount()) { + outRect.set(0, 0, 0, bottomPadding); + } else { + outRect.set(0, 0, 0, 0); + } + } else if (parent.getLayoutManager() instanceof GridLayoutManager) { + GridLayoutManager grid = (GridLayoutManager) parent.getLayoutManager(); + if ((dataSize - position) <= grid.getSpanCount()) { + outRect.set(0, 0, 0, bottomPadding); + } else { + outRect.set(0, 0, 0, 0); + } + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/ui/widgets/recyclerview/DynamicRecyclerView.java b/app/src/main/java/com/fastaccess/ui/widgets/recyclerview/DynamicRecyclerView.java new file mode 100644 index 00000000..21854d4b --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/widgets/recyclerview/DynamicRecyclerView.java @@ -0,0 +1,105 @@ +package com.fastaccess.ui.widgets.recyclerview; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.RecyclerView; +import android.util.AttributeSet; +import android.view.View; + +import com.fastaccess.ui.widgets.StateLayout; + + +/** + * Created by Kosh on 9/24/2015. copyrights are reserved + *

+ * recyclerview which will showParentOrSelf/showParentOrSelf itself base on adapter + */ +public class DynamicRecyclerView extends RecyclerView { + + private StateLayout emptyView; + @Nullable private View parentView; + + @NonNull private AdapterDataObserver observer = new AdapterDataObserver() { + @Override public void onChanged() { + showEmptyView(); + } + + @Override public void onItemRangeInserted(int positionStart, int itemCount) { + super.onItemRangeInserted(positionStart, itemCount); + showEmptyView(); + } + + @Override public void onItemRangeRemoved(int positionStart, int itemCount) { + super.onItemRangeRemoved(positionStart, itemCount); + showEmptyView(); + } + }; + + public DynamicRecyclerView(Context context) { + this(context, null); + } + + public DynamicRecyclerView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public DynamicRecyclerView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + if (isInEditMode()) return; + addItemDecoration(BottomPaddingDecoration.with(context)); + } + + @Override public void setAdapter(@Nullable Adapter adapter) { + super.setAdapter(adapter); + if (isInEditMode()) return; + if (adapter != null) { + adapter.registerAdapterDataObserver(observer); + observer.onChanged(); + } + } + + public void showEmptyView() { + Adapter adapter = getAdapter(); + if (adapter != null) { + if (emptyView != null) { + if (adapter.getItemCount() == 0) { + showParentOrSelf(false); + } else { + showParentOrSelf(true); + } + } + } else { + if (emptyView != null) { + showParentOrSelf(false); + } + } + } + + private void showParentOrSelf(boolean showRecyclerView) { + if (parentView == null) { + setVisibility(showRecyclerView ? VISIBLE : GONE); + } else { + parentView.setVisibility(showRecyclerView ? VISIBLE : GONE); + } + emptyView.setVisibility(!showRecyclerView ? VISIBLE : GONE); + } + + public void setEmptyView(@NonNull StateLayout emptyView, @Nullable View parentView) { + this.emptyView = emptyView; + this.parentView = parentView; + showEmptyView(); + } + + public void setEmptyView(@NonNull StateLayout emptyView) { + setEmptyView(emptyView, null); + } + + public void hideProgress(@NonNull StateLayout view) { + view.hideProgress(); + } + + public void showProgress(@NonNull StateLayout view) { + view.showProgress(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/widgets/recyclerview/layout_manager/GridManager.java b/app/src/main/java/com/fastaccess/ui/widgets/recyclerview/layout_manager/GridManager.java new file mode 100644 index 00000000..fa78284a --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/widgets/recyclerview/layout_manager/GridManager.java @@ -0,0 +1,63 @@ +package com.fastaccess.ui.widgets.recyclerview.layout_manager; + +import android.content.Context; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.util.AttributeSet; + +import com.fastaccess.helper.Logger; + +/** + * Created by Kosh on 17 May 2016, 10:02 PM + */ +public class GridManager extends GridLayoutManager { + + private int iconSize; + + public GridManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public GridManager(Context context, int spanCount) { + super(context, spanCount); + } + + public GridManager(Context context, int spanCount, int orientation, boolean reverseLayout) { + super(context, spanCount, orientation, reverseLayout); + } + + @Override public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { + try { + updateCount(); + super.onLayoutChildren(recycler, state); + } catch (Exception ignored) {} + } + + @Override public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) { + try { + super.onMeasure(recycler, state, widthSpec, heightSpec); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void updateCount() { + if (iconSize > 1) { + int spanCount = Math.max(1, getWidth() / iconSize); + if (spanCount < 1) { + spanCount = 1; + } + Logger.e(spanCount); + this.setSpanCount(spanCount); + } + } + + public int getIconSize() { + return iconSize; + } + + public void setIconSize(int iconSize) { + this.iconSize = iconSize; + updateCount(); + } +} diff --git a/app/src/main/java/com/fastaccess/ui/widgets/recyclerview/layout_manager/LinearManager.java b/app/src/main/java/com/fastaccess/ui/widgets/recyclerview/layout_manager/LinearManager.java new file mode 100644 index 00000000..9a787804 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/widgets/recyclerview/layout_manager/LinearManager.java @@ -0,0 +1,30 @@ +package com.fastaccess.ui.widgets.recyclerview.layout_manager; + +import android.content.Context; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.util.AttributeSet; + +/** + * Created by Kosh on 17 May 2016, 10:02 PM + */ +public class LinearManager extends LinearLayoutManager { + + public LinearManager(Context context) { + super(context); + } + + public LinearManager(Context context, int orientation, boolean reverseLayout) { + super(context, orientation, reverseLayout); + } + + public LinearManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { + try { + super.onLayoutChildren(recycler, state); + } catch (IndexOutOfBoundsException ignored) {} + } +} diff --git a/app/src/main/java/com/fastaccess/ui/widgets/recyclerview/layout_manager/StaggeredManager.java b/app/src/main/java/com/fastaccess/ui/widgets/recyclerview/layout_manager/StaggeredManager.java new file mode 100644 index 00000000..659fba28 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/widgets/recyclerview/layout_manager/StaggeredManager.java @@ -0,0 +1,27 @@ +package com.fastaccess.ui.widgets.recyclerview.layout_manager; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.StaggeredGridLayoutManager; +import android.util.AttributeSet; + +/** + * Created by Kosh on 17 May 2016, 10:02 PM + */ +public class StaggeredManager extends StaggeredGridLayoutManager { + + public StaggeredManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public StaggeredManager(int spanCount, int orientation) { + super(spanCount, orientation); + } + + @Override public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { + try { + super.onLayoutChildren(recycler, state); + } catch (IndexOutOfBoundsException ignored) {} + } + +} diff --git a/app/src/main/java/com/fastaccess/ui/widgets/recyclerview/scroll/InfiniteScroll.java b/app/src/main/java/com/fastaccess/ui/widgets/recyclerview/scroll/InfiniteScroll.java new file mode 100644 index 00000000..82067fda --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/widgets/recyclerview/scroll/InfiniteScroll.java @@ -0,0 +1,88 @@ +package com.fastaccess.ui.widgets.recyclerview.scroll; + +import android.support.v7.widget.RecyclerView; + +/** + * Created by Kosh on 8/2/2015. copyrights are reserved @Innov8tif + */ +public abstract class InfiniteScroll extends RecyclerView.OnScrollListener { + private int previousTotal = 0; + private boolean loading = true; + private int visibleThreshold = 2; + private int firstVisibleItem; + private int visibleItemCount; + private int totalItemCount; + private int current_page = 0; + private RecyclerViewPositionHelper mRecyclerViewHelper; + private static final int HIDE_THRESHOLD = 20; + private int scrolledDistance = 0; + private boolean controlsVisible = true; + private final int PAGING_SIZE = 30; + + public InfiniteScroll() {} + + @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + super.onScrollStateChanged(recyclerView, newState); + mRecyclerViewHelper = RecyclerViewPositionHelper.createHelper(recyclerView); + visibleItemCount = recyclerView.getChildCount(); + totalItemCount = mRecyclerViewHelper.getItemCount(); + firstVisibleItem = mRecyclerViewHelper.findFirstVisibleItemPosition(); + if (PAGING_SIZE > totalItemCount) return; + if (loading) { + if (totalItemCount > previousTotal) { + loading = false; + previousTotal = totalItemCount; + } + } + if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)) { + current_page++; + onLoadMore(current_page, previousTotal); + loading = true; + } + if (firstVisibleItem + visibleItemCount >= totalItemCount) { + onScrollToLast(recyclerView); + } + } + + @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + super.onScrolled(recyclerView, dx, dy); + if (scrolledDistance > HIDE_THRESHOLD && controlsVisible) { + onHide(recyclerView); + controlsVisible = false; + scrolledDistance = 0; + } else if (scrolledDistance < -HIDE_THRESHOLD && !controlsVisible) { + onShow(recyclerView); + controlsVisible = true; + scrolledDistance = 0; + } + if ((controlsVisible && dy > 0) || (!controlsVisible && dy < 0)) { + scrolledDistance += dy; + } + } + + @SuppressWarnings("WeakerAccess") protected void onScrollToLast(RecyclerView recyclerView) { + + } + + @SuppressWarnings("WeakerAccess") protected void onShow(RecyclerView recyclerView) {} + + @SuppressWarnings("WeakerAccess") protected void onHide(RecyclerView recyclerView) { + + } + + protected void onLoadMore(int page, int previousTotal) {} + + public void reset() { + this.previousTotal = 0; + this.loading = true; + this.current_page = 0; + } + + public void setCurrent_page(int page, int previousTotal) { + this.current_page = page; + this.previousTotal = previousTotal; + this.loading = true; + + } +} + diff --git a/app/src/main/java/com/fastaccess/ui/widgets/recyclerview/scroll/RecyclerViewPositionHelper.java b/app/src/main/java/com/fastaccess/ui/widgets/recyclerview/scroll/RecyclerViewPositionHelper.java new file mode 100644 index 00000000..f7c754bf --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/widgets/recyclerview/scroll/RecyclerViewPositionHelper.java @@ -0,0 +1,80 @@ +package com.fastaccess.ui.widgets.recyclerview.scroll; + +import android.support.v7.widget.OrientationHelper; +import android.support.v7.widget.RecyclerView; +import android.view.View; + +/** + * Created by Kosh on 8/2/2015. copyrights are reserved @Innov8tif + */ +class RecyclerViewPositionHelper { + private final RecyclerView recyclerView; + private final RecyclerView.LayoutManager layoutManager; + + private RecyclerViewPositionHelper(RecyclerView recyclerView) { + this.recyclerView = recyclerView; + this.layoutManager = recyclerView.getLayoutManager(); + } + + static RecyclerViewPositionHelper createHelper(RecyclerView recyclerView) { + if (recyclerView == null) { + throw new NullPointerException("Recycler View is null"); + } + return new RecyclerViewPositionHelper(recyclerView); + } + + int getItemCount() { + return layoutManager == null ? 0 : layoutManager.getItemCount(); + } + + int findFirstVisibleItemPosition() { + final View child = findOneVisibleChild(0, layoutManager.getChildCount(), false, true); + return child == null ? RecyclerView.NO_POSITION : recyclerView.getChildAdapterPosition(child); + } + + public int findFirstCompletelyVisibleItemPosition() { + final View child = findOneVisibleChild(0, layoutManager.getChildCount(), true, false); + return child == null ? RecyclerView.NO_POSITION : recyclerView.getChildAdapterPosition(child); + } + + public int findLastVisibleItemPosition() { + final View child = findOneVisibleChild(layoutManager.getChildCount() - 1, -1, false, true); + return child == null ? RecyclerView.NO_POSITION : recyclerView.getChildAdapterPosition(child); + } + + public int findLastCompletelyVisibleItemPosition() { + final View child = findOneVisibleChild(layoutManager.getChildCount() - 1, -1, true, false); + return child == null ? RecyclerView.NO_POSITION : recyclerView.getChildAdapterPosition(child); + } + + private View findOneVisibleChild(int fromIndex, int toIndex, boolean completelyVisible, boolean acceptPartiallyVisible) { + OrientationHelper helper; + if (layoutManager.canScrollVertically()) { + helper = OrientationHelper.createVerticalHelper(layoutManager); + } else { + helper = OrientationHelper.createHorizontalHelper(layoutManager); + } + + final int start = helper.getStartAfterPadding(); + final int end = helper.getEndAfterPadding(); + final int next = toIndex > fromIndex ? 1 : -1; + View partiallyVisible = null; + for (int i = fromIndex; i != toIndex; i += next) { + final View child = layoutManager.getChildAt(i); + final int childStart = helper.getDecoratedStart(child); + final int childEnd = helper.getDecoratedEnd(child); + if (childStart < end && childEnd > start) { + if (completelyVisible) { + if (childStart >= start && childEnd <= end) { + return child; + } else if (acceptPartiallyVisible && partiallyVisible == null) { + partiallyVisible = child; + } + } else { + return child; + } + } + } + return partiallyVisible; + } +} diff --git a/app/src/main/java/com/prettifier/pretty/NestedWebView.java b/app/src/main/java/com/prettifier/pretty/NestedWebView.java new file mode 100644 index 00000000..284286f9 --- /dev/null +++ b/app/src/main/java/com/prettifier/pretty/NestedWebView.java @@ -0,0 +1,128 @@ +package com.prettifier.pretty; + +import android.content.Context; +import android.os.Build; +import android.support.annotation.RequiresApi; +import android.support.v4.view.MotionEventCompat; +import android.support.v4.view.NestedScrollingChild; +import android.support.v4.view.NestedScrollingChildHelper; +import android.support.v4.view.ViewCompat; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.webkit.WebView; + +public class NestedWebView extends WebView implements NestedScrollingChild { + private int mLastY; + private final int[] mScrollOffset = new int[2]; + private final int[] mScrollConsumed = new int[2]; + private int mNestedOffsetY; + private NestedScrollingChildHelper mChildHelper; + private boolean firstScroll = true; + + + public NestedWebView(Context context) { + this(context, null); + } + + public NestedWebView(Context context, AttributeSet attrs) { + this(context, attrs, android.R.attr.webViewStyle); + } + + public NestedWebView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + mChildHelper = new NestedScrollingChildHelper(this); + setNestedScrollingEnabled(true); + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public NestedWebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override public boolean onTouchEvent(MotionEvent ev) { + boolean returnValue; + + MotionEvent event = MotionEvent.obtain(ev); + final int action = MotionEventCompat.getActionMasked(event); + if (action == MotionEvent.ACTION_DOWN) { + mNestedOffsetY = 0; + } + int eventY = (int) event.getY(); + event.offsetLocation(0, mNestedOffsetY); + switch (action) { + case MotionEvent.ACTION_MOVE: + int deltaY = mLastY - eventY; + // NestedPreScroll + if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset)) { + deltaY -= mScrollConsumed[1]; + mLastY = eventY - mScrollOffset[1]; + event.offsetLocation(0, -mScrollOffset[1]); + mNestedOffsetY += mScrollOffset[1]; + } + returnValue = super.onTouchEvent(event); + + // NestedScroll + if (dispatchNestedScroll(0, mScrollOffset[1], 0, deltaY, mScrollOffset)) { + event.offsetLocation(0, mScrollOffset[1]); + mNestedOffsetY += mScrollOffset[1]; + mLastY -= mScrollOffset[1]; + } + break; + case MotionEvent.ACTION_DOWN: + returnValue = super.onTouchEvent(event); + if (firstScroll) { + mLastY = eventY - 5; + firstScroll = false; + } else { + mLastY = eventY; + } + startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL); + break; + default: + returnValue = super.onTouchEvent(event); + // end NestedScroll + stopNestedScroll(); + break; + } + return returnValue; + } + + @Override public void setNestedScrollingEnabled(boolean enabled) { + mChildHelper.setNestedScrollingEnabled(enabled); + } + + @Override public boolean isNestedScrollingEnabled() { + return mChildHelper.isNestedScrollingEnabled(); + } + + @Override public boolean startNestedScroll(int axes) { + return mChildHelper.startNestedScroll(axes); + } + + @Override public void stopNestedScroll() { + mChildHelper.stopNestedScroll(); + } + + @Override public boolean hasNestedScrollingParent() { + return mChildHelper.hasNestedScrollingParent(); + } + + @Override public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, + int[] offsetInWindow) { + return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow); + } + + @Override public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { + return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); + } + + @Override public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { + return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed); + } + + @Override public boolean dispatchNestedPreFling(float velocityX, float velocityY) { + return mChildHelper.dispatchNestedPreFling(velocityX, velocityY); + } + +} + diff --git a/app/src/main/java/com/prettifier/pretty/PrettifyWebView.java b/app/src/main/java/com/prettifier/pretty/PrettifyWebView.java new file mode 100755 index 00000000..503f64c3 --- /dev/null +++ b/app/src/main/java/com/prettifier/pretty/PrettifyWebView.java @@ -0,0 +1,161 @@ +package com.prettifier.pretty; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.net.Uri; +import android.os.Build; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.webkit.WebChromeClient; +import android.webkit.WebResourceRequest; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; + +import com.fastaccess.helper.InputHelper; +import com.fastaccess.helper.Logger; +import com.fastaccess.provider.scheme.SchemeParser; +import com.prettifier.pretty.callback.MarkDownInterceptorInterface; +import com.prettifier.pretty.helper.GithubHelper; +import com.prettifier.pretty.helper.PrettifyHelper; + + +public class PrettifyWebView extends NestedWebView { + private String content; + private OnContentChangedListener onContentChangedListener; + private boolean interceptTouch; + + public interface OnContentChangedListener { + void onContentChanged(int progress); + } + + public PrettifyWebView(Context context) { + super(context); + if (isInEditMode()) return; + initView(); + } + + public PrettifyWebView(Context context, AttributeSet attrs) { + super(context, attrs); + initView(); + } + + public PrettifyWebView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initView(); + } + + @Override public boolean onInterceptTouchEvent(MotionEvent p_event) { + return true; + } + + @Override public boolean onTouchEvent(MotionEvent event) { + if (getParent() != null) { + getParent().requestDisallowInterceptTouchEvent(interceptTouch); + } + return super.onTouchEvent(event); + } + + @SuppressLint("SetJavaScriptEnabled") private void initView() { + if (isInEditMode()) return; + setWebChromeClient(new ChromeClient()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + setWebViewClient(new WebClient()); + } else { + setWebViewClient(new WebClientCompat()); + } + WebSettings settings = getSettings(); + settings.setAppCachePath(getContext().getCacheDir().getPath()); + settings.setAppCacheEnabled(true); + settings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); + settings.setDefaultTextEncodingName("utf-8"); + settings.setJavaScriptEnabled(true); + settings.setLoadsImagesAutomatically(true); + settings.setBlockNetworkImage(false); + } + + public void setOnContentChangedListener(@NonNull OnContentChangedListener onContentChangedListener) { + this.onContentChangedListener = onContentChangedListener; + } + + public void setSource(@NonNull String source) { + WebSettings settings = getSettings(); + settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING); + setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY); + settings.setSupportZoom(true); + settings.setBuiltInZoomControls(true); + settings.setDisplayZoomControls(false); + if (!InputHelper.isEmpty(source)) { + this.content = source; + String page = PrettifyHelper.generateContent(source); + post(() -> loadDataWithBaseURL("file:///android_asset/highlight/", page, "text/html", "utf-8", null)); + } else Log.e(getClass().getSimpleName(), "Source can't be null or empty."); + } + + public void setGithubContent(@NonNull String source, @Nullable String baseUrl) { + if (!InputHelper.isEmpty(source)) { + addJavascriptInterface(new MarkDownInterceptorInterface(this), "Android"); + this.content = source; + String page = GithubHelper.generateContent(source, baseUrl); + post(() -> loadDataWithBaseURL("file:///android_asset/md/", page, "text/html", "utf-8", null)); + } + } + + public void loadImage(@NonNull String url) { + WebSettings settings = getSettings(); + settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN); + setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY); + settings.setSupportZoom(true); + settings.setBuiltInZoomControls(true); + settings.setDisplayZoomControls(false); + String html = ""; + loadData(html, "text/html", null); + } + + public void refresh() { + if (content != null) { + loadUrl("about:blank"); + setSource(content); + } + } + + public void setInterceptTouch(boolean interceptTouch) { + this.interceptTouch = interceptTouch; + } + + private class ChromeClient extends WebChromeClient { + @Override public void onProgressChanged(WebView view, int progress) { + super.onProgressChanged(view, progress); + if (onContentChangedListener != null) { + onContentChangedListener.onContentChanged(progress); + } + } + } + + private class WebClient extends WebViewClient { + @Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { + startActivity(request.getUrl()); + + return true; + } + } + + private void startActivity(Uri url) { + Logger.e(url); + SchemeParser.launchUri(getContext(), url); + } + + private class WebClientCompat extends WebViewClient { + @SuppressWarnings("deprecation") @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { + startActivity(Uri.parse(url)); + return true; + } + } + + +} diff --git a/app/src/main/java/com/prettifier/pretty/callback/MarkDownInterceptorInterface.java b/app/src/main/java/com/prettifier/pretty/callback/MarkDownInterceptorInterface.java new file mode 100644 index 00000000..737178e4 --- /dev/null +++ b/app/src/main/java/com/prettifier/pretty/callback/MarkDownInterceptorInterface.java @@ -0,0 +1,33 @@ +package com.prettifier.pretty.callback; + +import android.webkit.JavascriptInterface; + +import com.fastaccess.helper.Logger; +import com.prettifier.pretty.PrettifyWebView; + +/** + * Created by Kosh on 13 Dec 2016, 3:01 PM + */ + +public class MarkDownInterceptorInterface { + private PrettifyWebView prettifyWebView; + + public MarkDownInterceptorInterface(PrettifyWebView prettifyWebView) { + this.prettifyWebView = prettifyWebView; + } + + @JavascriptInterface public void startIntercept() { + + if (prettifyWebView != null) { + prettifyWebView.setInterceptTouch(true); + } + } + + @JavascriptInterface public void stopIntercept() { + + if (prettifyWebView != null) { + prettifyWebView.setInterceptTouch(false); + } + } + +} diff --git a/app/src/main/java/com/prettifier/pretty/helper/GithubHelper.java b/app/src/main/java/com/prettifier/pretty/helper/GithubHelper.java new file mode 100755 index 00000000..34d17f3f --- /dev/null +++ b/app/src/main/java/com/prettifier/pretty/helper/GithubHelper.java @@ -0,0 +1,99 @@ +package com.prettifier.pretty.helper; + +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.fastaccess.BuildConfig; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * This Class was created by Patrick J + * on 09.06.16. For more Details and Licensing + * have a look at the README.md + */ + +public class GithubHelper { + private static Matcher IMAGE_TAG_MATCHER = Pattern.compile("<(img|IMG)(.*?)>").matcher(""); + + private static Matcher IMAGE_SRC_MATCHER = Pattern.compile("(src|SRC)=\"(.*?)\"").matcher(""); + + private static Matcher LINK_TAG_MATCHER = Pattern.compile("<(a|A)(.*?)>").matcher(""); + + private static Matcher HREF_MATCHER = Pattern.compile("(href)=\"(.*?)\"").matcher(""); + + @NonNull public static String generateContent(@NonNull String source, @Nullable String baseUrl) { + if (baseUrl == null) { + return mergeContent(source); + } else { + return mergeContent(validateImageBaseUrl(source, baseUrl)); + } + } + + @NonNull private static String validateImageBaseUrl(@NonNull String source, @NonNull String baseUrl) { + Uri uri = Uri.parse(baseUrl); + List segments = uri.getPathSegments(); + if (segments == null || segments.size() < 2) return source; + String owner = segments.get(0); + String repoName = segments.get(1); + Matcher matcher = IMAGE_TAG_MATCHER.reset(source); + while (matcher.find()) { + String image = matcher.group(2).trim(); + IMAGE_SRC_MATCHER.reset(image); + String src = null; + if (IMAGE_SRC_MATCHER.find()) { + src = IMAGE_SRC_MATCHER.group(2).trim(); + } + if (src == null || src.startsWith("http://") || src.startsWith("https://")) { + continue; + } + String finalSrc = "https://raw.githubusercontent.com/" + owner + "/" + repoName + "/master/" + src; + source = source.replace(src, finalSrc); + } + return validateLinks(source, baseUrl); + } + + private static String validateLinks(@NonNull String source, @NonNull String baseUrl) { + Uri uri = Uri.parse(baseUrl); + List segments = uri.getPathSegments(); + if (segments == null || segments.size() < 2) return source; + String owner = segments.get(0); + String repoName = segments.get(1); + Matcher matcher = LINK_TAG_MATCHER.reset(source); + while (matcher.find()) { + String link = matcher.group(2).trim(); + HREF_MATCHER.reset(link); + String href = null; + if (HREF_MATCHER.find()) { + href = HREF_MATCHER.group(2).trim(); + } + if (href == null || href.startsWith("http://") || href.startsWith("https://") || href.startsWith("mailto:")) { + continue; + } + String finalSrc = BuildConfig.REST_URL + "repos/" + owner + "/" + repoName + "/contents/" + href; + source = source.replace(href, finalSrc); + } + return source; + } + + private static String mergeContent(@NonNull String source) { + return "\n" + + "\n" + + "\n" + + " \n" + + " " + + " \n" + + "\n" + + "\n" + + "\n" + + source + + "\n\n" + + "\n" + + "\n" + + "\n"; + } + +} diff --git a/app/src/main/java/com/prettifier/pretty/helper/PrettifyHelper.java b/app/src/main/java/com/prettifier/pretty/helper/PrettifyHelper.java new file mode 100755 index 00000000..0d11e081 --- /dev/null +++ b/app/src/main/java/com/prettifier/pretty/helper/PrettifyHelper.java @@ -0,0 +1,39 @@ +package com.prettifier.pretty.helper; + +import android.support.annotation.NonNull; + +/** + * This Class was created by Patrick J + * on 09.06.16. For more Details and Licensing + * have a look at the README.md + */ + +public class PrettifyHelper { + + @NonNull private final static String HTML_CONTENT = + "\n" + + "\n" + + "\n" + + " \n" + + " \n" + + "\n" + + "\n" + + "

%s
\n" + + "\n" + + "\n" + + ""; + + + @NonNull public static String generateContent(@NonNull String source) { + return String.format(HTML_CONTENT, getStyle(), getFormattedSource(source)); + } + + @NonNull private static String getFormattedSource(@NonNull String source) { + return source.replaceAll("<", "<").replaceAll(">", ">"); + } + + @NonNull private static String getStyle() { + return "prettify.css"; + } + +} diff --git a/app/src/main/res/animator/cardview_selector.xml b/app/src/main/res/animator/cardview_selector.xml new file mode 100644 index 00000000..f6f70054 --- /dev/null +++ b/app/src/main/res/animator/cardview_selector.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/animator/raise_selector.xml b/app/src/main/res/animator/raise_selector.xml new file mode 100644 index 00000000..844f0e2f --- /dev/null +++ b/app/src/main/res/animator/raise_selector.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable-nodpi/header_background.jpeg b/app/src/main/res/drawable-nodpi/header_background.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..107f5769de247827093e82061c91e9eac9119f2a GIT binary patch literal 83414 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/bottom_shadow.9.png b/app/src/main/res/drawable-xxhdpi/bottom_shadow.9.png new file mode 100755 index 0000000000000000000000000000000000000000..bd8f7edf8b57bfad2fa448a97569e8e14fe3179c GIT binary patch literal 191 zcmeAS@N?(olHy`uVBq!ia0vp^Vn8g!!3HGbRrPd%RGX)ZV@SoVtyc{B4jBlzTx`|p zT*y?#k=_u%%v^>bP0l+XkKdX7v; literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_timeline_arrow_left.9.png b/app/src/main/res/drawable-xxhdpi/ic_timeline_arrow_left.9.png new file mode 100755 index 0000000000000000000000000000000000000000..0efa3c0cf4b1cf231bb80114d8f8daaf33e3dd79 GIT binary patch literal 1606 zcmV-M2D$l(P)003491^@s6hX!k}000IJNkl?znFQ%ub8xfv1qSc9o#>ayN1#5ji->-c3T8O|~*xlXzw7I#tI09O(2w;3X zjnZswZQbW7vGeou&9$|)4^c+)M6@zKZmT*xJp6&D%#MzZhT7ZPU&U5-8DMc?6*wG@ zZ+Hrggyxg}{(cjA#{5eFiw|pZauUKYL?bW~mRo&&eYH`*#KR)~3h7mbcuMW`^z?C8 zS66vdNQx48D4?{iuCCVO@jRs!n9Jq*1#`8$1ifV3)Ip6&Ub)q3y&nh!j%Wq8w6tWw zUQ#UdoFn(giFL0Vha#>PfBMv_`A ztXzy`7s+LLMbNMH_4S`=gGGjSqj5r! zzsxI}TqF+qRj$+N?vTR&pb3`G=X*w;&45v)5(_J)ezmo=y-%`W2Q9EjF6koAbscp} z60Df|H9S205z#L{6$&2;okOpGK?)&i%I$^xYk7J3Ct6w;c^)H5jcl;~r(asF_O{dM z{GAe5mH1p5IbcPGV;)W%7+YIg-zNICM+vMVIblTt%NNFv0|Nt=U@&-=<*;s~0#>ws zL6>`LVPWBW3Sbq-NK9pcCFs{x>~?GIcKbMkm3Y7%)-SW!{Fc}2-C?kj)GwRO_5sl^ zFN2k&e$CCz^)gtO(67C{y$1|dV*P^d_qB$GhPxh*XPv=HJm4;cn_I2c7BWb6Gg$HU zD?IlI`ZYB*^;tGdcw`FJ3;MOQvopkCCDyO-`>*Qi>Nj03*Pjel;`d*WPSo`D^t3Qo z@!#JI**%Qt>lv)%l2y>I*BPwjhCJgMU@=$>7K6oLF<1;1gT-JmST7~49K3l^mcUBMDjG4cDisc^Qc3pb{+^dzPyoV1@S~#7~F(twx_426zFxe+t=jZ1?R}w6!PlR5H-fx(aP&60}CZErzh()?k52z0(aVKjkQ;71h zv9V4iz#1PP?}Ykr681=^dQ~9&7poi&$HPp5Iyg9Z2=$0)RVt;xifmo5EB2b%Y;Gjt zx{@JLUxE8WJva&HI;3ln8}vLC9UUF-65V*7W}SKt_r;7|(upDI3`bc1VC?Mde9zGiwCoDm~^6@_m zAz>OF9sNY^u!QoUTqqy!Q^Lt`epV)c2_ad8Vq2xx>+5G`X1+W=K3WVTEK%fnttk9kiu@!P$CVH;XZ5cN0&fic@H;&77M9pcbR-av1N__-cGuf^|I;`Mqg zPs(YMinCY;$rfVR=<)T*b<`YV46ne?4fy>cPQG(W6%S2lX&Uao6L<@;mFQx4tP^k} zTM|`};z1M6cE0F*=Zv1MtW+{)_SbWm^=HuTf>5PZPWO}RU86D*A{=i%dv+eYxi9} z|K*oAz5o66X;EZZ?DjpkU)M-5C9*8bwEA9Kzb{7bd3$^N=d#_l_Vy250}dOk5$its z*yjA|%vo~c-AA?Fq&;(bkjJv_`sO#a5*f)Z^HlP{axKUVms&UnSw zs=w=UZ-4*&_uuEc*FGe2v zy^=TSGd6uHJ-o?*iA7YS_vAhnj#JlpPcv}}ge)*o`z`cf|JmBR8+3?LtDq5YuEPz? z=8czTyzy>WP&29#5s;&SgC~`d9XLPkJMtd>uq<=d_Qw`=A8p%H1%7O0X-oWZ!{qGt zLYe*c(MH?r=Wo546X$#R%dY~9n!j5oSIRQ&W4ZSFYx}y_b#Kac&o^(DYD?T2p(FRa z5~ONJgihGL^o=}BQB1%}1iG!PC{xWt~$(69BT(QH}rr literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/bottom_border.xml b/app/src/main/res/drawable/bottom_border.xml new file mode 100644 index 00000000..4820ce4b --- /dev/null +++ b/app/src/main/res/drawable/bottom_border.xml @@ -0,0 +1,14 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_add.xml b/app/src/main/res/drawable/ic_add.xml new file mode 100644 index 00000000..ad4981cf --- /dev/null +++ b/app/src/main/res/drawable/ic_add.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_announcement.xml b/app/src/main/res/drawable/ic_announcement.xml new file mode 100644 index 00000000..3d8eee24 --- /dev/null +++ b/app/src/main/res/drawable/ic_announcement.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_arrow_drop_down.xml b/app/src/main/res/drawable/ic_arrow_drop_down.xml new file mode 100644 index 00000000..62b27ef0 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_drop_down.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_arrow_right.xml b/app/src/main/res/drawable/ic_arrow_right.xml new file mode 100644 index 00000000..a3d16222 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_right.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_at.xml b/app/src/main/res/drawable/ic_at.xml new file mode 100644 index 00000000..38d97d2c --- /dev/null +++ b/app/src/main/res/drawable/ic_at.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_back.xml b/app/src/main/res/drawable/ic_back.xml new file mode 100644 index 00000000..ea6953ed --- /dev/null +++ b/app/src/main/res/drawable/ic_back.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_bookmark.xml b/app/src/main/res/drawable/ic_bookmark.xml new file mode 100644 index 00000000..6a6a1b39 --- /dev/null +++ b/app/src/main/res/drawable/ic_bookmark.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_brower.xml b/app/src/main/res/drawable/ic_brower.xml new file mode 100644 index 00000000..d976b424 --- /dev/null +++ b/app/src/main/res/drawable/ic_brower.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_clear.xml b/app/src/main/res/drawable/ic_clear.xml new file mode 100644 index 00000000..7ecab7cc --- /dev/null +++ b/app/src/main/res/drawable/ic_clear.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_clear_black.xml b/app/src/main/res/drawable/ic_clear_black.xml new file mode 100644 index 00000000..82656b2f --- /dev/null +++ b/app/src/main/res/drawable/ic_clear_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_code.xml b/app/src/main/res/drawable/ic_code.xml new file mode 100644 index 00000000..6f1ccb6e --- /dev/null +++ b/app/src/main/res/drawable/ic_code.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_comment.xml b/app/src/main/res/drawable/ic_comment.xml new file mode 100644 index 00000000..a8e74d26 --- /dev/null +++ b/app/src/main/res/drawable/ic_comment.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_done.xml b/app/src/main/res/drawable/ic_done.xml new file mode 100644 index 00000000..1b3f9529 --- /dev/null +++ b/app/src/main/res/drawable/ic_done.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_download.xml b/app/src/main/res/drawable/ic_download.xml new file mode 100644 index 00000000..492b41d3 --- /dev/null +++ b/app/src/main/res/drawable/ic_download.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_edit.xml b/app/src/main/res/drawable/ic_edit.xml new file mode 100644 index 00000000..2ab2fb75 --- /dev/null +++ b/app/src/main/res/drawable/ic_edit.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_email.xml b/app/src/main/res/drawable/ic_email.xml new file mode 100644 index 00000000..ce97ab85 --- /dev/null +++ b/app/src/main/res/drawable/ic_email.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_eye.xml b/app/src/main/res/drawable/ic_eye.xml new file mode 100644 index 00000000..e02f1d19 --- /dev/null +++ b/app/src/main/res/drawable/ic_eye.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_eye_off.xml b/app/src/main/res/drawable/ic_eye_off.xml new file mode 100644 index 00000000..ee74d3cc --- /dev/null +++ b/app/src/main/res/drawable/ic_eye_off.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_file_document.xml b/app/src/main/res/drawable/ic_file_document.xml new file mode 100644 index 00000000..693cc52c --- /dev/null +++ b/app/src/main/res/drawable/ic_file_document.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_file_multi.xml b/app/src/main/res/drawable/ic_file_multi.xml new file mode 100644 index 00000000..8f6ef573 --- /dev/null +++ b/app/src/main/res/drawable/ic_file_multi.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_folder.xml b/app/src/main/res/drawable/ic_folder.xml new file mode 100644 index 00000000..e6155636 --- /dev/null +++ b/app/src/main/res/drawable/ic_folder.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_fork.xml b/app/src/main/res/drawable/ic_fork.xml new file mode 100644 index 00000000..f7b41f1f --- /dev/null +++ b/app/src/main/res/drawable/ic_fork.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_format_bold.xml b/app/src/main/res/drawable/ic_format_bold.xml new file mode 100644 index 00000000..8879506b --- /dev/null +++ b/app/src/main/res/drawable/ic_format_bold.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_format_italic.xml b/app/src/main/res/drawable/ic_format_italic.xml new file mode 100644 index 00000000..707d4107 --- /dev/null +++ b/app/src/main/res/drawable/ic_format_italic.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_format_list_bulleted.xml b/app/src/main/res/drawable/ic_format_list_bulleted.xml new file mode 100644 index 00000000..6cb93c69 --- /dev/null +++ b/app/src/main/res/drawable/ic_format_list_bulleted.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_format_quote.xml b/app/src/main/res/drawable/ic_format_quote.xml new file mode 100644 index 00000000..68cda7d8 --- /dev/null +++ b/app/src/main/res/drawable/ic_format_quote.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_format_strikethrough.xml b/app/src/main/res/drawable/ic_format_strikethrough.xml new file mode 100644 index 00000000..386eb3eb --- /dev/null +++ b/app/src/main/res/drawable/ic_format_strikethrough.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_format_underlined.xml b/app/src/main/res/drawable/ic_format_underlined.xml new file mode 100644 index 00000000..948a96ca --- /dev/null +++ b/app/src/main/res/drawable/ic_format_underlined.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_gists.xml b/app/src/main/res/drawable/ic_gists.xml new file mode 100644 index 00000000..70ffb88a --- /dev/null +++ b/app/src/main/res/drawable/ic_gists.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_github.xml b/app/src/main/res/drawable/ic_github.xml new file mode 100644 index 00000000..5fb60eec --- /dev/null +++ b/app/src/main/res/drawable/ic_github.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_github_black.xml b/app/src/main/res/drawable/ic_github_black.xml new file mode 100644 index 00000000..5fb60eec --- /dev/null +++ b/app/src/main/res/drawable/ic_github_black.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_group.xml b/app/src/main/res/drawable/ic_group.xml new file mode 100644 index 00000000..4cfd8696 --- /dev/null +++ b/app/src/main/res/drawable/ic_group.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_header_one.xml b/app/src/main/res/drawable/ic_header_one.xml new file mode 100644 index 00000000..4dc72555 --- /dev/null +++ b/app/src/main/res/drawable/ic_header_one.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_header_three.xml b/app/src/main/res/drawable/ic_header_three.xml new file mode 100644 index 00000000..65f1883e --- /dev/null +++ b/app/src/main/res/drawable/ic_header_three.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_header_two.xml b/app/src/main/res/drawable/ic_header_two.xml new file mode 100644 index 00000000..6601c6a3 --- /dev/null +++ b/app/src/main/res/drawable/ic_header_two.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_home.xml b/app/src/main/res/drawable/ic_home.xml new file mode 100644 index 00000000..70fb2910 --- /dev/null +++ b/app/src/main/res/drawable/ic_home.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_image.xml b/app/src/main/res/drawable/ic_image.xml new file mode 100644 index 00000000..b2018595 --- /dev/null +++ b/app/src/main/res/drawable/ic_image.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_insert_link.xml b/app/src/main/res/drawable/ic_insert_link.xml new file mode 100644 index 00000000..538c5bdf --- /dev/null +++ b/app/src/main/res/drawable/ic_insert_link.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_issue_closed.xml b/app/src/main/res/drawable/ic_issue_closed.xml new file mode 100644 index 00000000..089ec76a --- /dev/null +++ b/app/src/main/res/drawable/ic_issue_closed.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_issue_opened.xml b/app/src/main/res/drawable/ic_issue_opened.xml new file mode 100644 index 00000000..c565d0a4 --- /dev/null +++ b/app/src/main/res/drawable/ic_issue_opened.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_issues.xml b/app/src/main/res/drawable/ic_issues.xml new file mode 100644 index 00000000..a07a0f90 --- /dev/null +++ b/app/src/main/res/drawable/ic_issues.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_label.xml b/app/src/main/res/drawable/ic_label.xml new file mode 100644 index 00000000..fd14344d --- /dev/null +++ b/app/src/main/res/drawable/ic_label.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_language.xml b/app/src/main/res/drawable/ic_language.xml new file mode 100644 index 00000000..d07324c8 --- /dev/null +++ b/app/src/main/res/drawable/ic_language.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_license.xml b/app/src/main/res/drawable/ic_license.xml new file mode 100644 index 00000000..f0592e30 --- /dev/null +++ b/app/src/main/res/drawable/ic_license.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_list_numbers.xml b/app/src/main/res/drawable/ic_list_numbers.xml new file mode 100644 index 00000000..918410f5 --- /dev/null +++ b/app/src/main/res/drawable/ic_list_numbers.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_location.xml b/app/src/main/res/drawable/ic_location.xml new file mode 100644 index 00000000..e3291a94 --- /dev/null +++ b/app/src/main/res/drawable/ic_location.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_lock.xml b/app/src/main/res/drawable/ic_lock.xml new file mode 100644 index 00000000..9a14b68f --- /dev/null +++ b/app/src/main/res/drawable/ic_lock.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_menu.xml b/app/src/main/res/drawable/ic_menu.xml new file mode 100644 index 00000000..6b35e7f5 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_merge.xml b/app/src/main/res/drawable/ic_merge.xml new file mode 100644 index 00000000..2abca015 --- /dev/null +++ b/app/src/main/res/drawable/ic_merge.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_milestone.xml b/app/src/main/res/drawable/ic_milestone.xml new file mode 100644 index 00000000..a44a9881 --- /dev/null +++ b/app/src/main/res/drawable/ic_milestone.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_minus.xml b/app/src/main/res/drawable/ic_minus.xml new file mode 100644 index 00000000..50fabe5b --- /dev/null +++ b/app/src/main/res/drawable/ic_minus.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_overflow.xml b/app/src/main/res/drawable/ic_overflow.xml new file mode 100644 index 00000000..64120176 --- /dev/null +++ b/app/src/main/res/drawable/ic_overflow.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_profile.xml b/app/src/main/res/drawable/ic_profile.xml new file mode 100644 index 00000000..47cca5c5 --- /dev/null +++ b/app/src/main/res/drawable/ic_profile.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_pull_requests.xml b/app/src/main/res/drawable/ic_pull_requests.xml new file mode 100644 index 00000000..b60dce5c --- /dev/null +++ b/app/src/main/res/drawable/ic_pull_requests.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_redo.xml b/app/src/main/res/drawable/ic_redo.xml new file mode 100644 index 00000000..424e788f --- /dev/null +++ b/app/src/main/res/drawable/ic_redo.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_search.xml b/app/src/main/res/drawable/ic_search.xml new file mode 100644 index 00000000..3d0fa1ba --- /dev/null +++ b/app/src/main/res/drawable/ic_search.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_settings.xml b/app/src/main/res/drawable/ic_settings.xml new file mode 100644 index 00000000..ace746c4 --- /dev/null +++ b/app/src/main/res/drawable/ic_settings.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_share.xml b/app/src/main/res/drawable/ic_share.xml new file mode 100644 index 00000000..e37dfdbd --- /dev/null +++ b/app/src/main/res/drawable/ic_share.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_star.xml b/app/src/main/res/drawable/ic_star.xml new file mode 100644 index 00000000..6bcbf2dd --- /dev/null +++ b/app/src/main/res/drawable/ic_star.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_time.xml b/app/src/main/res/drawable/ic_time.xml new file mode 100644 index 00000000..2239a4f4 --- /dev/null +++ b/app/src/main/res/drawable/ic_time.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_trash.xml b/app/src/main/res/drawable/ic_trash.xml new file mode 100644 index 00000000..0cec62b3 --- /dev/null +++ b/app/src/main/res/drawable/ic_trash.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_undo.xml b/app/src/main/res/drawable/ic_undo.xml new file mode 100644 index 00000000..5558e37d --- /dev/null +++ b/app/src/main/res/drawable/ic_undo.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_unlock.xml b/app/src/main/res/drawable/ic_unlock.xml new file mode 100644 index 00000000..c8125981 --- /dev/null +++ b/app/src/main/res/drawable/ic_unlock.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/left_border.xml b/app/src/main/res/drawable/left_border.xml new file mode 100644 index 00000000..93d03fad --- /dev/null +++ b/app/src/main/res/drawable/left_border.xml @@ -0,0 +1,17 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/right_border.xml b/app/src/main/res/drawable/right_border.xml new file mode 100644 index 00000000..8733f5cf --- /dev/null +++ b/app/src/main/res/drawable/right_border.xml @@ -0,0 +1,17 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/splash_screen_drawable.xml b/app/src/main/res/drawable/splash_screen_drawable.xml new file mode 100644 index 00000000..28bdf0d0 --- /dev/null +++ b/app/src/main/res/drawable/splash_screen_drawable.xml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/toolbar_shadow.xml b/app/src/main/res/drawable/toolbar_shadow.xml new file mode 100644 index 00000000..8a84c332 --- /dev/null +++ b/app/src/main/res/drawable/toolbar_shadow.xml @@ -0,0 +1,8 @@ + + + + diff --git a/app/src/main/res/drawable/toolbar_shadow_up.xml b/app/src/main/res/drawable/toolbar_shadow_up.xml new file mode 100644 index 00000000..e0f05b05 --- /dev/null +++ b/app/src/main/res/drawable/toolbar_shadow_up.xml @@ -0,0 +1,8 @@ + + + + diff --git a/app/src/main/res/drawable/top_border.xml b/app/src/main/res/drawable/top_border.xml new file mode 100644 index 00000000..b4040121 --- /dev/null +++ b/app/src/main/res/drawable/top_border.xml @@ -0,0 +1,16 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/main_layouts/layout/activity_fragment_layout.xml b/app/src/main/res/layouts/main_layouts/layout/activity_fragment_layout.xml new file mode 100644 index 00000000..71f04289 --- /dev/null +++ b/app/src/main/res/layouts/main_layouts/layout/activity_fragment_layout.xml @@ -0,0 +1,14 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/main_layouts/layout/activity_main_view.xml b/app/src/main/res/layouts/main_layouts/layout/activity_main_view.xml new file mode 100644 index 00000000..d1ce5bef --- /dev/null +++ b/app/src/main/res/layouts/main_layouts/layout/activity_main_view.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/main_layouts/layout/centered_tabbed_viewpager.xml b/app/src/main/res/layouts/main_layouts/layout/centered_tabbed_viewpager.xml new file mode 100644 index 00000000..d1adb05d --- /dev/null +++ b/app/src/main/res/layouts/main_layouts/layout/centered_tabbed_viewpager.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/main_layouts/layout/commit_pager_activity.xml b/app/src/main/res/layouts/main_layouts/layout/commit_pager_activity.xml new file mode 100644 index 00000000..6cf3f237 --- /dev/null +++ b/app/src/main/res/layouts/main_layouts/layout/commit_pager_activity.xml @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/main_layouts/layout/create_gist_layout.xml b/app/src/main/res/layouts/main_layouts/layout/create_gist_layout.xml new file mode 100644 index 00000000..ee3a2485 --- /dev/null +++ b/app/src/main/res/layouts/main_layouts/layout/create_gist_layout.xml @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/main_layouts/layout/editor_layout.xml b/app/src/main/res/layouts/main_layouts/layout/editor_layout.xml new file mode 100644 index 00000000..f2fb8a67 --- /dev/null +++ b/app/src/main/res/layouts/main_layouts/layout/editor_layout.xml @@ -0,0 +1,256 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/main_layouts/layout/general_viewer_layout.xml b/app/src/main/res/layouts/main_layouts/layout/general_viewer_layout.xml new file mode 100644 index 00000000..2dc5c432 --- /dev/null +++ b/app/src/main/res/layouts/main_layouts/layout/general_viewer_layout.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/main_layouts/layout/gists_pager_layout.xml b/app/src/main/res/layouts/main_layouts/layout/gists_pager_layout.xml new file mode 100644 index 00000000..e2ecb6de --- /dev/null +++ b/app/src/main/res/layouts/main_layouts/layout/gists_pager_layout.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/main_layouts/layout/issue_pager_activity.xml b/app/src/main/res/layouts/main_layouts/layout/issue_pager_activity.xml new file mode 100644 index 00000000..ad9aac90 --- /dev/null +++ b/app/src/main/res/layouts/main_layouts/layout/issue_pager_activity.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/main_layouts/layout/login_layout.xml b/app/src/main/res/layouts/main_layouts/layout/login_layout.xml new file mode 100644 index 00000000..e310c716 --- /dev/null +++ b/app/src/main/res/layouts/main_layouts/layout/login_layout.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/main_layouts/layout/repo_file_layout.xml b/app/src/main/res/layouts/main_layouts/layout/repo_file_layout.xml new file mode 100644 index 00000000..85b85331 --- /dev/null +++ b/app/src/main/res/layouts/main_layouts/layout/repo_file_layout.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/main_layouts/layout/repo_pager_activity.xml b/app/src/main/res/layouts/main_layouts/layout/repo_pager_activity.xml new file mode 100644 index 00000000..672a7fbc --- /dev/null +++ b/app/src/main/res/layouts/main_layouts/layout/repo_pager_activity.xml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/main_layouts/layout/search_layout.xml b/app/src/main/res/layouts/main_layouts/layout/search_layout.xml new file mode 100644 index 00000000..94139c94 --- /dev/null +++ b/app/src/main/res/layouts/main_layouts/layout/search_layout.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/main_layouts/layout/small_grid_refresh_list.xml b/app/src/main/res/layouts/main_layouts/layout/small_grid_refresh_list.xml new file mode 100644 index 00000000..4e975475 --- /dev/null +++ b/app/src/main/res/layouts/main_layouts/layout/small_grid_refresh_list.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/main_layouts/layout/tabbed_pager_layout.xml b/app/src/main/res/layouts/main_layouts/layout/tabbed_pager_layout.xml new file mode 100644 index 00000000..8d157581 --- /dev/null +++ b/app/src/main/res/layouts/main_layouts/layout/tabbed_pager_layout.xml @@ -0,0 +1,15 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/main_layouts/layout/tabbed_viewpager.xml b/app/src/main/res/layouts/main_layouts/layout/tabbed_viewpager.xml new file mode 100644 index 00000000..0ee2875b --- /dev/null +++ b/app/src/main/res/layouts/main_layouts/layout/tabbed_viewpager.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/main_layouts/layout/title_header_layout.xml b/app/src/main/res/layouts/main_layouts/layout/title_header_layout.xml new file mode 100644 index 00000000..15c91bbe --- /dev/null +++ b/app/src/main/res/layouts/main_layouts/layout/title_header_layout.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/main_layouts/layout/vertical_refresh_list.xml b/app/src/main/res/layouts/main_layouts/layout/vertical_refresh_list.xml new file mode 100644 index 00000000..6660da7a --- /dev/null +++ b/app/src/main/res/layouts/main_layouts/layout/vertical_refresh_list.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/other_layouts/layout/appbar_elevation_dark.xml b/app/src/main/res/layouts/other_layouts/layout/appbar_elevation_dark.xml new file mode 100644 index 00000000..462cc2a6 --- /dev/null +++ b/app/src/main/res/layouts/other_layouts/layout/appbar_elevation_dark.xml @@ -0,0 +1,33 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/other_layouts/layout/appbar_elevation_light.xml b/app/src/main/res/layouts/other_layouts/layout/appbar_elevation_light.xml new file mode 100644 index 00000000..9c1b36b0 --- /dev/null +++ b/app/src/main/res/layouts/other_layouts/layout/appbar_elevation_light.xml @@ -0,0 +1,30 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/other_layouts/layout/appbar_search_elevation.xml b/app/src/main/res/layouts/other_layouts/layout/appbar_search_elevation.xml new file mode 100644 index 00000000..66f77ce6 --- /dev/null +++ b/app/src/main/res/layouts/other_layouts/layout/appbar_search_elevation.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/other_layouts/layout/appbar_tabbed_elevation.xml b/app/src/main/res/layouts/other_layouts/layout/appbar_tabbed_elevation.xml new file mode 100644 index 00000000..cd491102 --- /dev/null +++ b/app/src/main/res/layouts/other_layouts/layout/appbar_tabbed_elevation.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/other_layouts/layout/avatar_layout.xml b/app/src/main/res/layouts/other_layouts/layout/avatar_layout.xml new file mode 100644 index 00000000..8e6e9455 --- /dev/null +++ b/app/src/main/res/layouts/other_layouts/layout/avatar_layout.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/other_layouts/layout/drawer_header.xml b/app/src/main/res/layouts/other_layouts/layout/drawer_header.xml new file mode 100644 index 00000000..908b9284 --- /dev/null +++ b/app/src/main/res/layouts/other_layouts/layout/drawer_header.xml @@ -0,0 +1,32 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/other_layouts/layout/empty_layout.xml b/app/src/main/res/layouts/other_layouts/layout/empty_layout.xml new file mode 100644 index 00000000..a2871a51 --- /dev/null +++ b/app/src/main/res/layouts/other_layouts/layout/empty_layout.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/other_layouts/layout/message_dialog.xml b/app/src/main/res/layouts/other_layouts/layout/message_dialog.xml new file mode 100644 index 00000000..d6e0275e --- /dev/null +++ b/app/src/main/res/layouts/other_layouts/layout/message_dialog.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/other_layouts/layout/simple_list_dialog.xml b/app/src/main/res/layouts/other_layouts/layout/simple_list_dialog.xml new file mode 100644 index 00000000..6d280a83 --- /dev/null +++ b/app/src/main/res/layouts/other_layouts/layout/simple_list_dialog.xml @@ -0,0 +1,26 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/other_layouts/layout/state_layout.xml b/app/src/main/res/layouts/other_layouts/layout/state_layout.xml new file mode 100644 index 00000000..dc5ba9b1 --- /dev/null +++ b/app/src/main/res/layouts/other_layouts/layout/state_layout.xml @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layouts/row_layouts/layout/comments_row_item.xml b/app/src/main/res/layouts/row_layouts/layout/comments_row_item.xml new file mode 100644 index 00000000..a1e39e28 --- /dev/null +++ b/app/src/main/res/layouts/row_layouts/layout/comments_row_item.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/row_layouts/layout/commit_comments_row_item.xml b/app/src/main/res/layouts/row_layouts/layout/commit_comments_row_item.xml new file mode 100644 index 00000000..8215085f --- /dev/null +++ b/app/src/main/res/layouts/row_layouts/layout/commit_comments_row_item.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/row_layouts/layout/commit_file_row_item.xml b/app/src/main/res/layouts/row_layouts/layout/commit_file_row_item.xml new file mode 100644 index 00000000..51cbe0e2 --- /dev/null +++ b/app/src/main/res/layouts/row_layouts/layout/commit_file_row_item.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/row_layouts/layout/feeds_row_item.xml b/app/src/main/res/layouts/row_layouts/layout/feeds_row_item.xml new file mode 100644 index 00000000..b940986c --- /dev/null +++ b/app/src/main/res/layouts/row_layouts/layout/feeds_row_item.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/row_layouts/layout/file_path_row_item.xml b/app/src/main/res/layouts/row_layouts/layout/file_path_row_item.xml new file mode 100644 index 00000000..1fca7b6d --- /dev/null +++ b/app/src/main/res/layouts/row_layouts/layout/file_path_row_item.xml @@ -0,0 +1,13 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layouts/row_layouts/layout/gist_files_row_item.xml b/app/src/main/res/layouts/row_layouts/layout/gist_files_row_item.xml new file mode 100644 index 00000000..e8001c27 --- /dev/null +++ b/app/src/main/res/layouts/row_layouts/layout/gist_files_row_item.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/row_layouts/layout/issue_detail_header_row_item.xml b/app/src/main/res/layouts/row_layouts/layout/issue_detail_header_row_item.xml new file mode 100644 index 00000000..017f5057 --- /dev/null +++ b/app/src/main/res/layouts/row_layouts/layout/issue_detail_header_row_item.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/row_layouts/layout/issue_row_item.xml b/app/src/main/res/layouts/row_layouts/layout/issue_row_item.xml new file mode 100644 index 00000000..4399372e --- /dev/null +++ b/app/src/main/res/layouts/row_layouts/layout/issue_row_item.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/row_layouts/layout/issue_timeline_row_item.xml b/app/src/main/res/layouts/row_layouts/layout/issue_timeline_row_item.xml new file mode 100644 index 00000000..e18ab635 --- /dev/null +++ b/app/src/main/res/layouts/row_layouts/layout/issue_timeline_row_item.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/row_layouts/layout/profile_overview_layout.xml b/app/src/main/res/layouts/row_layouts/layout/profile_overview_layout.xml new file mode 100644 index 00000000..9194d13f --- /dev/null +++ b/app/src/main/res/layouts/row_layouts/layout/profile_overview_layout.xml @@ -0,0 +1,201 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/row_layouts/layout/releases_row_item.xml b/app/src/main/res/layouts/row_layouts/layout/releases_row_item.xml new file mode 100644 index 00000000..d12475b6 --- /dev/null +++ b/app/src/main/res/layouts/row_layouts/layout/releases_row_item.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/row_layouts/layout/repo_files_row_item.xml b/app/src/main/res/layouts/row_layouts/layout/repo_files_row_item.xml new file mode 100644 index 00000000..c2d707ed --- /dev/null +++ b/app/src/main/res/layouts/row_layouts/layout/repo_files_row_item.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/row_layouts/layout/repos_row_item.xml b/app/src/main/res/layouts/row_layouts/layout/repos_row_item.xml new file mode 100644 index 00000000..8bba99b6 --- /dev/null +++ b/app/src/main/res/layouts/row_layouts/layout/repos_row_item.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/row_layouts/layout/simple_row_item.xml b/app/src/main/res/layouts/row_layouts/layout/simple_row_item.xml new file mode 100644 index 00000000..c4a21f6b --- /dev/null +++ b/app/src/main/res/layouts/row_layouts/layout/simple_row_item.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/bottom_nav_menu.xml b/app/src/main/res/menu/bottom_nav_menu.xml new file mode 100644 index 00000000..9743c520 --- /dev/null +++ b/app/src/main/res/menu/bottom_nav_menu.xml @@ -0,0 +1,27 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/download_browser_menu.xml b/app/src/main/res/menu/download_browser_menu.xml new file mode 100644 index 00000000..ea04d2ef --- /dev/null +++ b/app/src/main/res/menu/download_browser_menu.xml @@ -0,0 +1,15 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/download_menu.xml b/app/src/main/res/menu/download_menu.xml new file mode 100644 index 00000000..0bafbf4b --- /dev/null +++ b/app/src/main/res/menu/download_menu.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/download_share_menu.xml b/app/src/main/res/menu/download_share_menu.xml new file mode 100644 index 00000000..145fcd92 --- /dev/null +++ b/app/src/main/res/menu/download_share_menu.xml @@ -0,0 +1,18 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/follow_menu.xml b/app/src/main/res/menu/follow_menu.xml new file mode 100644 index 00000000..a6ea7b4d --- /dev/null +++ b/app/src/main/res/menu/follow_menu.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/gist_menu.xml b/app/src/main/res/menu/gist_menu.xml new file mode 100644 index 00000000..77140ab3 --- /dev/null +++ b/app/src/main/res/menu/gist_menu.xml @@ -0,0 +1,16 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/issue_menu.xml b/app/src/main/res/menu/issue_menu.xml new file mode 100644 index 00000000..47cd2156 --- /dev/null +++ b/app/src/main/res/menu/issue_menu.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/pull_request_menu.xml b/app/src/main/res/menu/pull_request_menu.xml new file mode 100644 index 00000000..22fa89fe --- /dev/null +++ b/app/src/main/res/menu/pull_request_menu.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/repo_bottom_nav_menu.xml b/app/src/main/res/menu/repo_bottom_nav_menu.xml new file mode 100644 index 00000000..897b7967 --- /dev/null +++ b/app/src/main/res/menu/repo_bottom_nav_menu.xml @@ -0,0 +1,27 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/repo_menu.xml b/app/src/main/res/menu/repo_menu.xml new file mode 100644 index 00000000..e5f37939 --- /dev/null +++ b/app/src/main/res/menu/repo_menu.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/search_menu.xml b/app/src/main/res/menu/search_menu.xml new file mode 100644 index 00000000..d6f409c0 --- /dev/null +++ b/app/src/main/res/menu/search_menu.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/share_menu.xml b/app/src/main/res/menu/share_menu.xml new file mode 100644 index 00000000..2e5ea11e --- /dev/null +++ b/app/src/main/res/menu/share_menu.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..cde69bcccec65160d92116f20ffce4fce0b5245c GIT binary patch literal 3418 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..efc028a636dd690a51db5a525cf781a5a7daba68 GIT binary patch literal 2555 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..3af2608a4492ef9ae63a77ec3305aedda89594cb GIT binary patch literal 6114 zcmV<87aiz{P)QBg$Z&8YKy<2dSjG6I2&!iu7JRdT!gcBlJx2NL9-^PTGD_Ptf# z_t*dbRdw&}d+xcr-QAko7-Mb(cL9%PAop{-%ba$?L0~%p4=0Y}p*W8FU1n`tILPv} zML2!uMd(K8O&CZREHF@fhVQ(Z5yVrJcYBD!LfyzFt;&e2oN5Pm5Z@1b~qKj96+4}@|h;R-VA2(=2-37BtnR`#_JMV#vgaqj!A)$dLw zzAqt=kf%brlHdkMtlkP5%mgwQBTv+&?;R(E^s|ch{RoQ*)slEY&`lQ-Zm%FW<@tmV z)uL|w%v_~goAvXG*IfwH2{j7hrMtKlq}vjs(Nzf{YD8VTsI{f7SiPs>{X2v+3gRt% zb1Q)~2q^^WJXX;T&sN_Xm~Vh zb#=9En0OP&wxC@%Z{GYqE-tQJs}Mm3TMTBXa{GnLsc$2`UQ2AK7a~NTIdi77l7ri6 z`43X1QUv+6ZQSM9m9|2JpMU;2wWOq^>uu=?@`M*IT!7^#gZw+m<=EqrAj0+Q*Hg$H zJ$Oq+P^6h2REa1@$fx}f$avWbNp+}hvdvenT!~)3e7WZ>$&QpcFrEB6N8An?S5|d~ zB^5-n^6EnVzO|5VtXly~JQKl6t4`ZnH?qHmS_oEMUA;k(9l5u-^-~3>C<3lsKL5sz z8*E#~Y!;d{mW8E%&1x=JwThmAI-oA!r+v=m8+=*h@o#ut?Trbv)l*PrWo2c7E!qoY zv?ucapvd#>&UUU|y~?7Ft!1Hy#&Qu1ry?9_Xo~@Lh|Ar;$)A_t%k~~!$?NJ!b|m5f zD<~+?wMb?p0}NHHJDsdpOP+u2+BKGS@&sFv@K-LtvgALql8XG>>WXmgqKZ7WIB_f& zU}@aPypE`=gT1H@oRBLjNl8iR<+gNF7DT_{uWTA=gaS^s< z%wkurUa`v+VILVNZ9(p5&+%~X&FO)h{Q2?zEb7oEUPshb%hUyrC1qui#Fe{(H`iD{ zRqAcU+)jfQUrQMS%gf7S-|N5O0)!^L%Z?YuT5Yf-9N%BNewEc+xx~t=irJa+43>S) zz%q&ta%7!LpwEu;@37DH>(}^iY-Kh0{%FB|wjj};3$QLWfY%M~M`LW_lSb%0be!=n z=>;;NR8>`VrY@E*Tu+@dUH;<5i!9}cfh{roiHor2@c*#Ns?tVRBuR&FuDMdhPL?LI znB3KD)A6ZndFr3ox5@9Z#Yu0oMTf?4EIjlk$D*XSSZFf2wv-7hB0Ye9vyz=WpTq+! zj-?a>uPZK{XDd?v%;qQhv4#3^RHsB@%l79i<(6Z#^lR)?X&T#`y^t+W`7gHk(A$K!h-@XsSO{Q_ z1&MDE-egNtK45#Y=JR7-yLJ`R2>e{TGZ%95=NtUkj`-EQPNk!V64;&s^jD12Z2L5d8ftq zyOG5#aFz8-zzQoWDwsZbKMOUyPa?cS*8WGfB+2Mr8lh1DQ}T@ha9>YYm^g+69%r=v z__uf+P#4t6m8)x_7c3LKpq-|`OA);fS^h;=S--LuAlT)cq+Ve7k_#Z=dI9`R1ZaXE zTN(c;%gN1hCh%JA1>lTg$|Z^gPk_rKM~-+p?EA?l1}H|n%#}T$>{1bnI5thh0oRf5 zhyW?TQ78(VIKDpAD{DT0|E=TTVVd^}lVCZ>RO!CxE{d0Zhr4 zKq633p6N<=REuMsI(2F@aq7|R=va0U@>@OV$LCxXeEATae15ZT$0qqLXZ;fM3_ffX zxudd6u9+^EDQS6mdFj%nOZ$M^O`A4(G&kevMmg-8u5v%dIhV^U@_3+a;vH~3EhzvH zerz(Yv$L6z(hVghCVl{J$++7$m;JcYNby@&SU(zo(Pezz59)-Qkso^K9k!GPWv;P) zO92*B#)Z$D69CZXZRB-#L3&z`xI)CQ5tDQtHr>yN5hFawZ>70H0O|KJ(zQiAM!xa+ z8(8I~Qbr?h^1~-+L_EnM@@-i^M!+~Gj*WA~o%)U+ODTYod;sSyD04m@NDd1N3)6e{ z?CE9I4aw{$H#c`6{h(U;W3ASI`O1%cg{e7L6PLG+Ro7H=f+Wf>7PB>JpV;kstO>CC z@L%XyB__wlxngoxS+#zNh+_fdihgve7sxnJSy@@LapT6};8=A~CIz6p)lcF7>z%Rw ztYQOqE9QhNf$vKy^GyhnIGDTAY3o0jyF&HY#g%z%fx*wF0GO!DEJ|>;7jOYE{}mGx z^S;$|RQms_s;aLQ%Z&}rSbxN^DK^QM?x&2bU5zBTCCAA(6(Ii92GwJi(&%?#;+s~< zm)Lk@BDKY-fZQNQ#c642(^cbuB0p_M5qq_>qhDA|-npa3Sxqa%D+6psajXSF)zwvO z)A4|2$+u{kLd}ek4`)t&f|q+W6j- z0PM_|$J^x0>?nE=#aBIX>}4@6A>O!+88fESjT<+PE9Ww_xSxwv6>LSyhjt49D_@d4 zj_t^t&7w~(WgCuu$v=0Nd#hD8qeFL)eT85DHFdl`B_vr><7ui~v0N7AEpW8vVEJ0hJn>BfdHEZ4SI_DI}ALlgP-T0h7K zHXi<(x6K&=Dk>^!LPJCU-69i`0_@wjZy5dHvQ`1m(ZtGVFFh9YMw@u3| zsZxMNix&M>Oifz~5E&Uc*clguAeCE~ZdV55O5$DRdaPN$5kBlBwM|PPR=S{|prEI% z3b10uipNP|%|RH0jr7xTMBJDbB3=XePP!h6ISD#;^i-^-6*DP7X=!QY#EBE1v?{56WdhMqlpwur`B{lT@#wL)Sb=014v;I1?hKJJVF ziCMeZ)CgZT@jD+Q*6Y|m2w$)FG2(j#Hu$hfz(yZ7`3D`FM40>oy$X+~mWiZq^wQN!a4U%W09`Y}ytox6)@@>Gjsp1aB6&4H(@B9+rxsS>y9hrkD{m+6AQ@Wv75@>#&X6UUn0?$%>?%Ou~~$fQB>|XVzxj~G?mf5Z1w?P7Icu_AM|CxK#VU7 ziKQ}@Tni!CCUh*w1m0G0D93RDK)jrcOG!xyCywt2*A|QOVv)d$y2(_5}*ufmkC#VvUv_!U^}|q|YVN zdC;W*Y$RUCQ^@AC9-Ud%V-9Ts$OW0|>T0%j?b;8)G5P=Y)>g#YFI>2A1f`;vw4|bH z0&tKBuwo1HRRowV+)7ZiQGj3z@_kjv_q8NH!2$9O&6BTH0GWcGJ9n=7^Uptj5gc1v zl7vsf7Y|*&d^ydf0*IcV6rqv)C|UY(%-*jqKoGf`phlOY6u`$!0O4M22w;o+xmL(` zMgWwVnVA{H?IYmWBmgTn8YbUMMVF$YqUBnyifD`hs)HjT0ukD1{rgM>Fel&WddM9e z^i>hS7+{qG%!$)+zi&$b$H;eH0Nlok-^9ekU^T3Z;8=azyLT_X>~!$p!4DL1puuGV z$e3`@Pn~?}|D%0G3{WHAw~2hE04SRgz!~yG5=J>JfV?mZlX%OQFaImJr8sb(RRP4{ zpu>Cbz4x2z*RK~l>W1tRK!|`$W@c2A8{(M{h*ywrDu7HIeND)hutvTVz!~zL5PRXyfA!T@F%8{8r2E#l*Is)Ky`WoRVPTl^nF#g^u*-5TMhym|dzooYzJ>MsD9ASz z06Bbf0=SBNM+Ff1e=YWpjg8$-oOT!7+TKVZq(~2L-@bjkV(z=acKP3Kjy9E%|Uyn;*HgDd% z2wVzI?c0PKdSLwc@z2tjpxoY+)ENN)xEG`A(KW&$^2zE$5_FaVxPW{I1(3nFQm51X z4qSfv>8JNPa-$@_Mu^IuM~@y|CYIq^OaNt`4sy-OHy1!H`>`ND!IF4QQP>DY54gkoLBjT`qL)Riji=><{%TdPj?fX`6c>3Tx+O_OP+0(d(WaLvhg zKmcz2d3kvk$ohW|4kt{QaG#c&<=sY(9EnG}_ew}em@5_{ZixT@+>tHv8&|CKX5_~^ zZuRz%Z;t@d`Z4hq78bSy+zAe~JvD{84q`!9%7})Pl$7K)H!g6c09=GPQ}To3nxIO) zezb)Et|C9!z8=6AUdV0d_wL;r1Fx=j<^HyM0d*rN_{geNt3JVnNw#j>MlVS|xyNM! zND;6YqDsCLK!tpJh znl)3RwZ3Th`#ocJ*~5?s0b>4~1hh7IdRW&f>Pw+5p! zYViPF6n-#0J)IrU?_rzvuVUf*mTSPWTY|8CORXXzY6Xjq+s)g8HkrF0#f{i(&6+g} zz>VOjMV=?^Mt-eB$BrFwUCR@(v9aM8Y(N7Hz0L0p#w66)vuANv2+PUI!F{rA3aB&c zjy9kz=JyQC=?2X8M@B|&0Vm)_+=|*_|Fq%WzkmM+#M0W(>2yR;ZA2vKF(C~QR>FGH0JZzw5qOy;dm)D4tl$2!Yj_%O^4p931dU4P1 z;SL=-JPQs47wuZo^{9y;gYsj9r}TRL0U4N4(bo8cbZ74RS3Hc5?b)*jZU>i{Kc)z} zxBMTLaKiROh77?!4B=nsp4_{4?+I(BdH*rUgJo3oD zb?)35A`G51Y0{r*R9FCC*%o_)((2KM)YR0oUwrWe23dpAMzr;IxgDD#bm`Kib06C1 z^`OTefBc2ryLWGw!*@*6))}|fZuNDduDGw4ZP~JA=YRnNu&Ol(ZF`Wm)<(Wk1f*dd z`}OPhD3t?{A5Wh?{fi?P3)lXhp;~2zSE+E$T{EpBESy_`f2@A0XP) zQM9pD|D_=YBKJM^*kj$hb?b(ICjCvP6-x%LaS@ltE?m-Jm>{bTRTd|41uQ zht;cBFM8&gXZ|4E%|O%@brx3d(H6LfFb5-hhTK4$NNMZLHW^QvKA?TDuaazO=@1&@6gpQS&WUqV9i9^wKM-|89fhxN z*Vc(wiw)??9pO_&wglHSm`HeX;J|^u4+seOf(AMpl9G~+;;Mr3@^ZewE&p3UtUNJm zn^>dZSr?w~!ynRDSy`W-pI@1roO~3=#yM~lW29pNtM``b5s=k5x!TRq|b4{^B1?GF9`<{9 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..324e72cdd7480cb983fa1bcc7ce686e51ef87fe7 GIT binary patch literal 7718 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..9bec2e623103ac9713b00cad8502a057c1efda61 GIT binary patch literal 10056 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 ek+^GN3bv-?^>(QkVinb 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 + 46dp + + diff --git a/app/src/main/res/values-sw600dp-land/dimens.xml b/app/src/main/res/values-sw600dp-land/dimens.xml new file mode 100644 index 00000000..a087d59e --- /dev/null +++ b/app/src/main/res/values-sw600dp-land/dimens.xml @@ -0,0 +1,7 @@ + + + 52dp + 24dp + 2 + @dimen/spacing_micro + \ No newline at end of file diff --git a/app/src/main/res/values-sw600dp/dimens.xml b/app/src/main/res/values-sw600dp/dimens.xml new file mode 100644 index 00000000..ca5318fb --- /dev/null +++ b/app/src/main/res/values-sw600dp/dimens.xml @@ -0,0 +1,6 @@ + + + 32dp + 24dp + 1 + \ No newline at end of file diff --git a/app/src/main/res/values-sw720dp-land/dimens.xml b/app/src/main/res/values-sw720dp-land/dimens.xml new file mode 100644 index 00000000..6fa8fb03 --- /dev/null +++ b/app/src/main/res/values-sw720dp-land/dimens.xml @@ -0,0 +1,7 @@ + + + 82dp + 24dp + 2 + @dimen/spacing_micro + \ No newline at end of file diff --git a/app/src/main/res/values-sw720dp/dimens.xml b/app/src/main/res/values-sw720dp/dimens.xml new file mode 100644 index 00000000..1a70fda4 --- /dev/null +++ b/app/src/main/res/values-sw720dp/dimens.xml @@ -0,0 +1,7 @@ + + + 58dp + 2 + 24dp + 1 + \ No newline at end of file diff --git a/app/src/main/res/values-v23/styles.xml b/app/src/main/res/values-v23/styles.xml new file mode 100644 index 00000000..3b826921 --- /dev/null +++ b/app/src/main/res/values-v23/styles.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-w820dp-land/dimens.xml b/app/src/main/res/values-w820dp-land/dimens.xml new file mode 100755 index 00000000..cef80fc6 --- /dev/null +++ b/app/src/main/res/values-w820dp-land/dimens.xml @@ -0,0 +1,7 @@ + + 84dp + 3 + 24dp + 3 + @dimen/spacing_micro + diff --git a/app/src/main/res/values-w820dp/dimens.xml b/app/src/main/res/values-w820dp/dimens.xml new file mode 100755 index 00000000..d71f02b3 --- /dev/null +++ b/app/src/main/res/values-w820dp/dimens.xml @@ -0,0 +1,8 @@ + + 64dp + 2 + 24dp + 2 + 144dp + @dimen/spacing_micro + diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml new file mode 100644 index 00000000..42f4d639 --- /dev/null +++ b/app/src/main/res/values/attrs.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + / + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 00000000..bb48194a --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,275 @@ + + + #F5F5F5 + #E0E0E0 + #FAFAFA + @color/material_blue_accent_700 + #FF2A456B + #213755 + #C5CAE9 + #448AFF + #212121 + #727272 + @color/white + #B6B6B6 + #00000000 + #FF000000 + #FFFFFF + #80000000 + @color/primary_light + @color/primary_light + #ffb2b2b2 + #111C2C + @color/material_amber_800 + #FF616161 + #FFECB3 + #FFE082 + #FFD54F + #FFCA28 + #FFF8E1 + #FFC107 + #FFB300 + #FFA000 + #FF8F00 + #FF6F00 + #FFE57F + #FFD740 + #FFC400 + #FFAB00 + #BBDEFB + #90CAF9 + #64B5F6 + #42A5F5 + #E3F2FD + #2196F3 + #1E88E5 + #1976D2 + #1565C0 + #0D47A1 + #82B1FF + #448AFF + #2979FF + #2962FF + #CFD8DC + #B0BEC5 + #90A4AE + #78909C + #ECEFF1 + #607D8B + #546E7A + #455A64 + #37474F + #263238 + #D7CCC8 + #BCAAA4 + #A1887F + #8D6E63 + #EFEBE9 + #795548 + #6D4C41 + #5D4037 + #4E342E + #3E2723 + #B2EBF2 + #80DEEA + #4DD0E1 + #26C6DA + #E0F7FA + #00BCD4 + #00ACC1 + #0097A7 + #00838F + #006064 + #84FFFF + #18FFFF + #00E5FF + #00B8D4 + #FFCCBC + #FFAB91 + #FF8A65 + #FF7043 + #FBE9E7 + #FF5722 + #F4511E + #E64A19 + #D84315 + #BF360C + #FF9E80 + #FF6E40 + #FF3D00 + #DD2C00 + #D1C4E9 + #B39DDB + #9575CD + #7E57C2 + #EDE7F6 + #673AB7 + #5E35B1 + #512DA8 + #4527A0 + #311B92 + #B388FF + #7C4DFF + #651FFF + #6200EA + #C8E6C9 + #A5D6A7 + #81C784 + #66BB6A + #E8F5E9 + #4CAF50 + #43A047 + #388E3C + #2E7D32 + #1B5E20 + #B9F6CA + #69F0AE + #00E676 + #00C853 + #EEEEEE + #BDBDBD + #9E9E9E + #616161 + #C5CAE9 + #9FA8DA + #7986CB + #5C6BC0 + #E8EAF6 + #3F51B5 + #3949AB + #303F9F + #283593 + #1A237E + #8C9EFF + #536DFE + #3D5AFE + #304FFE + #000000 + #B3E5FC + #81D4FA + #4FC3F7 + #29B6F6 + #E1F5FE + #03A9F4 + #039BE5 + #0288D1 + #0277BD + #01579B + #80D8FF + #40C4FF + #00B0FF + #0091EA + #DCEDC8 + #C5E1A5 + #AED581 + #9CCC65 + #F1F8E9 + #8BC34A + #7CB342 + #689F38 + #558B2F + #33691E + #CCFF90 + #B2FF59 + #76FF03 + #64DD17 + #FFFFFF + #F0F4C3 + #E6EE9C + #DCE775 + #D4E157 + #F9FBE7 + #CDDC39 + #C0CA33 + #AFB42B + #9E9D24 + #827717 + #F4FF81 + #EEFF41 + #C6FF00 + #AEEA00 + #FFE0B2 + #FFCC80 + #FFB74D + #FFA726 + #FFF3E0 + #FF9800 + #FB8C00 + #F57C00 + #EF6C00 + #E65100 + #FFD180 + #FFAB40 + #FF9100 + #FF6D00 + #F8BBD0 + #F48FB1 + #F06292 + #EC407A + #FCE4EC + #E91E63 + #D81B60 + #C2185B + #AD1457 + #880E4F + #FF80AB + #FF4081 + #F50057 + #C51162 + #E1BEE7 + #CE93D8 + #BA68C8 + #AB47BC + #F3E5F5 + #9C27B0 + #8E24AA + #7B1FA2 + #6A1B9A + #4A148C + #EA80FC + #E040FB + #D500F9 + #AA00FF + #FFCDD2 + #EF9A9A + #E57373 + #EF5350 + #FFEBEE + #F44336 + #E53935 + #D32F2F + #C62828 + #B71C1C + #FF8A80 + #FF5252 + #FF1744 + #D50000 + #B2DFDB + #80CBC4 + #4DB6AC + #26A69A + #E0F2F1 + #009688 + #00897B + #00796B + #00695C + #004D40 + #A7FFEB + #64FFDA + #1DE9B6 + #00BFA5 + #FFF9C4 + #FFF59D + #FFF176 + #FFEE58 + #FFFDE7 + #FFEB3B + #FDD835 + #FBC02D + #F9A825 + #F57F17 + #FFFF8D + #FFFF00 + #FFEA00 + #FFD600 + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 00000000..250b1d04 --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,26 @@ + + 16dp + 4dp + 8dp + 16dp + 24dp + 32dp + 48dp + 64dp + 72dp + 86dp + 16dp + 1 + 1 + 0dp + 2dp + 8dp + 8dp + 2dp + 1.7778 + 1.3333 + 2.5 + 48dp + 64dp + 24dp + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..8107d01a --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,137 @@ + + FastHub + com.fastaccess.ui.widgets.recyclerview.layout_manager.LinearManager + com.fastaccess.ui.widgets.recyclerview.layout_manager.GridManager + com.fastaccess.ui.widgets.recyclerview.layout_manager.StaggeredManager + @string/appbar_scrolling_view_behavior + In Progress, Please Wait… + Action + Settings + @android:string/cancel + @android:string/ok + No Data Available. + Search + Please Login to Continue Using FastHub + Sign-In to your Github account to get the best out of FastHub + Failed to sign-in. + Sign-In + Share + Reload + Feeds + Gists + Profile + Error + Press once again to exit! + Readme + Opened + Closed + Choose Repo + Commits + Followers + Following + Overview + Repositories + Forked + Starred + Follow + Unfollow + User + profile_image_transition + profile_title_transition + Code + Issues + Pull Requests + Details + Repo + Archive file detected, Please download the file to view its content. + Minimum Characters (3) + No File Found + No ReadMe Found + Downloading… + Downloading File… + Released + Drafted + This + On + Releases + No Body + Source code (zip) + Source code (tar.gz) + Contributors + Merged + By + Star + Fork + Close Issue + Re-Open Issue + Re-Open + Close + Successfully re-opened. + Locking the conversation means:\n·Other users can’t add new comments to this issue.\n·You and other + collaborators with access to this repository can still leave comments that others can see.\n·You can always unlock this issue again in the + future.\n + Unlocking the conversation means:\n·Everyone will be able to comment on this issue once more.\n·You can + always lock this issue again in the future.\n + Lock Conversation + Unlock Conversation + Error in closing issue, Please try again later. + Error re-opening issue, Please try again later. + Successfully closed issue. + No description provided. + Header One + Header Two + Header Three + Bold + Italic + Strike Through + Bullet List + Numbered List + Header + Quote + Link + Image + Deletion + Addition + Changes + Status + Are you sure? + Success + To + Error Deleting Comment + Delete + Comments + Issue + Merge + Pull Request + Commit + Successfully merged. + File + File Menu + Files + Download + Back + Parent Folder + Code Viewer + Open in Browser + File is Large + The file is large to open it.\nClick (OK) to Download it instead… + Viewer + Markdown + Submit + Type Here + Write + Description + File Name + Secret Gist + Public Gist + Submit As + Delete + Error deleting gist. + No Files + Required Field + Successfully Submitted + Create Gist + Gist + Clear + Users + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..3f5a1d6c --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..56188460 --- /dev/null +++ b/build.gradle @@ -0,0 +1,28 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + jcenter() + maven { url 'http://oss.jfrog.org/artifactory/oss-snapshot-local' } + } + dependencies { + classpath 'com.android.tools.build:gradle:2.3.0-beta4' + classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' + classpath 'com.google.gms:google-services:3.0.0' + classpath 'me.tatarka:gradle-retrolambda:3.2.4' + classpath 'me.tatarka.retrolambda.projectlombok:lombok.ast:0.2.3.a2' + classpath 'com.jakewharton.hugo:hugo-plugin:1.2.1' + classpath 'com.siimkinks.sqlitemagic:sqlitemagic-plugin:0.11.0' + } + configurations.classpath.exclude group: 'com.android.tools.external.lombok' +} + +allprojects { + repositories { + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..13372aef5e24af05341d49695ee84e5f9b594659 GIT binary patch literal 53636 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ⅅ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 H-+%o-(+5aQ literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..977f19d8 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Thu Feb 02 14:52:04 MYT 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip diff --git a/gradlew b/gradlew new file mode 100755 index 00000000..9d82f789 --- /dev/null +++ b/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 00000000..8a0b282a --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 00000000..e7b4def4 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include ':app'