From d2ff9b8a8ab98bbac72ee03e304527878aca22d7 Mon Sep 17 00:00:00 2001 From: M M Arif Date: Tue, 19 Sep 2023 05:23:43 +0000 Subject: [PATCH] Dashboard view (#1279) Closes #1231 Closes #1280 Closes #1295 Closes #1298 Reviewed-on: https://codeberg.org/gitnex/GitNex/pulls/1279 Co-authored-by: M M Arif Co-committed-by: M M Arif --- .woodpecker/build.yml | 2 +- .woodpecker/check.yml | 2 +- .woodpecker/finish.yml | 2 +- .woodpecker/locale.yml | 2 +- README.md | 23 +- app/build.gradle | 10 +- .../gitnex/activities/DeepLinksActivity.java | 2 +- .../activities/IssueDetailActivity.java | 22 +- .../mian/gitnex/activities/LoginActivity.java | 4 +- .../mian/gitnex/activities/MainActivity.java | 28 +- .../activities/RepoStargazersActivity.java | 71 +- .../activities/RepoWatchersActivity.java | 69 +- .../activities/SettingsGeneralActivity.java | 3 + .../gitnex/adapters/DashboardAdapter.java | 892 ++++++++++++++++++ .../gitnex/adapters/ExploreIssuesAdapter.java | 41 +- .../gitnex/adapters/PullRequestsAdapter.java | 27 + .../fragments/BottomSheetReplyFragment.java | 2 +- .../gitnex/fragments/DashboardFragment.java | 123 +++ .../gitnex/viewmodels/DashboardViewModel.java | 114 +++ app/src/main/res/drawable/ic_dashboard.xml | 34 + app/src/main/res/drawable/ic_draft.xml | 48 + .../main/res/drawable/shape_beta_badge.xml | 14 + .../main/res/drawable/shape_draft_release.xml | 2 +- .../main/res/drawable/shape_pre_release.xml | 2 +- .../res/drawable/shape_stable_release.xml | 2 +- app/src/main/res/layout/badge_beta.xml | 21 + .../main/res/layout/fragment_dashboard.xml | 49 + .../res/layout/list_dashboard_activity.xml | 139 +++ app/src/main/res/layout/list_pr.xml | 32 +- app/src/main/res/layout/list_releases.xml | 6 +- app/src/main/res/menu/drawer_menu.xml | 6 + app/src/main/res/values/settings.xml | 1 + app/src/main/res/values/strings.xml | 3 + 33 files changed, 1696 insertions(+), 102 deletions(-) create mode 100644 app/src/main/java/org/mian/gitnex/adapters/DashboardAdapter.java create mode 100644 app/src/main/java/org/mian/gitnex/fragments/DashboardFragment.java create mode 100644 app/src/main/java/org/mian/gitnex/viewmodels/DashboardViewModel.java create mode 100644 app/src/main/res/drawable/ic_dashboard.xml create mode 100644 app/src/main/res/drawable/ic_draft.xml create mode 100644 app/src/main/res/drawable/shape_beta_badge.xml create mode 100644 app/src/main/res/layout/badge_beta.xml create mode 100644 app/src/main/res/layout/fragment_dashboard.xml create mode 100644 app/src/main/res/layout/list_dashboard_activity.xml diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml index 8c59fa31..d216c01c 100644 --- a/.woodpecker/build.yml +++ b/.woodpecker/build.yml @@ -1,4 +1,4 @@ -pipeline: +steps: build: image: alvrme/alpine-android:android-32-jdk17 commands: diff --git a/.woodpecker/check.yml b/.woodpecker/check.yml index 4d3da82a..a5eefc89 100644 --- a/.woodpecker/check.yml +++ b/.woodpecker/check.yml @@ -1,4 +1,4 @@ -pipeline: +steps: author-header: image: qwerty287/woodpecker-regex-check group: check diff --git a/.woodpecker/finish.yml b/.woodpecker/finish.yml index 7202f32d..035661c5 100644 --- a/.woodpecker/finish.yml +++ b/.woodpecker/finish.yml @@ -6,7 +6,7 @@ depends_on: run_on: [ success, failure ] skip_clone: true -pipeline: +steps: discord: image: appleboy/drone-discord settings: diff --git a/.woodpecker/locale.yml b/.woodpecker/locale.yml index 1ded0156..31a94fc0 100644 --- a/.woodpecker/locale.yml +++ b/.woodpecker/locale.yml @@ -1,4 +1,4 @@ -pipeline: +steps: prepare: image: alpine commands: diff --git a/README.md b/README.md index cd1dce26..bc8be288 100644 --- a/README.md +++ b/README.md @@ -4,29 +4,27 @@ # GitNex - Android client for Forgejo and Gitea -GitNex is a free/paid, open-source Android client for Git repository management tool Forgejo and Gitea. +GitNex is a free/paid, open-source Android client for the Git repository management tools Forgejo and Gitea. -GitNex is licensed under GPLv3 License. See the LICENSE file for the full license text. **No trackers are used** and source code is available here for -anyone to audit. +GitNex is licensed under the GPLv3 License. Please refer to the LICENSE file for the full text of the license. **No trackers are used**, and the source code is available here for anyone to audit. ## Downloads [Get it on F-Droid](https://f-droid.org/en/packages/org.mian.gitnex/) [Get it on Google Play](https://play.google.com/store/apps/details?id=org.mian.gitnex.pro) -[Download builds and releases](https://cloud.swatian.com/s/DN7E5xxtaw4fRbE) +[Download builds and releases](https://cloud.swatian.com/s/WS4k3seXnmfQppo) ## Note about Forgejo and Gitea version -Please make sure that you are on latest stable release or later for better app experience. +Please make sure that you are on the latest stable release or later for a better app experience. -Check the versions [compatibility page](https://codeberg.org/gitnex/GitNex/wiki/Compatibility) which lists all the supported versions with -compatibility ratio. +Check the version [compatibility page](https://codeberg.org/gitnex/GitNex/wiki/Compatibility), which lists all the supported versions along with their compatibility ratios. ## Build from source -Option 1 - Download the source code, open it in Android Studio and build it there. +Option 1 - Download the source code, open it in Android Studio, and build it there. -Option 2 - Open terminal(Linux) and cd to the project dir. Run `./gradlew assembleFree`. +Option 2 - Open the terminal (Linux) and navigate to the project directory. Then, run `./gradlew assembleFree`. ## Features @@ -48,10 +46,9 @@ Option 2 - Open terminal(Linux) and cd to the project dir. Run `./gradlew assemb ## Translation -Help us translate GitNex to your native language. +Help us translate GitNex into your native language. -We use [Crowdin](https://crowdin.com/project/gitnex) for translation. If your language is not listed, please -request [here](https://codeberg.org/gitnex/GitNex/issues) to add it to the project. +We use [Crowdin](https://crowdin.com/project/gitnex) for translations. If your language is not listed, please request to add it to the project [here](https://codeberg.org/gitnex/GitNex/issues). **Link: https://crowdin.com/project/GitNex** @@ -75,7 +72,7 @@ request [here](https://codeberg.org/gitnex/GitNex/issues) to add it to the proje ## Thanks -Thanks to all the open source libraries, contributors and donators. +Thanks to all the open source libraries, contributors, and donors. #### Open source libraries diff --git a/app/build.gradle b/app/build.gradle index aa97c4a8..ccbdcdbc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -54,16 +54,16 @@ configurations { } dependencies { - def lifecycle_version = '2.6.1' + def lifecycle_version = '2.6.2' def markwon_version = '4.6.2' def work_version = '2.8.1' def acra = '5.9.7' implementation fileTree(include: ['*.jar'], dir: 'libs') implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'com.google.android.material:material:1.11.0-alpha01' - implementation 'androidx.compose.material3:material3:1.2.0-alpha03' - implementation 'androidx.compose.material3:material3-window-size-class:1.2.0-alpha03' + implementation 'com.google.android.material:material:1.11.0-alpha02' + implementation 'androidx.compose.material3:material3:1.2.0-alpha07' + implementation 'androidx.compose.material3:material3-window-size-class:1.2.0-alpha07' implementation 'androidx.viewpager2:viewpager2:1.1.0-beta02' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation "androidx.legacy:legacy-support-v4:1.0.0" @@ -111,7 +111,7 @@ dependencies { coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.1.5" implementation 'androidx.biometric:biometric:1.1.0' implementation 'com.github.chrisvest:stormpot:2.4.2' - implementation 'androidx.browser:browser:1.5.0' + implementation 'androidx.browser:browser:1.6.0' implementation 'com.google.android.flexbox:flexbox:3.0.0' implementation('org.codeberg.gitnex:tea4j-autodeploy:65f700d036') { exclude module: 'org.apache.oltu.oauth2.common' diff --git a/app/src/main/java/org/mian/gitnex/activities/DeepLinksActivity.java b/app/src/main/java/org/mian/gitnex/activities/DeepLinksActivity.java index 7300363c..ce298321 100644 --- a/app/src/main/java/org/mian/gitnex/activities/DeepLinksActivity.java +++ b/app/src/main/java/org/mian/gitnex/activities/DeepLinksActivity.java @@ -101,7 +101,7 @@ public class DeepLinksActivity extends BaseActivity { accountFound = true; - AppUtil.switchToAccount(ctx, userAccount, true); + AppUtil.switchToAccount(ctx, userAccount, false); break; } } diff --git a/app/src/main/java/org/mian/gitnex/activities/IssueDetailActivity.java b/app/src/main/java/org/mian/gitnex/activities/IssueDetailActivity.java index b842d9a0..2e36c300 100644 --- a/app/src/main/java/org/mian/gitnex/activities/IssueDetailActivity.java +++ b/app/src/main/java/org/mian/gitnex/activities/IssueDetailActivity.java @@ -700,6 +700,26 @@ public class IssueDetailActivity extends BaseActivity viewBinding.issuePrState, ColorStateList.valueOf( ctx.getResources().getColor(R.color.iconIssuePrClosedColor, null))); + } else if (issue.getIssue().getTitle().contains("[WIP]") + || issue.getIssue().getTitle().contains("[wip]")) { // draft + + viewBinding.issuePrState.setImageResource(R.drawable.ic_draft); + ImageViewCompat.setImageTintList( + viewBinding.issuePrState, + ColorStateList.valueOf( + ctx.getResources().getColor(R.color.colorWhite, null))); + viewBinding.issuePrState.setBackgroundResource(R.drawable.shape_draft_release); + viewBinding.issuePrState.setPadding( + (int) ctx.getResources().getDimension(R.dimen.dimen4dp), + (int) ctx.getResources().getDimension(R.dimen.dimen2dp), + (int) ctx.getResources().getDimension(R.dimen.dimen4dp), + (int) ctx.getResources().getDimension(R.dimen.dimen2dp)); + + viewBinding.toolbarTitle.setPadding( + (int) ctx.getResources().getDimension(R.dimen.dimen12dp), + (int) ctx.getResources().getDimension(R.dimen.dimen0dp), + (int) ctx.getResources().getDimension(R.dimen.dimen0dp), + (int) ctx.getResources().getDimension(R.dimen.dimen0dp)); } else { // open viewBinding.issuePrState.setImageResource(R.drawable.ic_pull_request); @@ -1034,7 +1054,7 @@ public class IssueDetailActivity extends BaseActivity RetrofitClient.getApiInterface(this) .repoGetPullRequest(repoOwner, repoName, (long) issueIndex) .enqueue( - new Callback() { + new Callback<>() { @Override public void onResponse( diff --git a/app/src/main/java/org/mian/gitnex/activities/LoginActivity.java b/app/src/main/java/org/mian/gitnex/activities/LoginActivity.java index db8e04db..0930c86b 100644 --- a/app/src/main/java/org/mian/gitnex/activities/LoginActivity.java +++ b/app/src/main/java/org/mian/gitnex/activities/LoginActivity.java @@ -639,7 +639,9 @@ public class LoginActivity extends BaseActivity { final String credential = Credentials.basic(loginUid, loginPass, StandardCharsets.UTF_8); CreateAccessTokenOption createUserToken = new CreateAccessTokenOption().name(tokenName); - if (giteaVersion.higherOrEqual("1.19.0")) { + if (giteaVersion.higherOrEqual("1.20.0")) { + createUserToken.addScopesItem("all"); + } else if (giteaVersion.less("1.20.0") && (giteaVersion.higherOrEqual("1.19.0"))) { createUserToken.addScopesItem("all"); createUserToken.addScopesItem("sudo"); } diff --git a/app/src/main/java/org/mian/gitnex/activities/MainActivity.java b/app/src/main/java/org/mian/gitnex/activities/MainActivity.java index 51d45e6e..b5464918 100644 --- a/app/src/main/java/org/mian/gitnex/activities/MainActivity.java +++ b/app/src/main/java/org/mian/gitnex/activities/MainActivity.java @@ -39,6 +39,7 @@ import org.mian.gitnex.databinding.ActivityMainBinding; import org.mian.gitnex.fragments.AdministrationFragment; import org.mian.gitnex.fragments.BottomSheetDraftsFragment; import org.mian.gitnex.fragments.BottomSheetMyIssuesFilterFragment; +import org.mian.gitnex.fragments.DashboardFragment; import org.mian.gitnex.fragments.DraftsFragment; import org.mian.gitnex.fragments.ExploreFragment; import org.mian.gitnex.fragments.MostVisitedReposFragment; @@ -150,6 +151,8 @@ public class MainActivity extends BaseActivity toolbarTitle.setText(getResources().getString(R.string.pageTitleAdministration)); } else if (fragmentById instanceof MyIssuesFragment) { toolbarTitle.setText(getResources().getString(R.string.navMyIssues)); + } else if (fragmentById instanceof DashboardFragment) { + toolbarTitle.setText(getResources().getString(R.string.dashboard)); } getNotificationsCount(); @@ -161,6 +164,11 @@ public class MainActivity extends BaseActivity Menu menu = navigationView.getMenu(); navNotifications = menu.findItem(R.id.nav_notifications); + MenuItem navDashboard = menu.findItem(R.id.nav_dashboard); + + navDashboard.getActionView().findViewById(R.id.betaBadge).setVisibility(View.VISIBLE); + TextView dashboardBetaView = navDashboard.getActionView().findViewById(R.id.betaBadge); + dashboardBetaView.setText(R.string.beta); navigationView .getViewTreeObserver() @@ -296,6 +304,10 @@ public class MainActivity extends BaseActivity if (getAccount().requiresVersion("1.14.0")) { navigationView.getMenu().findItem(R.id.nav_my_issues).setVisible(true); } + + if (getAccount().requiresVersion("1.20.0")) { + navigationView.getMenu().findItem(R.id.nav_dashboard).setVisible(true); + } } @Override @@ -489,7 +501,14 @@ public class MainActivity extends BaseActivity .commit(); navigationView.setCheckedItem(R.id.nav_notes); break; - + case 11: + toolbarTitle.setText(getResources().getString(R.string.dashboard)); + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.fragment_container, new DashboardFragment()) + .commit(); + navigationView.setCheckedItem(R.id.nav_dashboard); + break; default: toolbarTitle.setText(getResources().getString(R.string.navMyRepos)); getSupportFragmentManager() @@ -710,6 +729,13 @@ public class MainActivity extends BaseActivity .beginTransaction() .replace(R.id.fragment_container, new NotesFragment()) .commit(); + } else if (id == R.id.nav_dashboard) { + + toolbarTitle.setText(getResources().getString(R.string.dashboard)); + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.fragment_container, new DashboardFragment()) + .commit(); } drawer.closeDrawer(GravityCompat.START); diff --git a/app/src/main/java/org/mian/gitnex/activities/RepoStargazersActivity.java b/app/src/main/java/org/mian/gitnex/activities/RepoStargazersActivity.java index 0b36c9c6..c9f8b315 100644 --- a/app/src/main/java/org/mian/gitnex/activities/RepoStargazersActivity.java +++ b/app/src/main/java/org/mian/gitnex/activities/RepoStargazersActivity.java @@ -1,11 +1,12 @@ package org.mian.gitnex.activities; import android.os.Bundle; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; import android.view.View; -import android.widget.GridView; -import android.widget.ImageView; -import android.widget.ProgressBar; -import android.widget.TextView; +import android.view.inputmethod.EditorInfo; +import androidx.appcompat.widget.SearchView; import androidx.lifecycle.ViewModelProvider; import org.mian.gitnex.R; import org.mian.gitnex.adapters.UserGridAdapter; @@ -18,37 +19,29 @@ import org.mian.gitnex.viewmodels.RepoStargazersViewModel; */ public class RepoStargazersActivity extends BaseActivity { - private TextView noDataStargazers; private View.OnClickListener onClickListener; private UserGridAdapter adapter; - private GridView mGridView; - private ProgressBar mProgressBar; - private RepositoryContext repository; + private ActivityRepoStargazersBinding activityRepoStargazersBinding; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - ActivityRepoStargazersBinding activityRepoStargazersBinding = - ActivityRepoStargazersBinding.inflate(getLayoutInflater()); + activityRepoStargazersBinding = ActivityRepoStargazersBinding.inflate(getLayoutInflater()); setContentView(activityRepoStargazersBinding.getRoot()); - ImageView closeActivity = activityRepoStargazersBinding.close; - TextView toolbarTitle = activityRepoStargazersBinding.toolbarTitle; - noDataStargazers = activityRepoStargazersBinding.noDataStargazers; - mGridView = activityRepoStargazersBinding.gridView; - mProgressBar = activityRepoStargazersBinding.progressBar; + setSupportActionBar(activityRepoStargazersBinding.toolbar); repository = RepositoryContext.fromIntent(getIntent()); final String repoOwner = repository.getOwner(); final String repoName = repository.getName(); initCloseListener(); - closeActivity.setOnClickListener(onClickListener); + activityRepoStargazersBinding.close.setOnClickListener(onClickListener); - toolbarTitle.setText(R.string.repoStargazersInMenu); + activityRepoStargazersBinding.toolbarTitle.setText(R.string.repoStargazersInMenu); fetchDataAsync(repoOwner, repoName); } @@ -67,16 +60,18 @@ public class RepoStargazersActivity extends BaseActivity { if (adapter.getCount() > 0) { - mGridView.setAdapter(adapter); - noDataStargazers.setVisibility(View.GONE); + activityRepoStargazersBinding.gridView.setAdapter(adapter); + activityRepoStargazersBinding.noDataStargazers.setVisibility( + View.GONE); } else { adapter.notifyDataSetChanged(); - mGridView.setAdapter(adapter); - noDataStargazers.setVisibility(View.VISIBLE); + activityRepoStargazersBinding.gridView.setAdapter(adapter); + activityRepoStargazersBinding.noDataStargazers.setVisibility( + View.VISIBLE); } - mProgressBar.setVisibility(View.GONE); + activityRepoStargazersBinding.progressBar.setVisibility(View.GONE); }); } @@ -90,4 +85,36 @@ public class RepoStargazersActivity extends BaseActivity { super.onResume(); repository.checkAccountSwitch(this); } + + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + + final MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.search_menu, menu); + + MenuItem searchItem = menu.findItem(R.id.action_search); + SearchView searchView = (SearchView) searchItem.getActionView(); + searchView.setImeOptions(EditorInfo.IME_ACTION_DONE); + + searchView.setOnQueryTextListener( + new androidx.appcompat.widget.SearchView.OnQueryTextListener() { + + @Override + public boolean onQueryTextSubmit(String query) { + return true; + } + + @Override + public boolean onQueryTextChange(String newText) { + + if (activityRepoStargazersBinding.gridView.getAdapter() != null) { + adapter.getFilter().filter(newText); + } + + return false; + } + }); + + return true; + } } diff --git a/app/src/main/java/org/mian/gitnex/activities/RepoWatchersActivity.java b/app/src/main/java/org/mian/gitnex/activities/RepoWatchersActivity.java index a684b8bc..bec73fa3 100644 --- a/app/src/main/java/org/mian/gitnex/activities/RepoWatchersActivity.java +++ b/app/src/main/java/org/mian/gitnex/activities/RepoWatchersActivity.java @@ -1,11 +1,12 @@ package org.mian.gitnex.activities; import android.os.Bundle; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; import android.view.View; -import android.widget.GridView; -import android.widget.ImageView; -import android.widget.ProgressBar; -import android.widget.TextView; +import android.view.inputmethod.EditorInfo; +import androidx.appcompat.widget.SearchView; import androidx.lifecycle.ViewModelProvider; import org.mian.gitnex.R; import org.mian.gitnex.adapters.UserGridAdapter; @@ -18,12 +19,9 @@ import org.mian.gitnex.viewmodels.RepoWatchersViewModel; */ public class RepoWatchersActivity extends BaseActivity { - private TextView noDataWatchers; private View.OnClickListener onClickListener; private UserGridAdapter adapter; - private GridView mGridView; - private ProgressBar mProgressBar; - + private ActivityRepoWatchersBinding activityRepoWatchersBinding; private RepositoryContext repository; @Override @@ -31,24 +29,19 @@ public class RepoWatchersActivity extends BaseActivity { super.onCreate(savedInstanceState); - ActivityRepoWatchersBinding activityRepoWatchersBinding = - ActivityRepoWatchersBinding.inflate(getLayoutInflater()); + activityRepoWatchersBinding = ActivityRepoWatchersBinding.inflate(getLayoutInflater()); setContentView(activityRepoWatchersBinding.getRoot()); - ImageView closeActivity = activityRepoWatchersBinding.close; - TextView toolbarTitle = activityRepoWatchersBinding.toolbarTitle; - noDataWatchers = activityRepoWatchersBinding.noDataWatchers; - mGridView = activityRepoWatchersBinding.gridView; - mProgressBar = activityRepoWatchersBinding.progressBar; + setSupportActionBar(activityRepoWatchersBinding.toolbar); repository = RepositoryContext.fromIntent(getIntent()); final String repoOwner = repository.getOwner(); final String repoName = repository.getName(); initCloseListener(); - closeActivity.setOnClickListener(onClickListener); + activityRepoWatchersBinding.close.setOnClickListener(onClickListener); - toolbarTitle.setText(R.string.repoWatchersInMenu); + activityRepoWatchersBinding.toolbarTitle.setText(R.string.repoWatchersInMenu); fetchDataAsync(repoOwner, repoName); } @@ -67,16 +60,17 @@ public class RepoWatchersActivity extends BaseActivity { if (adapter.getCount() > 0) { - mGridView.setAdapter(adapter); - noDataWatchers.setVisibility(View.GONE); + activityRepoWatchersBinding.gridView.setAdapter(adapter); + activityRepoWatchersBinding.noDataWatchers.setVisibility(View.GONE); } else { adapter.notifyDataSetChanged(); - mGridView.setAdapter(adapter); - noDataWatchers.setVisibility(View.VISIBLE); + activityRepoWatchersBinding.gridView.setAdapter(adapter); + activityRepoWatchersBinding.noDataWatchers.setVisibility( + View.VISIBLE); } - mProgressBar.setVisibility(View.GONE); + activityRepoWatchersBinding.progressBar.setVisibility(View.GONE); }); } @@ -90,4 +84,35 @@ public class RepoWatchersActivity extends BaseActivity { super.onResume(); repository.checkAccountSwitch(this); } + + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + + final MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.search_menu, menu); + + MenuItem searchItem = menu.findItem(R.id.action_search); + SearchView searchView = (SearchView) searchItem.getActionView(); + searchView.setImeOptions(EditorInfo.IME_ACTION_DONE); + + searchView.setOnQueryTextListener( + new androidx.appcompat.widget.SearchView.OnQueryTextListener() { + + @Override + public boolean onQueryTextSubmit(String query) { + return true; + } + + @Override + public boolean onQueryTextChange(String newText) { + + if (activityRepoWatchersBinding.gridView.getAdapter() != null) { + adapter.getFilter().filter(newText); + } + return false; + } + }); + + return true; + } } diff --git a/app/src/main/java/org/mian/gitnex/activities/SettingsGeneralActivity.java b/app/src/main/java/org/mian/gitnex/activities/SettingsGeneralActivity.java index 3a82e756..29590950 100644 --- a/app/src/main/java/org/mian/gitnex/activities/SettingsGeneralActivity.java +++ b/app/src/main/java/org/mian/gitnex/activities/SettingsGeneralActivity.java @@ -93,6 +93,9 @@ public class SettingsGeneralActivity extends BaseActivity { } else if (homeScreenSelectedChoice == 10) { viewBinding.homeScreenSelected.setText(getResources().getString(R.string.navNotes)); + } else if (homeScreenSelectedChoice == 11) { + + viewBinding.homeScreenSelected.setText(getResources().getString(R.string.dashboard)); } viewBinding.homeScreenFrame.setOnClickListener( diff --git a/app/src/main/java/org/mian/gitnex/adapters/DashboardAdapter.java b/app/src/main/java/org/mian/gitnex/adapters/DashboardAdapter.java new file mode 100644 index 00000000..0091b0f0 --- /dev/null +++ b/app/src/main/java/org/mian/gitnex/adapters/DashboardAdapter.java @@ -0,0 +1,892 @@ +package org.mian.gitnex.adapters; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.os.Handler; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.core.content.res.ResourcesCompat; +import androidx.core.text.HtmlCompat; +import androidx.recyclerview.widget.RecyclerView; +import com.vdurmont.emoji.EmojiParser; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import org.apache.commons.lang3.StringUtils; +import org.gitnex.tea4j.v2.models.Activity; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.mian.gitnex.R; +import org.mian.gitnex.activities.IssueDetailActivity; +import org.mian.gitnex.activities.ProfileActivity; +import org.mian.gitnex.activities.RepoDetailActivity; +import org.mian.gitnex.clients.PicassoService; +import org.mian.gitnex.helpers.AppUtil; +import org.mian.gitnex.helpers.ClickListener; +import org.mian.gitnex.helpers.RoundedTransformation; +import org.mian.gitnex.helpers.TimeHelper; +import org.mian.gitnex.helpers.TinyDB; +import org.mian.gitnex.helpers.contexts.IssueContext; +import org.mian.gitnex.helpers.contexts.RepositoryContext; + +/** + * @author M M Arif + */ +public class DashboardAdapter extends RecyclerView.Adapter { + + private final Context context; + TinyDB tinyDb; + private List activityList; + private OnLoadMoreListener loadMoreListener; + private boolean isLoading = false, isMoreDataAvailable = true; + private Intent intent; + public boolean isUserOrg = false; + + public DashboardAdapter(List dataList, Context ctx) { + this.context = ctx; + this.activityList = dataList; + this.tinyDb = TinyDB.getInstance(ctx); + } + + @NonNull @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + LayoutInflater inflater = LayoutInflater.from(context); + return new DashboardAdapter.DashboardHolder( + inflater.inflate(R.layout.list_dashboard_activity, parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { + if (position >= getItemCount() - 1 + && isMoreDataAvailable + && !isLoading + && loadMoreListener != null) { + isLoading = true; + loadMoreListener.onLoadMore(); + } + + ((DashboardAdapter.DashboardHolder) holder).bindData(activityList.get(position)); + } + + @Override + public int getItemViewType(int position) { + return position; + } + + @Override + public int getItemCount() { + return activityList.size(); + } + + public void setMoreDataAvailable(boolean moreDataAvailable) { + isMoreDataAvailable = moreDataAvailable; + if (!isMoreDataAvailable) { + loadMoreListener.onLoadFinished(); + } + } + + @SuppressLint("NotifyDataSetChanged") + public void notifyDataChanged() { + notifyDataSetChanged(); + isLoading = false; + loadMoreListener.onLoadFinished(); + } + + public void setLoadMoreListener(OnLoadMoreListener loadMoreListener) { + this.loadMoreListener = loadMoreListener; + } + + public void updateList(List list) { + activityList = list; + notifyDataChanged(); + } + + public interface OnLoadMoreListener { + + void onLoadMore(); + + void onLoadFinished(); + } + + class DashboardHolder extends RecyclerView.ViewHolder { + + private final ImageView userAvatar; + private final TextView typeDetails; + private final TextView createdTime; + private final ImageView typeIcon; + private final TextView dashText; + private final LinearLayout dashTextFrame; + + private Activity activityObject; + + DashboardHolder(View itemView) { + + super(itemView); + userAvatar = itemView.findViewById(R.id.user_avatar); + typeDetails = itemView.findViewById(R.id.type_details); + typeIcon = itemView.findViewById(R.id.type_icon); + createdTime = itemView.findViewById(R.id.created_time); + dashText = itemView.findViewById(R.id.text); + dashTextFrame = itemView.findViewById(R.id.dash_text_frame); + + new Handler() + .postDelayed( + () -> { + if (!AppUtil.checkGhostUsers( + activityObject.getActUser().getLogin())) { + + userAvatar.setOnLongClickListener( + loginId -> { + AppUtil.copyToClipboard( + context, + activityObject.getActUser().getLogin(), + context.getString( + R.string.copyLoginIdToClipBoard, + activityObject + .getActUser() + .getLogin())); + return true; + }); + + userAvatar.setOnClickListener( + v -> { + intent = new Intent(context, ProfileActivity.class); + intent.putExtra( + "username", + activityObject.getActUser().getLogin()); + context.startActivity(intent); + }); + } + + if (activityObject.getOpType().equalsIgnoreCase("create_repo") + || activityObject + .getOpType() + .equalsIgnoreCase("rename_repo") + || activityObject.getOpType().equalsIgnoreCase("star_repo") + || activityObject + .getOpType() + .equalsIgnoreCase("transfer_repo")) { + + itemView.setOnClickListener( + v -> { + Context context = v.getContext(); + RepositoryContext repo = + new RepositoryContext( + activityObject.getRepo(), context); + repo.saveToDB(context); + Intent intent = + repo.getIntent( + context, RepoDetailActivity.class); + if (isUserOrg) { + intent.putExtra("openedFromUserOrg", true); + } + context.startActivity(intent); + }); + } + + if (activityObject.getOpType().equalsIgnoreCase("create_issue") + || activityObject + .getOpType() + .equalsIgnoreCase("comment_issue") + || activityObject + .getOpType() + .equalsIgnoreCase("close_issue") + || activityObject + .getOpType() + .equalsIgnoreCase("reopen_issue")) { + + String[] parts = + activityObject.getRepo().getFullName().split("/"); + final String repoOwner = parts[0]; + final String repoName = parts[1]; + + RepositoryContext repo = + new RepositoryContext(repoOwner, repoName, context); + + String[] contentParts = + activityObject.getContent().split("\\|"); + String id = contentParts[0]; + + Intent intentIssueDetail = + new IssueContext(repo, Integer.parseInt(id), "open") + .getIntent(context, IssueDetailActivity.class); + intentIssueDetail.putExtra("openedFromLink", "true"); + + itemView.setOnClickListener( + v -> { + repo.saveToDB(context); + context.startActivity(intentIssueDetail); + }); + } + + if (activityObject + .getOpType() + .equalsIgnoreCase("create_pull_request") + || activityObject + .getOpType() + .equalsIgnoreCase("close_pull_request") + || activityObject + .getOpType() + .equalsIgnoreCase("reopen_pull_request") + || activityObject + .getOpType() + .equalsIgnoreCase("approve_pull_request") + || activityObject + .getOpType() + .equalsIgnoreCase("reject_pull_request") + || activityObject + .getOpType() + .equalsIgnoreCase("comment_pull") + || activityObject + .getOpType() + .equalsIgnoreCase("auto_merge_pull_request") + || activityObject + .getOpType() + .equalsIgnoreCase("merge_pull_request")) { + + String[] parts = + activityObject.getRepo().getFullName().split("/"); + final String repoOwner = parts[0]; + final String repoName = parts[1]; + + RepositoryContext repo = + new RepositoryContext(repoOwner, repoName, context); + + String[] contentParts = + activityObject.getContent().split("\\|"); + String id = contentParts[0]; + + Intent intentIssueDetail = + new IssueContext(repo, Integer.parseInt(id), "open") + .getIntent(context, IssueDetailActivity.class); + intentIssueDetail.putExtra("openedFromLink", "true"); + + itemView.setOnClickListener( + v -> { + repo.saveToDB(context); + context.startActivity(intentIssueDetail); + }); + } + + if (activityObject.getOpType().equalsIgnoreCase("commit_repo")) { + + if (activityObject.getContent().isEmpty()) { + + itemView.setOnClickListener( + v -> { + RepositoryContext repo = + new RepositoryContext( + activityObject.getRepo(), + context); + + Intent repoIntent = + new Intent( + context, + RepoDetailActivity.class); + repoIntent.putExtra("goToSection", "yes"); + repoIntent.putExtra( + "goToSectionType", "commitsList"); + repoIntent.putExtra( + "branchName", + activityObject + .getRefName() + .substring( + activityObject + .getRefName() + .lastIndexOf( + "/") + + 1) + .trim()); + + repo.saveToDB(context); + repoIntent.putExtra( + RepositoryContext.INTENT_EXTRA, repo); + + context.startActivity(repoIntent); + }); + } + } + + if (activityObject.getOpType().equalsIgnoreCase("publish_release") + || activityObject + .getOpType() + .equalsIgnoreCase("push_tag")) { + + itemView.setOnClickListener( + v -> { + RepositoryContext repo = + new RepositoryContext( + activityObject.getRepo(), context); + + Intent repoIntent = + new Intent( + context, RepoDetailActivity.class); + repoIntent.putExtra("goToSection", "yes"); + repoIntent.putExtra("goToSectionType", "releases"); + repoIntent.putExtra( + "releaseTagName", + activityObject + .getRefName() + .substring( + activityObject + .getRefName() + .lastIndexOf( + "/") + + 1) + .trim()); + + repo.saveToDB(context); + repoIntent.putExtra( + RepositoryContext.INTENT_EXTRA, repo); + + context.startActivity(repoIntent); + }); + } + }, + 200); + } + + void bindData(Activity activity) { + + this.activityObject = activity; + Locale locale = context.getResources().getConfiguration().locale; + + int imgRadius = AppUtil.getPixelsFromDensity(context, 3); + + PicassoService.getInstance(context) + .get() + .load(activity.getActUser().getAvatarUrl()) + .placeholder(R.drawable.loader_animated) + .transform(new RoundedTransformation(imgRadius, 0)) + .resize(120, 120) + .centerCrop() + .into(userAvatar); + + String username = + "" + + activity.getActUser().getLogin() + + ""; + + String headerString = ""; + String typeString = ""; + + if (activity.getOpType().contains("repo")) { + + if (activity.getOpType().equalsIgnoreCase("create_repo")) { + + headerString = + "" + + activity.getRepo().getFullName() + + ""; + typeString = "created repository"; + typeIcon.setImageResource(R.drawable.ic_repo); + } else if (activity.getOpType().equalsIgnoreCase("rename_repo")) { + + headerString = + "" + + activity.getRepo().getFullName() + + ""; + typeString = "renamed repository from " + activity.getContent() + " to"; + typeIcon.setImageResource(R.drawable.ic_repo); + } else if (activity.getOpType().equalsIgnoreCase("star_repo")) { + + headerString = + "" + + activity.getRepo().getFullName() + + ""; + typeString = "starred"; + typeIcon.setImageResource(R.drawable.ic_star); + } else if (activity.getOpType().equalsIgnoreCase("transfer_repo")) { + + headerString = + "" + + activity.getRepo().getFullName() + + ""; + typeString = "transferred repository " + activity.getContent() + " to"; + typeIcon.setImageResource(R.drawable.ic_arrow_up); + } else if (activity.getOpType().equalsIgnoreCase("commit_repo")) { + + headerString = + "" + + activity.getRepo().getFullName() + + ""; + + if (activity.getContent().isEmpty()) { + String branch = + "" + + activity.getRefName() + .substring( + activity.getRefName().lastIndexOf("/") + 1) + .trim() + + ""; + typeString = "created branch " + branch + " in"; + } else { + String branch = + "" + + activity.getRefName() + .substring( + activity.getRefName().lastIndexOf("/") + 1) + .trim() + + ""; + typeString = "pushed to " + branch + " at"; + + JSONObject commitsObj = null; + try { + commitsObj = new JSONObject(activity.getContent()); + } catch (JSONException ignored) { + } + + JSONArray commitsShaArray = null; + try { + commitsShaArray = + Objects.requireNonNull(commitsObj).getJSONArray("Commits"); + } catch (JSONException ignored) { + } + + dashTextFrame.setVisibility(View.VISIBLE); + + dashTextFrame.setOrientation(LinearLayout.VERTICAL); + dashTextFrame.removeAllViews(); + + for (int i = 0; i < Objects.requireNonNull(commitsShaArray).length(); i++) { + + try { + + String timelineCommits = + "" + + StringUtils.substring( + String.valueOf(commitsShaArray.get(i)), + 9, + 19) + + ""; + + TextView dynamicCommitTv = new TextView(context); + dynamicCommitTv.setId(View.generateViewId()); + + dynamicCommitTv.setText( + HtmlCompat.fromHtml( + timelineCommits, HtmlCompat.FROM_HTML_MODE_LEGACY)); + + JSONObject sha1Obj = null; + try { + sha1Obj = (JSONObject) commitsShaArray.get(i); + } catch (JSONException ignored) { + } + + JSONObject finalSha1Obj = sha1Obj; + dynamicCommitTv.setOnClickListener( + v14 -> { + RepositoryContext repo = + new RepositoryContext( + activity.getRepo(), context); + + Intent repoIntent = + new Intent(context, RepoDetailActivity.class); + repoIntent.putExtra("goToSection", "yes"); + repoIntent.putExtra("goToSectionType", "commit"); + try { + assert finalSha1Obj != null; + repoIntent.putExtra( + "sha", (String) finalSha1Obj.get("Sha1")); + } catch (JSONException ignored) { + } + + repo.saveToDB(context); + repoIntent.putExtra( + RepositoryContext.INTENT_EXTRA, repo); + + context.startActivity(repoIntent); + }); + + dashTextFrame.setOrientation(LinearLayout.VERTICAL); + dashTextFrame.addView(dynamicCommitTv); + } catch (JSONException ignored) { + } + } + } + typeIcon.setImageResource(R.drawable.ic_commit); + } + } else if (activity.getOpType().contains("issue")) { + + String id; + String content; + String[] contentParts = activity.getContent().split("\\|"); + if (contentParts.length > 1) { + id = contentParts[0]; + content = contentParts[1]; + dashTextFrame.setVisibility(View.VISIBLE); + dashText.setText(EmojiParser.parseToUnicode(content)); + } else { + id = contentParts[0]; + } + + if (activity.getOpType().equalsIgnoreCase("create_issue")) { + + headerString = + "" + + activity.getRepo().getFullName() + + context.getResources().getString(R.string.hash) + + id + + ""; + typeString = "opened issue"; + typeIcon.setImageResource(R.drawable.ic_issue); + } else if (activity.getOpType().equalsIgnoreCase("comment_issue")) { + + headerString = + "" + + activity.getRepo().getFullName() + + context.getResources().getString(R.string.hash) + + id + + ""; + typeString = "commented on issue"; + typeIcon.setImageResource(R.drawable.ic_comment); + } else if (activity.getOpType().equalsIgnoreCase("close_issue")) { + + headerString = + "" + + activity.getRepo().getFullName() + + context.getResources().getString(R.string.hash) + + id + + ""; + typeString = "closed issue"; + typeIcon.setImageResource(R.drawable.ic_issue_closed); + } else if (activity.getOpType().equalsIgnoreCase("reopen_issue")) { + + headerString = + "" + + activity.getRepo().getFullName() + + context.getResources().getString(R.string.hash) + + id + + ""; + typeString = "reopened issue"; + typeIcon.setImageResource(R.drawable.ic_reopen); + } + } else if (activity.getOpType().contains("pull")) { + + String id; + String content; + String[] contentParts = activity.getContent().split("\\|"); + if (contentParts.length > 1) { + id = contentParts[0]; + content = contentParts[1]; + dashTextFrame.setVisibility(View.VISIBLE); + dashText.setText(EmojiParser.parseToUnicode(content)); + } else { + id = contentParts[0]; + } + + if (activity.getOpType().equalsIgnoreCase("create_pull_request")) { + + headerString = + "" + + activity.getRepo().getFullName() + + context.getResources().getString(R.string.hash) + + id + + ""; + typeString = "created pull request"; + typeIcon.setImageResource(R.drawable.ic_pull_request); + } else if (activity.getOpType().equalsIgnoreCase("close_pull_request")) { + + headerString = + "" + + activity.getRepo().getFullName() + + context.getResources().getString(R.string.hash) + + id + + ""; + typeString = "closed pull request"; + typeIcon.setImageResource(R.drawable.ic_issue_closed); + } else if (activity.getOpType().equalsIgnoreCase("reopen_pull_request")) { + + headerString = + "" + + activity.getRepo().getFullName() + + context.getResources().getString(R.string.hash) + + id + + ""; + typeString = "reopened pull request"; + typeIcon.setImageResource(R.drawable.ic_reopen); + } else if (activity.getOpType().equalsIgnoreCase("merge_pull_request")) { + + headerString = + "" + + activity.getRepo().getFullName() + + context.getResources().getString(R.string.hash) + + id + + ""; + typeString = "merged pull request"; + typeIcon.setImageResource(R.drawable.ic_pull_request); + } else if (activity.getOpType().equalsIgnoreCase("approve_pull_request")) { + + headerString = + "" + + activity.getRepo().getFullName() + + context.getResources().getString(R.string.hash) + + id + + ""; + typeString = "approved"; + typeIcon.setImageResource(R.drawable.ic_done); + } else if (activity.getOpType().equalsIgnoreCase("reject_pull_request")) { + + headerString = + "" + + activity.getRepo().getFullName() + + context.getResources().getString(R.string.hash) + + id + + ""; + typeString = "suggested changes for"; + typeIcon.setImageResource(R.drawable.ic_diff); + } else if (activity.getOpType().equalsIgnoreCase("comment_pull")) { + + headerString = + "" + + activity.getRepo().getFullName() + + context.getResources().getString(R.string.hash) + + id + + ""; + typeString = "commented on pull request"; + typeIcon.setImageResource(R.drawable.ic_comment); + } else if (activity.getOpType().equalsIgnoreCase("auto_merge_pull_request")) { + + headerString = + "" + + activity.getRepo().getFullName() + + context.getResources().getString(R.string.hash) + + id + + ""; + typeString = "automatically merged pull request"; + typeIcon.setImageResource(R.drawable.ic_issue_closed); + } + } else if (activity.getOpType().contains("branch")) { + + String content; + String[] contentParts = activity.getContent().split("\\|"); + if (contentParts.length > 1) { + content = contentParts[1]; + dashTextFrame.setVisibility(View.VISIBLE); + dashText.setText(EmojiParser.parseToUnicode(content)); + } + + if (activity.getOpType().equalsIgnoreCase("delete_branch")) { + + headerString = + "" + + activity.getRepo().getFullName() + + ""; + + String branch = + "" + + activity.getRefName() + .substring(activity.getRefName().lastIndexOf("/") + 1) + .trim() + + ""; + + typeString = "deleted branch " + branch + " at"; + typeIcon.setImageResource(R.drawable.ic_commit); + } + } else if (activity.getOpType().contains("tag")) { + + if (activity.getOpType().equalsIgnoreCase("push_tag")) { + + headerString = + "" + + activity.getRepo().getFullName() + + ""; + + String branch = + "" + + activity.getRefName() + .substring(activity.getRefName().lastIndexOf("/") + 1) + .trim() + + ""; + + typeString = "pushed tag " + branch + " to"; + typeIcon.setImageResource(R.drawable.ic_commit); + } else if (activity.getOpType().equalsIgnoreCase("delete_tag")) { + + headerString = + "" + + activity.getRepo().getFullName() + + ""; + + String branch = + "" + + activity.getRefName() + .substring(activity.getRefName().lastIndexOf("/") + 1) + .trim() + + ""; + + typeString = "deleted tag " + branch + " from"; + typeIcon.setImageResource(R.drawable.ic_commit); + } + } else if (activity.getOpType().contains("release")) { + + if (activity.getOpType().equalsIgnoreCase("publish_release")) { + + headerString = + "" + + activity.getRepo().getFullName() + + ""; + + String branch = + "" + + activity.getRefName() + .substring(activity.getRefName().lastIndexOf("/") + 1) + .trim() + + ""; + + typeString = "released " + branch + " at"; + typeIcon.setImageResource(R.drawable.ic_tag); + } + } else if (activity.getOpType().contains("mirror")) { + + if (activity.getOpType().equalsIgnoreCase("mirror_sync_push")) { + + headerString = + "" + + activity.getRepo().getFullName() + + ""; + + typeString = "synced commits to " + headerString + " at"; + typeIcon.setImageResource(R.drawable.ic_tag); + } else if (activity.getOpType().equalsIgnoreCase("mirror_sync_create")) { + + headerString = + "" + + activity.getRepo().getFullName() + + ""; + + typeString = "synced new reference " + headerString + " to"; + typeIcon.setImageResource(R.drawable.ic_tag); + } else if (activity.getOpType().equalsIgnoreCase("mirror_sync_delete")) { + + headerString = + "" + + activity.getRepo().getFullName() + + ""; + + typeString = "synced and deleted reference " + headerString + " at"; + typeIcon.setImageResource(R.drawable.ic_tag); + } + } else { + dashTextFrame.setVisibility(View.GONE); + dashText.setVisibility(View.GONE); + } + + typeDetails.setText( + HtmlCompat.fromHtml( + username + " " + typeString + " " + headerString, + HtmlCompat.FROM_HTML_MODE_LEGACY)); + + this.createdTime.setText(TimeHelper.formatTime(activity.getCreated(), locale)); + this.createdTime.setOnClickListener( + new ClickListener( + TimeHelper.customDateFormatForToastDateFormat(activity.getCreated()), + context)); + } + } +} diff --git a/app/src/main/java/org/mian/gitnex/adapters/ExploreIssuesAdapter.java b/app/src/main/java/org/mian/gitnex/adapters/ExploreIssuesAdapter.java index df8be3bb..0b5cb5a4 100644 --- a/app/src/main/java/org/mian/gitnex/adapters/ExploreIssuesAdapter.java +++ b/app/src/main/java/org/mian/gitnex/adapters/ExploreIssuesAdapter.java @@ -170,26 +170,31 @@ public class ExploreIssuesAdapter extends RecyclerView.Adapter { + Intent intent = + new Intent(context, ProfileActivity.class); + intent.putExtra( + "username", issue.getUser().getLogin()); + context.startActivity(intent); + }); + + issueAssigneeAvatar.setOnLongClickListener( + loginId -> { + AppUtil.copyToClipboard( + context, + issue.getUser().getLogin(), + context.getString( + R.string.copyLoginIdToClipBoard, + issue.getUser().getLogin())); + return true; + }); + } }, 200); - - issueAssigneeAvatar.setOnClickListener( - v -> { - Intent intent = new Intent(context, ProfileActivity.class); - intent.putExtra("username", issue.getUser().getLogin()); - context.startActivity(intent); - }); - - issueAssigneeAvatar.setOnLongClickListener( - loginId -> { - AppUtil.copyToClipboard( - context, - issue.getUser().getLogin(), - context.getString( - R.string.copyLoginIdToClipBoard, - issue.getUser().getLogin())); - return true; - }); } void bindData(Issue issue) { diff --git a/app/src/main/java/org/mian/gitnex/adapters/PullRequestsAdapter.java b/app/src/main/java/org/mian/gitnex/adapters/PullRequestsAdapter.java index 2fa28bc5..b29390de 100644 --- a/app/src/main/java/org/mian/gitnex/adapters/PullRequestsAdapter.java +++ b/app/src/main/java/org/mian/gitnex/adapters/PullRequestsAdapter.java @@ -3,6 +3,7 @@ package org.mian.gitnex.adapters; import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; +import android.content.res.ColorStateList; import android.graphics.Color; import android.graphics.Typeface; import android.os.Handler; @@ -18,6 +19,7 @@ import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; import androidx.core.content.res.ResourcesCompat; import androidx.core.text.HtmlCompat; +import androidx.core.widget.ImageViewCompat; import androidx.recyclerview.widget.RecyclerView; import com.amulyakhare.textdrawable.TextDrawable; import com.vdurmont.emoji.EmojiParser; @@ -106,6 +108,7 @@ public class PullRequestsAdapter extends RecyclerView.Adapter"; + if (pullRequest.getTitle().contains("[WIP]") + || pullRequest.getTitle().contains("[wip]")) { + this.issuePrState.setVisibility(View.VISIBLE); + this.issuePrState.setImageResource(R.drawable.ic_draft); + ImageViewCompat.setImageTintList( + this.issuePrState, + ColorStateList.valueOf( + context.getResources().getColor(R.color.colorWhite, null))); + this.issuePrState.setBackgroundResource(R.drawable.shape_draft_release); + this.issuePrState.setPadding( + (int) context.getResources().getDimension(R.dimen.dimen4dp), + (int) context.getResources().getDimension(R.dimen.dimen0dp), + (int) context.getResources().getDimension(R.dimen.dimen4dp), + (int) context.getResources().getDimension(R.dimen.dimen0dp)); + this.prTitle.setPadding( + (int) context.getResources().getDimension(R.dimen.dimen12dp), + (int) context.getResources().getDimension(R.dimen.dimen0dp), + (int) context.getResources().getDimension(R.dimen.dimen0dp), + (int) context.getResources().getDimension(R.dimen.dimen0dp)); + } else { + this.issuePrState.setVisibility(View.GONE); + } + this.prTitle.setText( HtmlCompat.fromHtml( prNumber_ + " " + EmojiParser.parseToUnicode(pullRequest.getTitle()), diff --git a/app/src/main/java/org/mian/gitnex/fragments/BottomSheetReplyFragment.java b/app/src/main/java/org/mian/gitnex/fragments/BottomSheetReplyFragment.java index a05863f1..9b0eef65 100644 --- a/app/src/main/java/org/mian/gitnex/fragments/BottomSheetReplyFragment.java +++ b/app/src/main/java/org/mian/gitnex/fragments/BottomSheetReplyFragment.java @@ -314,7 +314,7 @@ public class BottomSheetReplyFragment extends BottomSheetDialogFragment { } draftsHint.setVisibility(View.VISIBLE); - valueAnimator.start(); + // valueAnimator.start(); } } diff --git a/app/src/main/java/org/mian/gitnex/fragments/DashboardFragment.java b/app/src/main/java/org/mian/gitnex/fragments/DashboardFragment.java new file mode 100644 index 00000000..10502f0a --- /dev/null +++ b/app/src/main/java/org/mian/gitnex/fragments/DashboardFragment.java @@ -0,0 +1,123 @@ +package org.mian.gitnex.fragments; + +import android.content.Context; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.LinearLayoutManager; +import java.util.ArrayList; +import java.util.List; +import org.gitnex.tea4j.v2.models.Activity; +import org.mian.gitnex.R; +import org.mian.gitnex.activities.BaseActivity; +import org.mian.gitnex.activities.MainActivity; +import org.mian.gitnex.adapters.DashboardAdapter; +import org.mian.gitnex.databinding.FragmentDashboardBinding; +import org.mian.gitnex.helpers.TinyDB; +import org.mian.gitnex.viewmodels.DashboardViewModel; + +/** + * @author M M Arif + */ +public class DashboardFragment extends Fragment { + + protected TinyDB tinyDB; + private DashboardViewModel dashboardViewModel; + private FragmentDashboardBinding binding; + private DashboardAdapter adapter; + private List activityList; + private int page = 1; + private String username; + + @Override + public View onCreateView( + @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + + binding = FragmentDashboardBinding.inflate(inflater, container, false); + + Context ctx = getContext(); + setHasOptionsMenu(true); + + ((MainActivity) requireActivity()) + .setActionBarTitle(getResources().getString(R.string.dashboard)); + + activityList = new ArrayList<>(); + + dashboardViewModel = new ViewModelProvider(this).get(DashboardViewModel.class); + + username = ((BaseActivity) requireActivity()).getAccount().getAccount().getUserName(); + + binding.recyclerView.setHasFixedSize(true); + binding.recyclerView.setLayoutManager(new LinearLayoutManager(ctx)); + + adapter = new DashboardAdapter(activityList, ctx); + + binding.pullToRefresh.setOnRefreshListener( + () -> + new Handler(Looper.getMainLooper()) + .postDelayed( + () -> { + activityList.clear(); + binding.pullToRefresh.setRefreshing(false); + binding.progressBar.setVisibility(View.VISIBLE); + fetchDataAsync(username); + }, + 250)); + + fetchDataAsync(username); + + return binding.getRoot(); + } + + @Override + public void onResume() { + super.onResume(); + // fetchDataAsync(username); + } + + private void fetchDataAsync(String username) { + + dashboardViewModel + .getActivitiesList(username, getContext(), binding) + .observe( + getViewLifecycleOwner(), + activityListMain -> { + adapter = new DashboardAdapter(activityListMain, getContext()); + adapter.setLoadMoreListener( + new DashboardAdapter.OnLoadMoreListener() { + + @Override + public void onLoadMore() { + + page += 1; + dashboardViewModel.loadMoreActivities( + username, page, getContext(), adapter, binding); + binding.progressBar.setVisibility(View.VISIBLE); + } + + @Override + public void onLoadFinished() { + + binding.progressBar.setVisibility(View.GONE); + } + }); + + if (adapter.getItemCount() > 0) { + binding.recyclerView.setAdapter(adapter); + binding.noData.setVisibility(View.GONE); + } else { + adapter.notifyDataChanged(); + binding.recyclerView.setAdapter(adapter); + binding.noData.setVisibility(View.VISIBLE); + } + + binding.progressBar.setVisibility(View.GONE); + }); + } +} diff --git a/app/src/main/java/org/mian/gitnex/viewmodels/DashboardViewModel.java b/app/src/main/java/org/mian/gitnex/viewmodels/DashboardViewModel.java new file mode 100644 index 00000000..e5efd68b --- /dev/null +++ b/app/src/main/java/org/mian/gitnex/viewmodels/DashboardViewModel.java @@ -0,0 +1,114 @@ +package org.mian.gitnex.viewmodels; + +import android.content.Context; +import android.view.View; +import androidx.annotation.NonNull; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; +import java.util.List; +import org.gitnex.tea4j.v2.models.Activity; +import org.mian.gitnex.R; +import org.mian.gitnex.adapters.DashboardAdapter; +import org.mian.gitnex.clients.RetrofitClient; +import org.mian.gitnex.databinding.FragmentDashboardBinding; +import org.mian.gitnex.helpers.Constants; +import org.mian.gitnex.helpers.Toasty; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +/** + * @author M M Arif + */ +public class DashboardViewModel extends ViewModel { + + private MutableLiveData> activityList; + private int resultLimit; + + public LiveData> getActivitiesList( + String username, Context ctx, FragmentDashboardBinding binding) { + + activityList = new MutableLiveData<>(); + resultLimit = Constants.getCurrentResultLimit(ctx); + loadActivityList(username, ctx, binding); + return activityList; + } + + public void loadActivityList(String username, Context ctx, FragmentDashboardBinding binding) { + + Call> call = + RetrofitClient.getApiInterface(ctx) + .userListActivityFeeds(username, false, null, 1, resultLimit); + + call.enqueue( + new Callback<>() { + + @Override + public void onResponse( + @NonNull Call> call, + @NonNull Response> response) { + + if (response.isSuccessful()) { + activityList.postValue(response.body()); + } else { + binding.progressBar.setVisibility(View.GONE); + Toasty.error(ctx, ctx.getString(R.string.genericError)); + } + } + + @Override + public void onFailure( + @NonNull Call> call, @NonNull Throwable t) { + + Toasty.error(ctx, ctx.getString(R.string.genericServerResponseError)); + } + }); + } + + public void loadMoreActivities( + String username, + int page, + Context ctx, + DashboardAdapter adapter, + FragmentDashboardBinding binding) { + + Call> call = + RetrofitClient.getApiInterface(ctx) + .userListActivityFeeds(username, false, null, page, resultLimit); + + call.enqueue( + new Callback<>() { + + @Override + public void onResponse( + @NonNull Call> call, + @NonNull Response> response) { + + if (response.isSuccessful()) { + + List list = activityList.getValue(); + assert list != null; + assert response.body() != null; + + if (response.body().size() != 0) { + list.addAll(response.body()); + adapter.updateList(list); + } else { + adapter.setMoreDataAvailable(false); + } + } else { + binding.progressBar.setVisibility(View.GONE); + Toasty.error(ctx, ctx.getString(R.string.genericError)); + } + } + + @Override + public void onFailure( + @NonNull Call> call, @NonNull Throwable t) { + + Toasty.error(ctx, ctx.getString(R.string.genericServerResponseError)); + } + }); + } +} diff --git a/app/src/main/res/drawable/ic_dashboard.xml b/app/src/main/res/drawable/ic_dashboard.xml new file mode 100644 index 00000000..8cc041b8 --- /dev/null +++ b/app/src/main/res/drawable/ic_dashboard.xml @@ -0,0 +1,34 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_draft.xml b/app/src/main/res/drawable/ic_draft.xml new file mode 100644 index 00000000..e63c7a9b --- /dev/null +++ b/app/src/main/res/drawable/ic_draft.xml @@ -0,0 +1,48 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/shape_beta_badge.xml b/app/src/main/res/drawable/shape_beta_badge.xml new file mode 100644 index 00000000..3effc35e --- /dev/null +++ b/app/src/main/res/drawable/shape_beta_badge.xml @@ -0,0 +1,14 @@ + + + + + + + diff --git a/app/src/main/res/drawable/shape_draft_release.xml b/app/src/main/res/drawable/shape_draft_release.xml index 66e02aef..65c5db56 100644 --- a/app/src/main/res/drawable/shape_draft_release.xml +++ b/app/src/main/res/drawable/shape_draft_release.xml @@ -8,7 +8,7 @@ + android:radius="@dimen/dimen6dp"> diff --git a/app/src/main/res/drawable/shape_pre_release.xml b/app/src/main/res/drawable/shape_pre_release.xml index 1f250589..4a08a2a0 100644 --- a/app/src/main/res/drawable/shape_pre_release.xml +++ b/app/src/main/res/drawable/shape_pre_release.xml @@ -8,7 +8,7 @@ + android:radius="@dimen/dimen6dp"> diff --git a/app/src/main/res/drawable/shape_stable_release.xml b/app/src/main/res/drawable/shape_stable_release.xml index 9f3dedc6..c74d7fc3 100644 --- a/app/src/main/res/drawable/shape_stable_release.xml +++ b/app/src/main/res/drawable/shape_stable_release.xml @@ -8,7 +8,7 @@ + android:radius="@dimen/dimen6dp"> diff --git a/app/src/main/res/layout/badge_beta.xml b/app/src/main/res/layout/badge_beta.xml new file mode 100644 index 00000000..291f633b --- /dev/null +++ b/app/src/main/res/layout/badge_beta.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/app/src/main/res/layout/fragment_dashboard.xml b/app/src/main/res/layout/fragment_dashboard.xml new file mode 100644 index 00000000..c14c07c8 --- /dev/null +++ b/app/src/main/res/layout/fragment_dashboard.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/list_dashboard_activity.xml b/app/src/main/res/layout/list_dashboard_activity.xml new file mode 100644 index 00000000..6949b03f --- /dev/null +++ b/app/src/main/res/layout/list_dashboard_activity.xml @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/list_pr.xml b/app/src/main/res/layout/list_pr.xml index 29d657bc..c9bdfdeb 100644 --- a/app/src/main/res/layout/list_pr.xml +++ b/app/src/main/res/layout/list_pr.xml @@ -54,15 +54,31 @@ android:layout_height="wrap_content" android:orientation="vertical"> - + android:orientation="horizontal"> + + + + + + diff --git a/app/src/main/res/menu/drawer_menu.xml b/app/src/main/res/menu/drawer_menu.xml index 7bf01c41..5be95c19 100644 --- a/app/src/main/res/menu/drawer_menu.xml +++ b/app/src/main/res/menu/drawer_menu.xml @@ -7,6 +7,12 @@ + + diff --git a/app/src/main/res/values/settings.xml b/app/src/main/res/values/settings.xml index 42055e81..2d985ff3 100644 --- a/app/src/main/res/values/settings.xml +++ b/app/src/main/res/values/settings.xml @@ -74,6 +74,7 @@ @string/navMyIssues @string/navMostVisited @string/navNotes + @string/dashboard diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 321459c2..f2a757ae 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -515,6 +515,7 @@ Open in Browser Delete %s Reset + BETA Explore users @@ -863,4 +864,6 @@ Language Statistics %s%% + + Dashboard