diff --git a/app/build.gradle b/app/build.gradle index cd76ce07..dbceb658 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -108,7 +108,7 @@ dependencies { implementation "androidx.work:work-runtime:$work_version" implementation "io.mikael:urlbuilder:2.0.9" implementation "org.codeberg.gitnex-garage:emoji-java:v5.1.2" - coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.2.0" + 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.4.0' 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 75148660..f9930a85 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.fragments.BottomSheetDraftsFragment; import org.mian.gitnex.fragments.BottomSheetMyIssuesFilterFragment; import org.mian.gitnex.fragments.DraftsFragment; import org.mian.gitnex.fragments.ExploreFragment; +import org.mian.gitnex.fragments.MostVisitedReposFragment; import org.mian.gitnex.fragments.MyIssuesFragment; import org.mian.gitnex.fragments.MyProfileFragment; import org.mian.gitnex.fragments.MyRepositoriesFragment; @@ -145,6 +146,9 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig else if(fragmentById instanceof MyProfileFragment) { toolbarTitle.setText(getResources().getString(R.string.navProfile)); } + else if(fragmentById instanceof MostVisitedReposFragment) { + toolbarTitle.setText(getResources().getString(R.string.navMostVisited)); + } else if(fragmentById instanceof DraftsFragment) { toolbarTitle.setText(getResources().getString(R.string.titleDrafts)); } @@ -592,6 +596,11 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig toolbarTitle.setText(getResources().getString(R.string.navMyIssues)); getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, new MyIssuesFragment()).commit(); } + else if(id == R.id.nav_most_visited) { + + toolbarTitle.setText(getResources().getString(R.string.navMostVisited)); + getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, new MostVisitedReposFragment()).commit(); + } drawer.closeDrawer(GravityCompat.START); return true; diff --git a/app/src/main/java/org/mian/gitnex/adapters/DraftsAdapter.java b/app/src/main/java/org/mian/gitnex/adapters/DraftsAdapter.java index bfc632ef..bda741f2 100644 --- a/app/src/main/java/org/mian/gitnex/adapters/DraftsAdapter.java +++ b/app/src/main/java/org/mian/gitnex/adapters/DraftsAdapter.java @@ -62,7 +62,6 @@ public class DraftsAdapter extends RecyclerView.Adapter { @@ -98,9 +97,7 @@ public class DraftsAdapter extends RecyclerView.Adapter draftsListMain) { @@ -152,9 +149,14 @@ public class DraftsAdapter extends RecyclerView.Adapter list) { draftsList = list; - notifyDataSetChanged(); + notifyDataChanged(); } } 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 4bc98159..2a371a39 100644 --- a/app/src/main/java/org/mian/gitnex/adapters/ExploreIssuesAdapter.java +++ b/app/src/main/java/org/mian/gitnex/adapters/ExploreIssuesAdapter.java @@ -117,14 +117,21 @@ public class ExploreIssuesAdapter extends RecyclerView.Adapter context.startActivity(intentIssueDetail)); - frameLabels.setOnClickListener(v -> context.startActivity(intentIssueDetail)); - frameLabelsDots.setOnClickListener(v -> context.startActivity(intentIssueDetail)); + itemView.setOnClickListener(v -> { + repo.saveToDB(context); + context.startActivity(intentIssueDetail); + }); + frameLabels.setOnClickListener(v -> { + repo.saveToDB(context); + context.startActivity(intentIssueDetail); + }); + frameLabelsDots.setOnClickListener(v -> { + repo.saveToDB(context); + context.startActivity(intentIssueDetail); + }); }, 200); issueAssigneeAvatar.setOnClickListener(v -> { diff --git a/app/src/main/java/org/mian/gitnex/adapters/ExploreRepositoriesAdapter.java b/app/src/main/java/org/mian/gitnex/adapters/ExploreRepositoriesAdapter.java index 03c3147f..380315da 100644 --- a/app/src/main/java/org/mian/gitnex/adapters/ExploreRepositoriesAdapter.java +++ b/app/src/main/java/org/mian/gitnex/adapters/ExploreRepositoriesAdapter.java @@ -116,7 +116,7 @@ public class ExploreRepositoriesAdapter extends RecyclerView.Adapter { + + private List mostVisitedReposList; + + static class MostVisitedViewHolder extends RecyclerView.ViewHolder { + + private Repository repository; + + private final ImageView image; + private final TextView repoName; + private final TextView orgName; + private final TextView mostVisited; + + private MostVisitedViewHolder(View itemView) { + + super(itemView); + + image = itemView.findViewById(R.id.image); + repoName = itemView.findViewById(R.id.repo_name); + orgName = itemView.findViewById(R.id.org_name); + mostVisited = itemView.findViewById(R.id.most_visited); + + itemView.setOnClickListener(v -> { + + Context context = v.getContext(); + RepositoryContext repositoryContext = new RepositoryContext(repository.getRepositoryOwner(), repository.getRepositoryName(), context); + Intent intent = repositoryContext.getIntent(context, RepoDetailActivity.class); + context.startActivity(intent); + }); + } + } + + public MostVisitedReposAdapter(List reposListMain) { + this.mostVisitedReposList = reposListMain; + } + + @NonNull + @Override + public MostVisitedViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_most_visited_repos, parent, false); + return new MostVisitedViewHolder(v); + } + + @SuppressLint("DefaultLocale") + @Override + public void onBindViewHolder(@NonNull MostVisitedViewHolder holder, int position) { + + Repository currentItem = mostVisitedReposList.get(position); + holder.repository = currentItem; + + ColorGenerator generator = ColorGenerator.Companion.getMATERIAL(); + int color = generator.getColor(currentItem.getRepositoryOwner()); + String firstCharacter = String.valueOf(currentItem.getRepositoryOwner().charAt(0)); + TextDrawable drawable = TextDrawable.builder().beginConfig().useFont(Typeface.DEFAULT).fontSize(18).toUpperCase().width(28).height(28).endConfig().buildRoundRect(firstCharacter, color, 14); + + holder.image.setImageDrawable(drawable); + holder.orgName.setText(currentItem.getRepositoryOwner()); + holder.repoName.setText(currentItem.getRepositoryName()); + holder.mostVisited.setText(AppUtil.numberFormatter(currentItem.getMostVisited())); + } + + @Override + public int getItemCount() { + return mostVisitedReposList.size(); + } + + @SuppressLint("NotifyDataSetChanged") + public void notifyDataChanged() { + notifyDataSetChanged(); + } + + public void updateList(List list) { + + mostVisitedReposList = list; + notifyDataChanged(); + } +} diff --git a/app/src/main/java/org/mian/gitnex/adapters/RepoForksAdapter.java b/app/src/main/java/org/mian/gitnex/adapters/RepoForksAdapter.java index 0b412e41..ad626887 100644 --- a/app/src/main/java/org/mian/gitnex/adapters/RepoForksAdapter.java +++ b/app/src/main/java/org/mian/gitnex/adapters/RepoForksAdapter.java @@ -109,7 +109,7 @@ public class RepoForksAdapter extends RecyclerView.Adapter repositoriesDao.deleteRepository(repositoryId)); } + public void updateRepositoryMostVisited(int mostVisited, int repositoryId) { + executorService.execute(() -> repositoriesDao.updateRepositoryMostVisited(mostVisited, repositoryId)); + } + + public LiveData> fetchAllMostVisited(int repoAccountId) { + return repositoriesDao.fetchAllMostVisited(repoAccountId); + } } diff --git a/app/src/main/java/org/mian/gitnex/database/dao/RepositoriesDao.java b/app/src/main/java/org/mian/gitnex/database/dao/RepositoriesDao.java index 92ad05d6..2c6b2658 100644 --- a/app/src/main/java/org/mian/gitnex/database/dao/RepositoriesDao.java +++ b/app/src/main/java/org/mian/gitnex/database/dao/RepositoriesDao.java @@ -44,4 +44,9 @@ public interface RepositoriesDao { @Query("DELETE FROM Repositories WHERE repoAccountId = :repoAccountId") void deleteRepositoriesByAccount(int repoAccountId); + @Query("UPDATE Repositories SET mostVisited = :mostVisited WHERE repositoryId = :repositoryId") + void updateRepositoryMostVisited(int mostVisited, int repositoryId); + + @Query("SELECT * FROM Repositories WHERE mostVisited > 0 AND repoAccountId = :repoAccountId ORDER BY mostVisited DESC LIMIT 50") + LiveData> fetchAllMostVisited(int repoAccountId); } diff --git a/app/src/main/java/org/mian/gitnex/database/db/GitnexDatabase.java b/app/src/main/java/org/mian/gitnex/database/db/GitnexDatabase.java index ed74a9b1..e1e13524 100644 --- a/app/src/main/java/org/mian/gitnex/database/db/GitnexDatabase.java +++ b/app/src/main/java/org/mian/gitnex/database/db/GitnexDatabase.java @@ -19,11 +19,11 @@ import org.mian.gitnex.database.models.UserAccount; */ @Database(entities = {Draft.class, Repository.class, UserAccount.class}, - version = 5, exportSchema = false) + version = 6, exportSchema = false) public abstract class GitnexDatabase extends RoomDatabase { private static final String DB_NAME = "gitnex"; - private static GitnexDatabase gitnexDatabase; + private static volatile GitnexDatabase gitnexDatabase; public abstract DraftsDao draftsDao(); public abstract RepositoriesDao repositoriesDao(); @@ -61,6 +61,14 @@ public abstract class GitnexDatabase extends RoomDatabase { } }; + private static final Migration MIGRATION_5_6 = new Migration(5, 6) { + + @Override + public void migrate(@NonNull SupportSQLiteDatabase database) { + database.execSQL("ALTER TABLE 'Repositories' ADD COLUMN 'mostVisited' INTEGER NOT NULL DEFAULT 0"); + } + }; + public static GitnexDatabase getDatabaseInstance(Context context) { if (gitnexDatabase == null) { @@ -68,9 +76,9 @@ public abstract class GitnexDatabase extends RoomDatabase { if(gitnexDatabase == null) { gitnexDatabase = Room.databaseBuilder(context, GitnexDatabase.class, DB_NAME) - // .fallbackToDestructiveMigration() + //.fallbackToDestructiveMigration() .allowMainThreadQueries() - .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5) + .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6) .build(); } diff --git a/app/src/main/java/org/mian/gitnex/database/models/Repository.java b/app/src/main/java/org/mian/gitnex/database/models/Repository.java index f89c864e..18f366aa 100644 --- a/app/src/main/java/org/mian/gitnex/database/models/Repository.java +++ b/app/src/main/java/org/mian/gitnex/database/models/Repository.java @@ -24,6 +24,7 @@ public class Repository implements Serializable { private int repoAccountId; private String repositoryOwner; private String repositoryName; + private int mostVisited; public int getRepositoryId() { return repositoryId; @@ -56,4 +57,12 @@ public class Repository implements Serializable { public void setRepositoryName(String repositoryName) { this.repositoryName = repositoryName; } + + public int getMostVisited() { + return mostVisited; + } + + public void setMostVisited(int mostVisited) { + this.mostVisited = mostVisited; + } } diff --git a/app/src/main/java/org/mian/gitnex/fragments/DraftsFragment.java b/app/src/main/java/org/mian/gitnex/fragments/DraftsFragment.java index f24b8624..a977487c 100644 --- a/app/src/main/java/org/mian/gitnex/fragments/DraftsFragment.java +++ b/app/src/main/java/org/mian/gitnex/fragments/DraftsFragment.java @@ -85,7 +85,6 @@ public class DraftsFragment extends Fragment { fetchDataAsync(currentActiveAccountId); return fragmentDraftsBinding.getRoot(); - } private void fetchDataAsync(int accountId) { @@ -99,18 +98,14 @@ public class DraftsFragment extends Fragment { draftsList_.clear(); noData.setVisibility(View.GONE); draftsList_.addAll(drafts); - adapter.notifyDataSetChanged(); + adapter.notifyDataChanged(); mRecyclerView.setAdapter(adapter); - } else { noData.setVisibility(View.VISIBLE); - } - }); - } @Override @@ -126,9 +121,8 @@ public class DraftsFragment extends Fragment { BaseApi.getInstance(ctx, DraftsApi.class).deleteAllDrafts(accountId); draftsList_.clear(); - adapter.notifyDataSetChanged(); + adapter.notifyDataChanged(); Toasty.success(ctx, getResources().getString(R.string.draftsDeleteSuccess)); - } else { Toasty.warning(ctx, getResources().getString(R.string.draftsListEmpty)); @@ -160,10 +154,8 @@ public class DraftsFragment extends Fragment { filter(newText); return false; - } }); - } private void filter(String text) { @@ -184,5 +176,4 @@ public class DraftsFragment extends Fragment { adapter.updateList(arr); } - } diff --git a/app/src/main/java/org/mian/gitnex/fragments/MostVisitedReposFragment.java b/app/src/main/java/org/mian/gitnex/fragments/MostVisitedReposFragment.java new file mode 100644 index 00000000..eb32ca1d --- /dev/null +++ b/app/src/main/java/org/mian/gitnex/fragments/MostVisitedReposFragment.java @@ -0,0 +1,103 @@ +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 android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; +import org.mian.gitnex.R; +import org.mian.gitnex.activities.MainActivity; +import org.mian.gitnex.adapters.MostVisitedReposAdapter; +import org.mian.gitnex.database.api.BaseApi; +import org.mian.gitnex.database.api.RepositoriesApi; +import org.mian.gitnex.database.models.Repository; +import org.mian.gitnex.databinding.FragmentDraftsBinding; +import org.mian.gitnex.helpers.TinyDB; +import java.util.ArrayList; +import java.util.List; + +/** + * @author M M Arif + */ + +public class MostVisitedReposFragment extends Fragment { + + private Context ctx; + private MostVisitedReposAdapter adapter; + private RecyclerView mRecyclerView; + private RepositoriesApi repositoriesApi; + private TextView noData; + private List mostVisitedReposList; + private int currentActiveAccountId; + private SwipeRefreshLayout swipeRefresh; + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + FragmentDraftsBinding fragmentDraftsBinding = FragmentDraftsBinding.inflate(inflater, container, false); + + ctx = getContext(); + setHasOptionsMenu(true); + + ((MainActivity) requireActivity()).setActionBarTitle(getResources().getString(R.string.navMostVisited)); + + TinyDB tinyDb = TinyDB.getInstance(ctx); + + mostVisitedReposList = new ArrayList<>(); + repositoriesApi = BaseApi.getInstance(ctx, RepositoriesApi.class); + + noData = fragmentDraftsBinding.noData; + mRecyclerView = fragmentDraftsBinding.recyclerView; + swipeRefresh = fragmentDraftsBinding.pullToRefresh; + + mRecyclerView.setHasFixedSize(true); + mRecyclerView.setLayoutManager(new LinearLayoutManager(ctx)); + + DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(mRecyclerView.getContext(), + DividerItemDecoration.VERTICAL); + mRecyclerView.addItemDecoration(dividerItemDecoration); + + adapter = new MostVisitedReposAdapter(mostVisitedReposList); + currentActiveAccountId = tinyDb.getInt("currentActiveAccountId"); + swipeRefresh.setOnRefreshListener(() -> new Handler(Looper.getMainLooper()).postDelayed(() -> { + + mostVisitedReposList.clear(); + fetchDataAsync(currentActiveAccountId); + }, 250)); + + fetchDataAsync(currentActiveAccountId); + + return fragmentDraftsBinding.getRoot(); + } + + private void fetchDataAsync(int accountId) { + + repositoriesApi.fetchAllMostVisited(accountId).observe(getViewLifecycleOwner(), mostVisitedRepos -> { + + swipeRefresh.setRefreshing(false); + assert mostVisitedRepos != null; + if(mostVisitedRepos.size() > 0) { + + mostVisitedReposList.clear(); + noData.setVisibility(View.GONE); + mostVisitedReposList.addAll(mostVisitedRepos); + adapter.notifyDataChanged(); + mRecyclerView.setAdapter(adapter); + } + else { + + noData.setVisibility(View.VISIBLE); + } + }); + } +} diff --git a/app/src/main/java/org/mian/gitnex/helpers/AppUtil.java b/app/src/main/java/org/mian/gitnex/helpers/AppUtil.java index 1da9c358..92e9619a 100644 --- a/app/src/main/java/org/mian/gitnex/helpers/AppUtil.java +++ b/app/src/main/java/org/mian/gitnex/helpers/AppUtil.java @@ -34,6 +34,7 @@ import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.nio.charset.StandardCharsets; +import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; @@ -456,6 +457,27 @@ public class AppUtil { } /** + * Pretty number format + * Example, 1200 = 1.2k + */ + public static String numberFormatter(Number number) { + + char[] suffix = {' ', 'k', 'M', 'B', 'T'}; + long numValue = number.longValue(); + int value = (int) Math.floor(Math.log10(numValue)); + int base = value / 3; + if(value >= 3 && base < suffix.length) { + return new DecimalFormat("#0.0").format(numValue / Math.pow(10, base * 3)) + suffix[base]; + } + if(base >= suffix.length) { + return new DecimalFormat("#0").format(numValue / Math.pow(10, base * 2)) + suffix[4]; + } + else { + return new DecimalFormat("#,##0").format(numValue); + } + } + + /* * check for ghost/restricted users/profiles */ public static Boolean checkGhostUsers(String str) { diff --git a/app/src/main/java/org/mian/gitnex/helpers/contexts/RepositoryContext.java b/app/src/main/java/org/mian/gitnex/helpers/contexts/RepositoryContext.java index 6a769bee..3af5e1f0 100644 --- a/app/src/main/java/org/mian/gitnex/helpers/contexts/RepositoryContext.java +++ b/app/src/main/java/org/mian/gitnex/helpers/contexts/RepositoryContext.java @@ -33,7 +33,6 @@ public class RepositoryContext implements Serializable { OPEN, CLOSED; - @NonNull @Override public String toString() { @@ -243,16 +242,17 @@ public class RepositoryContext implements Serializable { RepositoriesApi repositoryData = BaseApi.getInstance(context, RepositoriesApi.class); assert repositoryData != null; - Integer count = repositoryData.checkRepository(currentActiveAccountId, getOwner(), getName()); + Repository getMostVisitedValue = repositoryData.getRepository(currentActiveAccountId, getOwner(), getName()); - if(count == 0) { - long id = repositoryData.insertRepository(currentActiveAccountId, getOwner(), getName()); + if(getMostVisitedValue == null) { + long id = repositoryData.insertRepository(currentActiveAccountId, getOwner(), getName(), 1); setRepositoryId((int) id); return (int) id; } else { Repository data = repositoryData.getRepository(currentActiveAccountId, getOwner(), getName()); setRepositoryId(data.getRepositoryId()); + repositoryData.updateRepositoryMostVisited(getMostVisitedValue.getMostVisited() + 1, data.getRepositoryId()); return data.getRepositoryId(); } } diff --git a/app/src/main/res/drawable/ic_trending.xml b/app/src/main/res/drawable/ic_trending.xml new file mode 100644 index 00000000..b544f5d0 --- /dev/null +++ b/app/src/main/res/drawable/ic_trending.xml @@ -0,0 +1,20 @@ + + + + diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 601f429e..0428902e 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -70,7 +70,7 @@ android:layout_marginBottom="@dimen/dimen8dp" android:hint="@string/protocol" app:endIconTint="?attr/iconsColor" - style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox.ExposedDropdownMenu"> + style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu"> + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/menu/drawer_menu.xml b/app/src/main/res/menu/drawer_menu.xml index 7e79f428..9e425b4c 100644 --- a/app/src/main/res/menu/drawer_menu.xml +++ b/app/src/main/res/menu/drawer_menu.xml @@ -38,6 +38,10 @@ android:icon="@drawable/ic_search" android:title="@string/pageTitleExplore" /> + + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index ab210a01..4220048b 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -9,6 +9,7 @@ 10dp 12dp 16dp + 18dp 20dp 24dp 26dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5f231acd..2e226f5c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -31,6 +31,7 @@ Logout Administration My Issues + Most Visited Repos