diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9ced3bea..6f6e96e0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -39,9 +39,6 @@ - branchesList = new ArrayList<>(); - private String selectedBranch; - private RepositoryContext repository; - private boolean renderMd = false; - private MaterialAlertDialogBuilder materialAlertDialogBuilderNotes; - private CustomInsertNoteBinding customInsertNoteBinding; - private NotesAdapter adapter; - private NotesApi notesApi; - public AlertDialog dialogNotes; - - @SuppressLint("ClickableViewAccessibility") - @Override - public void onCreate(Bundle savedInstanceState) { - - super.onCreate(savedInstanceState); - - binding = ActivityCreateReleaseBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - - repository = RepositoryContext.fromIntent(getIntent()); - - materialAlertDialogBuilderNotes = - new MaterialAlertDialogBuilder(ctx, R.style.ThemeOverlay_Material3_Dialog_Alert); - - binding.releaseContent.setOnTouchListener( - (touchView, motionEvent) -> { - touchView.getParent().requestDisallowInterceptTouchEvent(true); - - if ((motionEvent.getAction() & MotionEvent.ACTION_UP) != 0 - && (motionEvent.getActionMasked() & MotionEvent.ACTION_UP) != 0) { - - touchView.getParent().requestDisallowInterceptTouchEvent(false); - } - return false; - }); - - binding.topAppBar.setNavigationOnClickListener(v -> finish()); - - binding.topAppBar.setOnMenuItemClickListener( - menuItem -> { - int id = menuItem.getItemId(); - - if (id == R.id.markdown) { - - if (!renderMd) { - Markdown.render( - ctx, - EmojiParser.parseToUnicode( - Objects.requireNonNull( - Objects.requireNonNull( - binding.releaseContent - .getText()) - .toString())), - binding.markdownPreview); - - binding.markdownPreview.setVisibility(View.VISIBLE); - binding.releaseContentLayout.setVisibility(View.GONE); - renderMd = true; - } else { - binding.markdownPreview.setVisibility(View.GONE); - binding.releaseContentLayout.setVisibility(View.VISIBLE); - renderMd = false; - } - - return true; - } else if (id == R.id.create) { - processNewRelease(); - return true; - } else if (id == R.id.create_tag) { - createNewTag(); - return true; - } else { - return super.onOptionsItemSelected(menuItem); - } - }); - - binding.insertNote.setOnClickListener(insertNote -> showAllNotes()); - - binding.releaseBranch.setKeyListener(null); - binding.releaseBranch.setCursorVisible(false); - - binding.releaseBranch.setOnFocusChangeListener( - (v, hasFocus) -> { - if (hasFocus) { - getBranches(); - binding.releaseBranch.clearFocus(); - } - }); - } - - private void showAllNotes() { - List notesList = new ArrayList<>(); - notesApi = BaseApi.getInstance(ctx, NotesApi.class); - customInsertNoteBinding = CustomInsertNoteBinding.inflate(LayoutInflater.from(ctx)); - - materialAlertDialogBuilderNotes.setView(customInsertNoteBinding.getRoot()); - - customInsertNoteBinding.recyclerView.setLayoutManager(new LinearLayoutManager(ctx)); - adapter = new NotesAdapter(ctx, notesList, "insert", "release"); - customInsertNoteBinding.recyclerView.setAdapter(adapter); - - if (notesApi.getCount() > 0) { - fetchNotes(); - dialogNotes = materialAlertDialogBuilderNotes.show(); - } else { - Toasty.show(ctx, getString(R.string.noNotes)); - } - } - - private void fetchNotes() { - customInsertNoteBinding.expressiveLoader.setVisibility(View.VISIBLE); - - notesApi.fetchAllNotes() - .observe( - this, - allNotes -> { - customInsertNoteBinding.expressiveLoader.setVisibility(View.GONE); - - if (allNotes != null && !allNotes.isEmpty()) { - adapter.updateList(allNotes); - customInsertNoteBinding - .layoutEmpty - .getRoot() - .setVisibility(View.GONE); - } else { - adapter.updateList(new ArrayList<>()); - customInsertNoteBinding - .layoutEmpty - .getRoot() - .setVisibility(View.VISIBLE); - } - }); - } - - private void createNewTag() { - - String tagName = Objects.requireNonNull(binding.releaseTagName.getText()).toString(); - String message = - Objects.requireNonNull(binding.releaseTitle.getText()) - + "\n\n" - + Objects.requireNonNull(binding.releaseContent.getText()); - - if (tagName.isEmpty()) { - Toasty.show(ctx, getString(R.string.tagNameErrorEmpty)); - return; - } - - if (selectedBranch == null) { - Toasty.show(ctx, getString(R.string.selectBranchError)); - return; - } - - CreateTagOption createReleaseJson = new CreateTagOption(); - createReleaseJson.setMessage(message); - createReleaseJson.setTagName(tagName); - createReleaseJson.setTarget(selectedBranch); - - Call call = - RetrofitClient.getApiInterface(ctx) - .repoCreateTag( - repository.getOwner(), repository.getName(), createReleaseJson); - - call.enqueue( - new Callback<>() { - - @Override - public void onResponse( - @NonNull Call call, @NonNull retrofit2.Response response) { - - if (response.code() == 201) { - - // RepoDetailActivity.updateFABActions = true; - Toasty.show(ctx, getString(R.string.tagCreated)); - new Handler().postDelayed(() -> finish(), 3000); - } else if (response.code() == 401) { - AlertDialogs.authorizationTokenRevokedDialog(ctx); - } else if (response.code() == 403) { - Toasty.show(ctx, getString(R.string.authorizeError)); - } else if (response.code() == 404) { - Toasty.show(ctx, getString(R.string.apiNotFound)); - } else { - Toasty.show(ctx, getString(R.string.genericError)); - } - } - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) {} - }); - } - - private void processNewRelease() { - - String newReleaseTagName = - Objects.requireNonNull(binding.releaseTagName.getText()).toString(); - String newReleaseTitle = Objects.requireNonNull(binding.releaseTitle.getText()).toString(); - String newReleaseContent = - Objects.requireNonNull(binding.releaseContent.getText()).toString(); - String checkBranch = selectedBranch; - boolean newReleaseType = binding.releaseType.isChecked(); - boolean newReleaseDraft = binding.releaseDraft.isChecked(); - - if (newReleaseTitle.isEmpty()) { - Toasty.show(ctx, getString(R.string.titleErrorEmpty)); - return; - } - - if (newReleaseTagName.isEmpty()) { - Toasty.show(ctx, getString(R.string.tagNameErrorEmpty)); - return; - } - - if (checkBranch == null) { - Toasty.show(ctx, getString(R.string.selectBranchError)); - return; - } - - createNewReleaseFunc( - repository.getOwner(), - repository.getName(), - newReleaseTagName, - newReleaseTitle, - newReleaseContent, - selectedBranch, - newReleaseType, - newReleaseDraft); - } - - private void createNewReleaseFunc( - String repoOwner, - String repoName, - String newReleaseTagName, - String newReleaseTitle, - String newReleaseContent, - String selectedBranch, - boolean newReleaseType, - boolean newReleaseDraft) { - - CreateReleaseOption createReleaseJson = new CreateReleaseOption(); - createReleaseJson.setName(newReleaseTitle); - createReleaseJson.setTagName(newReleaseTagName); - createReleaseJson.setBody(newReleaseContent); - createReleaseJson.setDraft(newReleaseDraft); - createReleaseJson.setPrerelease(newReleaseType); - createReleaseJson.setTargetCommitish(selectedBranch); - - Call call = - RetrofitClient.getApiInterface(ctx) - .repoCreateRelease(repoOwner, repoName, createReleaseJson); - - call.enqueue( - new Callback<>() { - - @Override - public void onResponse( - @NonNull Call call, - @NonNull retrofit2.Response response) { - - if (response.code() == 201) { - - // RepoDetailActivity.updateFABActions = true; - Toasty.show(ctx, getString(R.string.releaseCreatedText)); - new Handler().postDelayed(() -> finish(), 3000); - } else if (response.code() == 401) { - - AlertDialogs.authorizationTokenRevokedDialog(ctx); - } else if (response.code() == 403) { - - Toasty.show(ctx, getString(R.string.authorizeError)); - } else if (response.code() == 404) { - - Toasty.show(ctx, getString(R.string.apiNotFound)); - } else { - - Toasty.show(ctx, getString(R.string.genericError)); - } - } - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) {} - }); - } - - private void getBranches() { - - Dialog progressDialog = new Dialog(ctx); - progressDialog.setCancelable(false); - progressDialog.setContentView(R.layout.custom_progress_loader); - progressDialog.show(); - - MaterialAlertDialogBuilder dialogBuilder = new MaterialAlertDialogBuilder(ctx); - View dialogView = getLayoutInflater().inflate(R.layout.custom_branches_dialog, null); - dialogBuilder.setView(dialogView); - - RecyclerView recyclerView = dialogView.findViewById(R.id.recyclerView); - recyclerView.setLayoutManager(new LinearLayoutManager(ctx)); - - recyclerView.addItemDecoration( - new RecyclerView.ItemDecoration() { - @Override - public void getItemOffsets( - @NonNull Rect outRect, - @NonNull View view, - @NonNull RecyclerView parent, - @NonNull RecyclerView.State state) { - - int position = parent.getChildAdapterPosition(view); - int spacingSides = (int) ctx.getResources().getDimension(R.dimen.dimen16dp); - int spacingTop = (int) ctx.getResources().getDimension(R.dimen.dimen12dp); - - outRect.right = spacingSides; - outRect.left = spacingSides; - - if (position > 0) { - outRect.top = spacingTop; - } - } - }); - - dialogBuilder.setNeutralButton(R.string.close, (dialog, which) -> dialog.dismiss()); - AlertDialog dialog = dialogBuilder.create(); - dialog.setCancelable(false); - dialog.setCanceledOnTouchOutside(false); - - final int[] page = {1}; - final int resultLimit = Constants.getCurrentResultLimit(ctx); - final boolean[] isLoading = {false}; - final boolean[] isLastPage = {false}; - - BranchAdapter adapter = - new BranchAdapter( - branchName -> { - binding.releaseBranch.setText(branchName); - selectedBranch = branchName; - dialog.dismiss(); - }); - recyclerView.setAdapter(adapter); - - Runnable fetchBranches = - () -> { - if (isLoading[0] || isLastPage[0]) return; - isLoading[0] = true; - - Call> call = - RetrofitClient.getApiInterface(ctx) - .repoListBranches( - repository.getOwner(), - repository.getName(), - page[0], - resultLimit); - - call.enqueue( - new Callback<>() { - @Override - public void onResponse( - @NonNull Call> call, - @NonNull Response> response) { - - isLoading[0] = false; - - if (response.code() == 200 && response.body() != null) { - List newBranches = response.body(); - adapter.addBranches(newBranches); - - String totalCountStr = - response.headers().get("X-Total-Count"); - - if (totalCountStr != null) { - - int totalItems = Integer.parseInt(totalCountStr); - int totalPages = - (int) - Math.ceil( - (double) totalItems - / resultLimit); - isLastPage[0] = page[0] >= totalPages; - } else { - isLastPage[0] = newBranches.size() < resultLimit; - } - page[0]++; - - if (page[0] == 2 && !dialog.isShowing()) { - progressDialog.dismiss(); - dialog.show(); - } - } else { - progressDialog.dismiss(); - } - } - - @Override - public void onFailure( - @NonNull Call> call, @NonNull Throwable t) { - isLoading[0] = false; - progressDialog.dismiss(); - } - }); - }; - - recyclerView.addOnScrollListener( - new RecyclerView.OnScrollListener() { - @Override - public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { - - super.onScrolled(recyclerView, dx, dy); - LinearLayoutManager layoutManager = - (LinearLayoutManager) recyclerView.getLayoutManager(); - - if (layoutManager != null) { - - int visibleItemCount = layoutManager.getChildCount(); - int totalItemCount = layoutManager.getItemCount(); - int firstVisibleItemPosition = - layoutManager.findFirstVisibleItemPosition(); - - if (!isLoading[0] - && !isLastPage[0] - && (visibleItemCount + firstVisibleItemPosition) - >= totalItemCount - 5) { - fetchBranches.run(); - } - } - } - }); - - // adapter.clear(); - fetchBranches.run(); - } - - @Override - public void onResume() { - super.onResume(); - repository.checkAccountSwitch(this); - } -} diff --git a/app/src/main/java/org/mian/gitnex/activities/RepoDetailActivity.java b/app/src/main/java/org/mian/gitnex/activities/RepoDetailActivity.java index 4f76c904..4a02d9e5 100644 --- a/app/src/main/java/org/mian/gitnex/activities/RepoDetailActivity.java +++ b/app/src/main/java/org/mian/gitnex/activities/RepoDetailActivity.java @@ -21,6 +21,7 @@ import org.mian.gitnex.R; import org.mian.gitnex.databinding.ActivityRepoDetailBinding; import org.mian.gitnex.fragments.BottomSheetCreateIssue; import org.mian.gitnex.fragments.BottomSheetCreateMilestone; +import org.mian.gitnex.fragments.BottomSheetCreateRelease; import org.mian.gitnex.fragments.BottomsheetRepoMenu; import org.mian.gitnex.fragments.CollaboratorsFragment; import org.mian.gitnex.fragments.FilesFragment; @@ -625,7 +626,8 @@ public class RepoDetailActivity extends BaseActivity case "newRelease": switchTab("releases", R.id.btn_nav_releases); - startActivity(repository.getIntent(this, CreateReleaseActivity.class)); + BottomSheetCreateRelease.newInstance(repository, null) + .show(getSupportFragmentManager(), "CREATE_RELEASE"); break; case "wiki": diff --git a/app/src/main/java/org/mian/gitnex/adapters/NotesAdapter.java b/app/src/main/java/org/mian/gitnex/adapters/NotesAdapter.java index cef75f40..e7c76a22 100644 --- a/app/src/main/java/org/mian/gitnex/adapters/NotesAdapter.java +++ b/app/src/main/java/org/mian/gitnex/adapters/NotesAdapter.java @@ -22,7 +22,6 @@ import org.mian.gitnex.R; import org.mian.gitnex.activities.BaseActivity; import org.mian.gitnex.activities.CreateNoteActivity; import org.mian.gitnex.activities.CreatePullRequestActivity; -import org.mian.gitnex.activities.CreateReleaseActivity; import org.mian.gitnex.database.api.BaseApi; import org.mian.gitnex.database.api.NotesApi; import org.mian.gitnex.database.models.Notes; @@ -107,10 +106,7 @@ public class NotesAdapter extends RecyclerView.Adapter releasesList; private final boolean canDelete; - private final ReleasesFragment.OnReleaseItemClickListener listener; + private final OnReleaseItemClickListener listener; + + public interface OnReleaseItemClickListener { + void onMenuClick(Release release, int position); + + void onDownload(String url); + } public ReleasesAdapter( Context context, List releases, boolean canDelete, - ReleasesFragment.OnReleaseItemClickListener listener) { + OnReleaseItemClickListener listener) { this.context = context; this.releasesList = releases; this.canDelete = canDelete; @@ -71,14 +76,6 @@ public class ReleasesAdapter extends RecyclerView.Adapter= 0 && position < releasesList.size()) { - releasesList.remove(position); - notifyItemRemoved(position); - notifyItemRangeChanged(position, releasesList.size()); - } - } - public class ReleasesViewHolder extends RecyclerView.ViewHolder { private final ListReleasesBinding binding; @@ -137,7 +134,7 @@ public class ReleasesAdapter extends RecyclerView.Adapter listener.onDelete(release, getBindingAdapterPosition())); + v -> listener.onMenuClick(release, getBindingAdapterPosition())); binding.btnAssets.setOnClickListener( v -> { diff --git a/app/src/main/java/org/mian/gitnex/fragments/BottomSheetContentViewer.java b/app/src/main/java/org/mian/gitnex/fragments/BottomSheetContentViewer.java new file mode 100644 index 00000000..b7d588e2 --- /dev/null +++ b/app/src/main/java/org/mian/gitnex/fragments/BottomSheetContentViewer.java @@ -0,0 +1,253 @@ +package org.mian.gitnex.fragments; + +import android.app.Dialog; +import android.os.Bundle; +import android.text.Spannable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.android.material.bottomsheet.BottomSheetDialog; +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; +import org.mian.gitnex.R; +import org.mian.gitnex.databinding.BottomsheetContentViewerBinding; +import org.mian.gitnex.helpers.AppUtil; +import org.mian.gitnex.helpers.Markdown; +import org.mian.gitnex.helpers.contexts.RepositoryContext; + +/** + * @author mmarif + */ +public class BottomSheetContentViewer extends BottomSheetDialogFragment { + + public enum Feature { + MARKDOWN_PREVIEW, // Enable markdown preview toggle + START_IN_MARKDOWN, // Start showing Markdown instead of raw content + ALLOW_COPY, // Show copy button (always visible by default) + ALLOW_SHARE, // Show share button (always visible by default) + SYNTAX_HIGHLIGHT, // Use renderWithHighlights for code + SHOW_TITLE, // Show title in header + } + + /* + Spannable highlighted = SyntaxHighlighter.highlight(code, "java"); + BottomSheetContentViewer.newInstance( + highlighted, + "Main.java", + repoContext, + BottomSheetContentViewer.Feature.SYNTAX_HIGHLIGHT, + ContentViewerBottomSheet.Feature.MARKDOWN_PREVIEW, + ContentViewerBottomSheet.Feature.SHOW_TITLE + ).show(fm, "viewer"); + */ + + private static final String ARG_CONTENT = "content"; + private static final String ARG_TITLE = "title"; + private static final String ARG_REPO_CONTEXT = "repo_context"; + private static final String ARG_FEATURES = "features"; + private static final String ARG_IS_SPANNABLE = "is_spannable"; + + private BottomsheetContentViewerBinding binding; + private final Set enabledFeatures = new HashSet<>(); + private RepositoryContext repoContext; + private String rawContent; + private String title; + private Spannable spannableContent; + private boolean isSpannable = false; + private boolean isMarkdownMode = false; + + public static BottomSheetContentViewer newInstance( + String content, + @Nullable String title, + @Nullable RepositoryContext repoContext, + Feature... features) { + BottomSheetContentViewer fragment = new BottomSheetContentViewer(); + Bundle args = new Bundle(); + args.putString(ARG_CONTENT, content); + args.putBoolean(ARG_IS_SPANNABLE, false); + if (title != null) args.putString(ARG_TITLE, title); + if (repoContext != null) args.putSerializable(ARG_REPO_CONTEXT, repoContext); + args.putStringArrayList(ARG_FEATURES, featureNamesToList(features)); + fragment.setArguments(args); + return fragment; + } + + public static BottomSheetContentViewer newInstance( + Spannable spannable, + @Nullable String title, + @Nullable RepositoryContext repoContext, + Feature... features) { + BottomSheetContentViewer fragment = new BottomSheetContentViewer(); + Bundle args = new Bundle(); + args.putCharSequence(ARG_CONTENT, spannable); + args.putBoolean(ARG_IS_SPANNABLE, true); + if (title != null) args.putString(ARG_TITLE, title); + if (repoContext != null) args.putSerializable(ARG_REPO_CONTEXT, repoContext); + args.putStringArrayList(ARG_FEATURES, featureNamesToList(features)); + fragment.setArguments(args); + return fragment; + } + + private static ArrayList featureNamesToList(Feature... features) { + ArrayList names = new ArrayList<>(); + for (Feature f : features) { + names.add(f.name()); + } + return names; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + isSpannable = getArguments().getBoolean(ARG_IS_SPANNABLE, false); + + if (isSpannable) { + spannableContent = (Spannable) getArguments().getCharSequence(ARG_CONTENT); + } else { + rawContent = getArguments().getString(ARG_CONTENT, ""); + } + + title = getArguments().getString(ARG_TITLE); + repoContext = (RepositoryContext) getArguments().getSerializable(ARG_REPO_CONTEXT); + + ArrayList featureNames = getArguments().getStringArrayList(ARG_FEATURES); + if (featureNames != null) { + for (String name : featureNames) { + try { + enabledFeatures.add(Feature.valueOf(name)); + } catch (IllegalArgumentException ignored) { + } + } + } + } + + isMarkdownMode = enabledFeatures.contains(Feature.START_IN_MARKDOWN); + } + + @Nullable @Override + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + binding = BottomsheetContentViewerBinding.inflate(inflater, container, false); + return binding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + setupUI(); + renderContent(); + } + + private void setupUI() { + binding.btnClose.setOnClickListener(v -> dismiss()); + + if (enabledFeatures.contains(Feature.ALLOW_COPY)) { + binding.btnCopy.setVisibility(View.VISIBLE); + binding.btnCopy.setOnClickListener(v -> copyContent()); + } + + if (enabledFeatures.contains(Feature.ALLOW_SHARE)) { + binding.btnShare.setVisibility(View.VISIBLE); + binding.btnShare.setOnClickListener(v -> shareContent()); + } + + if (enabledFeatures.contains(Feature.MARKDOWN_PREVIEW)) { + binding.btnMarkdown.setVisibility(View.VISIBLE); + binding.btnMarkdown.setOnClickListener(v -> toggleMarkdownMode()); + updateMarkdownIcon(); + } + + if (enabledFeatures.contains(Feature.SHOW_TITLE) && title != null) { + binding.viewerTitle.setText(title); + binding.viewerTitle.setVisibility(View.VISIBLE); + } + } + + private void renderContent() { + if (isMarkdownMode) { + renderMarkdown(); + } else { + renderRaw(); + } + } + + private void renderRaw() { + binding.rawContentScroll.setVisibility(View.VISIBLE); + binding.markdownPreviewScroll.setVisibility(View.GONE); + binding.markdownPreview.setVisibility(View.GONE); + binding.markdownPreviewText.setVisibility(View.GONE); + + String content = getContentAsString(); + binding.rawContentText.setText(content); + } + + private void renderMarkdown() { + binding.rawContentScroll.setVisibility(View.GONE); + binding.markdownPreviewScroll.setVisibility(View.VISIBLE); + + String content = getContentAsString(); + if (content == null) content = ""; + + if (enabledFeatures.contains(Feature.SYNTAX_HIGHLIGHT) && spannableContent != null) { + binding.markdownPreview.setVisibility(View.VISIBLE); + binding.markdownPreviewText.setVisibility(View.GONE); + Markdown.renderWithHighlights( + requireContext(), spannableContent, binding.markdownPreview, repoContext); + } else if (repoContext != null) { + binding.markdownPreview.setVisibility(View.VISIBLE); + binding.markdownPreviewText.setVisibility(View.GONE); + Markdown.render(requireContext(), content, binding.markdownPreview, repoContext); + } else { + binding.markdownPreview.setVisibility(View.GONE); + binding.markdownPreviewText.setVisibility(View.VISIBLE); + Markdown.render(requireContext(), content, binding.markdownPreviewText); + } + } + + private String getContentAsString() { + return isSpannable && spannableContent != null ? spannableContent.toString() : rawContent; + } + + private void toggleMarkdownMode() { + isMarkdownMode = !isMarkdownMode; + updateMarkdownIcon(); + renderContent(); + } + + private void updateMarkdownIcon() { + binding.btnMarkdown.setIconResource( + isMarkdownMode ? R.drawable.ic_edit : R.drawable.ic_markdown); + } + + private void copyContent() { + String content = getContentAsString(); + AppUtil.copyToClipboard(requireContext(), content, getString(R.string.copied_to_clipboard)); + } + + private void shareContent() { + String content = getContentAsString(); + AppUtil.sharingIntent(requireContext(), content); + } + + @Override + public void onStart() { + super.onStart(); + Dialog dialog = getDialog(); + if (dialog instanceof BottomSheetDialog) { + AppUtil.applyFullScreenSheetStyle((BottomSheetDialog) dialog, false); + } + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + binding = null; + } +} diff --git a/app/src/main/java/org/mian/gitnex/fragments/BottomSheetCreateRelease.java b/app/src/main/java/org/mian/gitnex/fragments/BottomSheetCreateRelease.java new file mode 100644 index 00000000..7fcc7f48 --- /dev/null +++ b/app/src/main/java/org/mian/gitnex/fragments/BottomSheetCreateRelease.java @@ -0,0 +1,460 @@ +package org.mian.gitnex.fragments; + +import android.app.Dialog; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.ViewModelProvider; +import com.google.android.material.bottomsheet.BottomSheetDialog; +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; +import java.util.Objects; +import org.gitnex.tea4j.v2.models.CreateReleaseOption; +import org.gitnex.tea4j.v2.models.CreateTagOption; +import org.gitnex.tea4j.v2.models.Release; +import org.mian.gitnex.R; +import org.mian.gitnex.databinding.BottomsheetCreateReleaseBinding; +import org.mian.gitnex.helpers.AlertDialogs; +import org.mian.gitnex.helpers.AppUtil; +import org.mian.gitnex.helpers.Toasty; +import org.mian.gitnex.helpers.contexts.RepositoryContext; +import org.mian.gitnex.viewmodels.ReleasesViewModel; + +/** + * @author mmarif + */ +public class BottomSheetCreateRelease extends BottomSheetDialogFragment { + + private BottomsheetCreateReleaseBinding binding; + private ReleasesViewModel viewModel; + private RepositoryContext repoContext; + private Release releaseToEdit; + private String selectedBranch = null; + private boolean isReleaseMode = true; + + public static BottomSheetCreateRelease newInstance( + RepositoryContext repository, @Nullable Release release) { + BottomSheetCreateRelease fragment = new BottomSheetCreateRelease(); + Bundle args = new Bundle(); + args.putSerializable("repo_context", repository); + if (release != null) { + args.putSerializable("release_item", release); + } + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + repoContext = (RepositoryContext) getArguments().getSerializable("repo_context"); + releaseToEdit = (Release) getArguments().getSerializable("release_item"); + } + } + + @Nullable @Override + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + binding = BottomsheetCreateReleaseBinding.inflate(inflater, container, false); + return binding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + viewModel = new ViewModelProvider(requireActivity()).get(ReleasesViewModel.class); + + viewModel.clearCreatedRelease(); + viewModel.clearCreatedTag(); + viewModel.clearUpdatedRelease(); + + setupUI(); + setupListeners(); + observeViewModel(); + + ViewGroup.LayoutParams editTextParams = binding.releaseContent.getLayoutParams(); + editTextParams.height = (int) (224 * getResources().getDisplayMetrics().density); + binding.releaseContent.setLayoutParams(editTextParams); + } + + private void setupUI() { + boolean hasWriteAccess = + repoContext.getPermissions() != null && repoContext.getPermissions().isPush(); + + binding.cardBranch.cardIcon.setImageResource(R.drawable.ic_branch); + binding.cardBranch.tvCardLabel.setText(R.string.pageTitleChooseBranch); + binding.cardBranch.getRoot().setVisibility(hasWriteAccess ? View.VISIBLE : View.GONE); + + if (releaseToEdit != null) { + binding.sheetTitle.setText(R.string.editRelease); + binding.createTypeToggle.setVisibility(View.GONE); + binding.releaseTagNameLayout.setVisibility(View.GONE); + binding.switchDraft.setVisibility(View.GONE); + binding.descriptionContainer.setVisibility(View.VISIBLE); + binding.switchPrerelease.setVisibility(View.VISIBLE); + + binding.releaseTitle.setText(releaseToEdit.getName()); + binding.releaseContent.setText(releaseToEdit.getBody()); + binding.switchPrerelease.setChecked(releaseToEdit.isPrerelease()); + + binding.cardBranch.getRoot().setVisibility(View.GONE); + + binding.btnSubmit.setText(R.string.update); + } else { + binding.sheetTitle.setText(R.string.createRelease); + binding.descriptionContainer.setVisibility(View.VISIBLE); + binding.switchPrerelease.setVisibility(View.VISIBLE); + binding.switchDraft.setVisibility(View.VISIBLE); + + if (hasWriteAccess) { + updateBranchDisplay(); + } + } + + updateBranchClearButtonVisibility(); + } + + private void setupListeners() { + binding.btnClose.setOnClickListener(v -> dismiss()); + binding.btnExpand.setOnClickListener(v -> openFullScreenEditor()); + binding.btnSubmit.setOnClickListener(v -> submitAction()); + + boolean hasWriteAccess = + repoContext.getPermissions() != null && repoContext.getPermissions().isPush(); + + if (hasWriteAccess) { + binding.cardBranch.getRoot().setOnClickListener(v -> openBranchPicker()); + binding.cardBranch.btnClear.setOnClickListener( + v -> { + selectedBranch = null; + updateBranchDisplay(); + updateBranchClearButtonVisibility(); + }); + } + + binding.createTypeToggle.addOnButtonCheckedListener( + (group, checkedId, isChecked) -> { + if (isChecked) { + isReleaseMode = checkedId == R.id.btn_create_release; + updateUIForMode(); + } + }); + } + + private void updateUIForMode() { + if (releaseToEdit == null) { + if (isReleaseMode) { + binding.releaseTitleLayout.setHint(R.string.releaseTitleText); + binding.descriptionContainer.setVisibility(View.VISIBLE); + binding.switchPrerelease.setVisibility(View.VISIBLE); + binding.switchDraft.setVisibility(View.VISIBLE); + binding.btnSubmit.setText(R.string.createRelease); + } else { + binding.releaseTitleLayout.setHint(R.string.description); + binding.descriptionContainer.setVisibility(View.GONE); + binding.switchPrerelease.setVisibility(View.GONE); + binding.switchDraft.setVisibility(View.GONE); + binding.btnSubmit.setText(R.string.createTag); + } + } + } + + private void openBranchPicker() { + BottomsheetBranchPicker branchPicker = + BottomsheetBranchPicker.newInstance( + repoContext.getOwner(), + repoContext.getName(), + selectedBranch != null ? selectedBranch : repoContext.getBranchRef()); + + branchPicker.setOnBranchSelectedListener( + branchName -> { + selectedBranch = branchName; + updateBranchDisplay(); + updateBranchClearButtonVisibility(); + }); + + branchPicker.show(getParentFragmentManager(), "BRANCH_PICKER"); + } + + private void updateBranchDisplay() { + if (selectedBranch == null || selectedBranch.isEmpty()) { + binding.cardBranch.tvSelectedText.setText(R.string.add_release_branch); + } else { + binding.cardBranch.tvSelectedText.setText(selectedBranch); + } + } + + private void updateBranchClearButtonVisibility() { + binding.cardBranch.btnClear.setVisibility( + selectedBranch == null || selectedBranch.isEmpty() ? View.GONE : View.VISIBLE); + } + + private void openFullScreenEditor() { + BottomSheetFullScreenEditor editorBottomSheet = + BottomSheetFullScreenEditor.newInstance( + Objects.requireNonNull(binding.releaseContent.getText()).toString(), + repoContext, + true, + true); + + editorBottomSheet.setEditorListener( + newContent -> { + binding.releaseContent.setText(newContent); + binding.releaseContent.setSelection( + newContent != null ? newContent.length() : 0); + }); + + editorBottomSheet.show(getParentFragmentManager(), "FULLSCREEN_EDITOR"); + } + + private void submitAction() { + if (releaseToEdit != null) { + submitUpdateRelease(); + } else if (isReleaseMode) { + submitCreateRelease(); + } else { + submitCreateTag(); + } + } + + private void submitCreateRelease() { + String tagName = + binding.releaseTagName.getText() != null + ? binding.releaseTagName.getText().toString().trim() + : ""; + String title = + binding.releaseTitle.getText() != null + ? binding.releaseTitle.getText().toString().trim() + : ""; + String content = + binding.releaseContent.getText() != null + ? binding.releaseContent.getText().toString().trim() + : ""; + + if (tagName.isEmpty()) { + Toasty.show(requireContext(), R.string.tagNameErrorEmpty); + return; + } + + if (title.isEmpty()) { + Toasty.show(requireContext(), R.string.titleErrorEmpty); + return; + } + + if (selectedBranch == null || selectedBranch.isEmpty()) { + Toasty.show(requireContext(), R.string.selectBranchError); + return; + } + + CreateReleaseOption releaseData = new CreateReleaseOption(); + releaseData.setTagName(tagName); + releaseData.setName(title); + releaseData.setName(title); + releaseData.setBody(content); + releaseData.setTargetCommitish(selectedBranch); + releaseData.setDraft(binding.switchDraft.isChecked()); + releaseData.setPrerelease(binding.switchPrerelease.isChecked()); + + viewModel.createRelease( + requireContext(), repoContext.getOwner(), repoContext.getName(), releaseData); + } + + private void submitCreateTag() { + String tagName = + binding.releaseTagName.getText() != null + ? binding.releaseTagName.getText().toString().trim() + : ""; + String message = + binding.releaseTitle.getText() != null + ? binding.releaseTitle.getText().toString().trim() + : ""; + + if (tagName.isEmpty()) { + Toasty.show(requireContext(), R.string.tagNameErrorEmpty); + return; + } + + if (selectedBranch == null || selectedBranch.isEmpty()) { + Toasty.show(requireContext(), R.string.selectBranchError); + return; + } + + CreateTagOption tagData = new CreateTagOption(); + tagData.setTagName(tagName); + tagData.setMessage(message); + tagData.setTarget(selectedBranch); + + viewModel.createTag( + requireContext(), repoContext.getOwner(), repoContext.getName(), tagData); + } + + private void submitUpdateRelease() { + String title = + binding.releaseTitle.getText() != null + ? binding.releaseTitle.getText().toString().trim() + : ""; + String content = + binding.releaseContent.getText() != null + ? binding.releaseContent.getText().toString().trim() + : ""; + + if (title.isEmpty()) { + Toasty.show(requireContext(), R.string.titleErrorEmpty); + return; + } + + viewModel.updateRelease( + requireContext(), + repoContext.getOwner(), + repoContext.getName(), + releaseToEdit.getId(), + title, + content, + binding.switchPrerelease.isChecked()); + } + + private void observeViewModel() { + viewModel + .getIsCreatingRelease() + .observe( + getViewLifecycleOwner(), + isCreating -> { + binding.loadingIndicator.setVisibility( + isCreating ? View.VISIBLE : View.GONE); + binding.btnSubmit.setEnabled(!isCreating); + binding.btnSubmit.setText( + isCreating + ? "" + : getString( + releaseToEdit != null + ? R.string.update + : (isReleaseMode + ? R.string.createRelease + : R.string.createTag))); + }); + + viewModel + .getCreatedRelease() + .observe( + getViewLifecycleOwner(), + release -> { + if (release != null) { + Toasty.show(requireContext(), R.string.releaseCreatedText); + dismiss(); + } + }); + + viewModel + .getCreateReleaseError() + .observe( + getViewLifecycleOwner(), + error -> { + if (error != null && !error.isEmpty()) { + handleError(error); + viewModel.clearCreateReleaseError(); + } + }); + + viewModel + .getIsCreatingTag() + .observe( + getViewLifecycleOwner(), + isCreating -> { + if (!isReleaseMode) { + binding.loadingIndicator.setVisibility( + isCreating ? View.VISIBLE : View.GONE); + binding.btnSubmit.setEnabled(!isCreating); + binding.btnSubmit.setText( + isCreating ? "" : getString(R.string.createTag)); + } + }); + + viewModel + .getCreatedTag() + .observe( + getViewLifecycleOwner(), + tag -> { + if (tag != null) { + Toasty.show(requireContext(), R.string.tagCreated); + dismiss(); + } + }); + + viewModel + .getCreateTagError() + .observe( + getViewLifecycleOwner(), + error -> { + if (error != null && !error.isEmpty()) { + handleError(error); + viewModel.clearCreateTagError(); + } + }); + + viewModel + .getIsUpdatingRelease() + .observe( + getViewLifecycleOwner(), + isUpdating -> { + if (releaseToEdit != null) { + binding.loadingIndicator.setVisibility( + isUpdating ? View.VISIBLE : View.GONE); + binding.btnSubmit.setEnabled(!isUpdating); + binding.btnSubmit.setText( + isUpdating ? "" : getString(R.string.update)); + } + }); + + viewModel + .getUpdatedRelease() + .observe( + getViewLifecycleOwner(), + release -> { + if (release != null) { + Toasty.show(requireContext(), R.string.editReleaseSuccessMessage); + dismiss(); + } + }); + + viewModel + .getUpdateReleaseError() + .observe( + getViewLifecycleOwner(), + error -> { + if (error != null && !error.isEmpty()) { + handleError(error); + viewModel.clearUpdateReleaseError(); + } + }); + } + + private void handleError(String error) { + if (error.equals("UNAUTHORIZED")) { + AlertDialogs.authorizationTokenRevokedDialog(requireContext()); + } else if (error.equals(getString(R.string.tagNameConflictError))) { + Toasty.show(requireContext(), R.string.tagNameConflictError); + } else { + Toasty.show(requireContext(), error); + } + } + + @Override + public void onStart() { + super.onStart(); + Dialog dialog = getDialog(); + if (dialog instanceof BottomSheetDialog) { + AppUtil.applyFullScreenSheetStyle((BottomSheetDialog) dialog, false); + } + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + binding = null; + } +} diff --git a/app/src/main/java/org/mian/gitnex/fragments/BottomSheetFullScreenEditor.java b/app/src/main/java/org/mian/gitnex/fragments/BottomSheetFullScreenEditor.java index 3c063b69..bd905d4c 100644 --- a/app/src/main/java/org/mian/gitnex/fragments/BottomSheetFullScreenEditor.java +++ b/app/src/main/java/org/mian/gitnex/fragments/BottomSheetFullScreenEditor.java @@ -122,8 +122,6 @@ public class BottomSheetFullScreenEditor extends BottomSheetDialogFragment { isMarkdownMode = !isMarkdownMode; binding.fullscreenBtnMarkdown.setIconResource( isMarkdownMode ? R.drawable.ic_edit : R.drawable.ic_markdown); - binding.fullscreenBtnMarkdown.setText( - isMarkdownMode ? R.string.menuEditText : R.string.strMarkdown); binding.fullscreenBtnMarkdown.setIconSize(52); if (isMarkdownMode) { diff --git a/app/src/main/java/org/mian/gitnex/fragments/ReleasesFragment.java b/app/src/main/java/org/mian/gitnex/fragments/ReleasesFragment.java index 9347b2d4..62a4874f 100644 --- a/app/src/main/java/org/mian/gitnex/fragments/ReleasesFragment.java +++ b/app/src/main/java/org/mian/gitnex/fragments/ReleasesFragment.java @@ -39,7 +39,6 @@ import org.gitnex.tea4j.v2.models.Release; import org.gitnex.tea4j.v2.models.Tag; import org.mian.gitnex.R; import org.mian.gitnex.activities.BaseActivity; -import org.mian.gitnex.activities.CreateReleaseActivity; import org.mian.gitnex.activities.RepoDetailActivity; import org.mian.gitnex.adapters.ReleasesAdapter; import org.mian.gitnex.adapters.TagsAdapter; @@ -108,7 +107,7 @@ public class ReleasesFragment extends Fragment implements RepoDetailActivity.Rep @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { binding = FragmentReleasesBinding.inflate(inflater, container, false); - viewModel = new ViewModelProvider(this).get(ReleasesViewModel.class); + viewModel = new ViewModelProvider(requireActivity()).get(ReleasesViewModel.class); resultLimit = Constants.getCurrentResultLimit(requireContext()); @@ -138,7 +137,7 @@ public class ReleasesFragment extends Fragment implements RepoDetailActivity.Rep items.add( new RepositoryMenuItemModel( "RELEASE_CREATE_NEW", - R.string.createRelease, + R.string.create_release_tag, R.drawable.ic_add, R.attr.colorPrimaryContainer, R.attr.colorOnPrimaryContainer)); @@ -156,7 +155,8 @@ public class ReleasesFragment extends Fragment implements RepoDetailActivity.Rep break; case "RELEASE_CREATE_NEW": - startActivity(repository.getIntent(requireContext(), CreateReleaseActivity.class)); + BottomSheetCreateRelease.newInstance(repository, null) + .show(getChildFragmentManager(), "CREATE_RELEASE"); break; } } @@ -295,14 +295,17 @@ public class ReleasesFragment extends Fragment implements RepoDetailActivity.Rep .observe( getViewLifecycleOwner(), code -> { - if (code == -1) return; + if (code == null || code == -1) return; + if (code == 204) { int messageRes = repository.isReleasesViewTypeIsTag() ? R.string.tagDeleted : R.string.releaseDeleted; - Toasty.show(requireContext(), messageRes); + refreshData(); + } else if (code == 200 || code == 201) { + refreshData(); } else { Toasty.show(requireContext(), R.string.genericError); } @@ -334,10 +337,9 @@ public class ReleasesFragment extends Fragment implements RepoDetailActivity.Rep requireContext(), list, canDelete, - new OnReleaseItemClickListener() { + new ReleasesAdapter.OnReleaseItemClickListener() { @Override - public void onDelete(Object item, int position) { - Release release = (Release) item; + public void onMenuClick(Release release, int position) { showReleaseOptionsBottomSheet(release, position); } @@ -382,6 +384,13 @@ public class ReleasesFragment extends Fragment implements RepoDetailActivity.Rep .show(); }); + menuBinding.editRelease.setOnClickListener( + v -> { + dialog.dismiss(); + BottomSheetCreateRelease.newInstance(repository, release) + .show(getParentFragmentManager(), "EDIT_RELEASE"); + }); + dialog.show(); } diff --git a/app/src/main/java/org/mian/gitnex/fragments/WikiFragment.java b/app/src/main/java/org/mian/gitnex/fragments/WikiFragment.java index 984959c7..6fc28dda 100644 --- a/app/src/main/java/org/mian/gitnex/fragments/WikiFragment.java +++ b/app/src/main/java/org/mian/gitnex/fragments/WikiFragment.java @@ -1,5 +1,8 @@ package org.mian.gitnex.fragments; +import static android.view.View.GONE; +import static android.view.View.VISIBLE; + import android.content.Intent; import android.os.Bundle; import android.view.LayoutInflater; @@ -22,6 +25,7 @@ import org.mian.gitnex.activities.WikiActivity; import org.mian.gitnex.adapters.WikiListAdapter; import org.mian.gitnex.databinding.BottomsheetWikiItemMenuBinding; import org.mian.gitnex.databinding.FragmentWikiBinding; +import org.mian.gitnex.helpers.AlertDialogs; import org.mian.gitnex.helpers.AppUtil; import org.mian.gitnex.helpers.Constants; import org.mian.gitnex.helpers.EndlessRecyclerViewScrollListener; @@ -43,6 +47,7 @@ public class WikiFragment extends Fragment implements RepoDetailActivity.RepoHub private RepositoryContext repository; private int resultLimit; private boolean isFirstLoad = true; + private String pendingPageName = null; public static WikiFragment newInstance(RepositoryContext repository) { WikiFragment fragment = new WikiFragment(); @@ -197,14 +202,73 @@ public class WikiFragment extends Fragment implements RepoDetailActivity.RepoHub err -> { if (err != null) Toasty.show(requireContext(), err); }); + + viewModel + .getIsLoadingPage() + .observe( + getViewLifecycleOwner(), + isLoading -> { + binding.expressiveLoader.setVisibility(VISIBLE); + }); + + viewModel + .getPageContent() + .observe( + getViewLifecycleOwner(), + content -> { + if (content != null && pendingPageName != null) { + showContentViewer(pendingPageName, content); + pendingPageName = null; + viewModel.clearPageContent(); + binding.expressiveLoader.setVisibility(GONE); + } + }); + + viewModel + .getPageError() + .observe( + getViewLifecycleOwner(), + error -> { + if (error != null && !error.isEmpty()) { + if (error.equals("UNAUTHORIZED")) { + AlertDialogs.authorizationTokenRevokedDialog(requireContext()); + } else { + Toasty.show(requireContext(), error); + } + pendingPageName = null; + viewModel.clearPageError(); + } + }); } private void openWiki(WikiPageMetaData wikiPage, String action) { - Intent intent = new Intent(requireContext(), WikiActivity.class); - intent.putExtra("pageName", wikiPage.getTitle()); - if (action != null) intent.putExtra("action", action); - intent.putExtra(RepositoryContext.INTENT_EXTRA, repository); - startActivity(intent); + if (action != null && action.equals("edit")) { + Intent intent = new Intent(requireContext(), WikiActivity.class); + intent.putExtra("pageName", wikiPage.getSubUrl()); + intent.putExtra("action", action); + intent.putExtra(RepositoryContext.INTENT_EXTRA, repository); + startActivity(intent); + } else { + pendingPageName = wikiPage.getTitle(); + viewModel.fetchWikiPageContent( + requireContext(), + repository.getOwner(), + repository.getName(), + wikiPage.getSubUrl()); + } + } + + private void showContentViewer(String title, String content) { + BottomSheetContentViewer.newInstance( + content, + title, + repository, + BottomSheetContentViewer.Feature.ALLOW_COPY, + BottomSheetContentViewer.Feature.ALLOW_SHARE, + BottomSheetContentViewer.Feature.MARKDOWN_PREVIEW, + BottomSheetContentViewer.Feature.START_IN_MARKDOWN, + BottomSheetContentViewer.Feature.SHOW_TITLE) + .show(getParentFragmentManager(), "WIKI_VIEWER"); } private void showDeleteDialog(WikiPageMetaData wikiPage) { @@ -219,7 +283,7 @@ public class WikiFragment extends Fragment implements RepoDetailActivity.RepoHub requireContext(), repository.getOwner(), repository.getName(), - wikiPage.getTitle()); + wikiPage.getSubUrl()); }) .setNegativeButton(R.string.cancelButton, null) .show(); @@ -253,14 +317,12 @@ public class WikiFragment extends Fragment implements RepoDetailActivity.RepoHub boolean hasData = adapter != null && adapter.getItemCount() > 0; boolean hasLoadedOnce = Boolean.TRUE.equals(viewModel.getHasLoadedOnce().getValue()); - binding.expressiveLoader.setVisibility(isLoading && !hasData ? View.VISIBLE : View.GONE); + binding.expressiveLoader.setVisibility(isLoading && !hasData ? VISIBLE : GONE); if (isLoading) { - binding.layoutEmpty.getRoot().setVisibility(View.GONE); + binding.layoutEmpty.getRoot().setVisibility(GONE); } else { - binding.layoutEmpty - .getRoot() - .setVisibility(!hasData && hasLoadedOnce ? View.VISIBLE : View.GONE); + binding.layoutEmpty.getRoot().setVisibility(!hasData && hasLoadedOnce ? VISIBLE : GONE); } } diff --git a/app/src/main/java/org/mian/gitnex/viewmodels/ReleasesViewModel.java b/app/src/main/java/org/mian/gitnex/viewmodels/ReleasesViewModel.java index 1ba65b33..81e269d4 100644 --- a/app/src/main/java/org/mian/gitnex/viewmodels/ReleasesViewModel.java +++ b/app/src/main/java/org/mian/gitnex/viewmodels/ReleasesViewModel.java @@ -8,8 +8,12 @@ import androidx.lifecycle.ViewModel; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import org.gitnex.tea4j.v2.models.CreateReleaseOption; +import org.gitnex.tea4j.v2.models.CreateTagOption; +import org.gitnex.tea4j.v2.models.EditReleaseOption; import org.gitnex.tea4j.v2.models.Release; import org.gitnex.tea4j.v2.models.Tag; +import org.mian.gitnex.R; import org.mian.gitnex.clients.RetrofitClient; import retrofit2.Call; import retrofit2.Callback; @@ -28,6 +32,15 @@ public class ReleasesViewModel extends ViewModel { private final MutableLiveData isTagsLoading = new MutableLiveData<>(false); private final MutableLiveData actionResult = new MutableLiveData<>(-1); private final MutableLiveData repoReleasesCountLiveData = new MutableLiveData<>(-1); + private final MutableLiveData isCreatingRelease = new MutableLiveData<>(false); + private final MutableLiveData isCreatingTag = new MutableLiveData<>(false); + private final MutableLiveData isUpdatingRelease = new MutableLiveData<>(false); + private final MutableLiveData createdRelease = new MutableLiveData<>(); + private final MutableLiveData createdTag = new MutableLiveData<>(); + private final MutableLiveData updatedRelease = new MutableLiveData<>(); + private final MutableLiveData createReleaseError = new MutableLiveData<>(); + private final MutableLiveData createTagError = new MutableLiveData<>(); + private final MutableLiveData updateReleaseError = new MutableLiveData<>(); private int totalCount = -1; private boolean isLastPage = false; @@ -62,6 +75,42 @@ public class ReleasesViewModel extends ViewModel { return repoReleasesCountLiveData; } + public LiveData getIsCreatingRelease() { + return isCreatingRelease; + } + + public LiveData getIsCreatingTag() { + return isCreatingTag; + } + + public LiveData getIsUpdatingRelease() { + return isUpdatingRelease; + } + + public LiveData getCreatedRelease() { + return createdRelease; + } + + public LiveData getCreatedTag() { + return createdTag; + } + + public LiveData getUpdatedRelease() { + return updatedRelease; + } + + public LiveData getCreateReleaseError() { + return createReleaseError; + } + + public LiveData getCreateTagError() { + return createTagError; + } + + public LiveData getUpdateReleaseError() { + return updateReleaseError; + } + public void resetPagination() { this.isLastPage = false; this.totalCount = -1; @@ -75,6 +124,30 @@ public class ReleasesViewModel extends ViewModel { this.tags.setValue(null); } + public void clearCreatedRelease() { + createdRelease.setValue(null); + } + + public void clearCreatedTag() { + createdTag.setValue(null); + } + + public void clearUpdatedRelease() { + updatedRelease.setValue(null); + } + + public void clearCreateReleaseError() { + createReleaseError.setValue(null); + } + + public void clearCreateTagError() { + createTagError.setValue(null); + } + + public void clearUpdateReleaseError() { + updateReleaseError.setValue(null); + } + public void resetActionResult() { actionResult.setValue(-1); } @@ -266,4 +339,124 @@ public class ReleasesViewModel extends ViewModel { } }); } + + public void createRelease( + Context ctx, String owner, String repo, CreateReleaseOption releaseData) { + isCreatingRelease.setValue(true); + + Call call = + RetrofitClient.getApiInterface(ctx).repoCreateRelease(owner, repo, releaseData); + + call.enqueue( + new Callback<>() { + @Override + public void onResponse( + @NonNull Call call, @NonNull Response response) { + isCreatingRelease.setValue(false); + if (response.isSuccessful() && response.body() != null) { + createdRelease.setValue(response.body()); + actionResult.setValue(201); + } else if (response.code() == 401) { + createReleaseError.setValue("UNAUTHORIZED"); + } else if (response.code() == 403) { + createReleaseError.setValue(ctx.getString(R.string.authorizeError)); + } else if (response.code() == 404) { + createReleaseError.setValue(ctx.getString(R.string.apiNotFound)); + } else if (response.code() == 409) { + createReleaseError.setValue( + ctx.getString(R.string.tagNameConflictError)); + } else { + createReleaseError.setValue(ctx.getString(R.string.genericError)); + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + isCreatingRelease.setValue(false); + createReleaseError.setValue(t.getMessage()); + } + }); + } + + public void createTag(Context ctx, String owner, String repo, CreateTagOption tagData) { + isCreatingTag.setValue(true); + + Call call = RetrofitClient.getApiInterface(ctx).repoCreateTag(owner, repo, tagData); + + call.enqueue( + new Callback<>() { + @Override + public void onResponse( + @NonNull Call call, @NonNull Response response) { + isCreatingTag.setValue(false); + if (response.isSuccessful() && response.body() != null) { + createdTag.setValue(response.body()); + actionResult.setValue(201); + } else if (response.code() == 401) { + createTagError.setValue("UNAUTHORIZED"); + } else if (response.code() == 403) { + createTagError.setValue(ctx.getString(R.string.authorizeError)); + } else if (response.code() == 404) { + createTagError.setValue(ctx.getString(R.string.apiNotFound)); + } else if (response.code() == 409) { + createTagError.setValue(ctx.getString(R.string.tagNameConflictError)); + } else { + createTagError.setValue(ctx.getString(R.string.genericError)); + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + isCreatingTag.setValue(false); + createTagError.setValue(t.getMessage()); + } + }); + } + + public void updateRelease( + Context ctx, + String owner, + String repo, + long releaseId, + String name, + String body, + boolean prerelease) { + isUpdatingRelease.setValue(true); + + EditReleaseOption editData = new EditReleaseOption(); + editData.setName(name); + editData.setBody(body); + editData.setPrerelease(prerelease); + + Call call = + RetrofitClient.getApiInterface(ctx) + .repoEditRelease(owner, repo, releaseId, editData); + + call.enqueue( + new Callback() { + @Override + public void onResponse( + @NonNull Call call, @NonNull Response response) { + isUpdatingRelease.setValue(false); + if (response.isSuccessful() && response.body() != null) { + updatedRelease.setValue(response.body()); + actionResult.setValue(200); + } else if (response.code() == 401) { + updateReleaseError.setValue("UNAUTHORIZED"); + } else if (response.code() == 403) { + updateReleaseError.setValue(ctx.getString(R.string.authorizeError)); + } else if (response.code() == 404) { + updateReleaseError.setValue(ctx.getString(R.string.apiNotFound)); + } else { + updateReleaseError.setValue(ctx.getString(R.string.genericError)); + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + isUpdatingRelease.setValue(false); + updateReleaseError.setValue(t.getMessage()); + } + }); + } } diff --git a/app/src/main/java/org/mian/gitnex/viewmodels/WikiViewModel.java b/app/src/main/java/org/mian/gitnex/viewmodels/WikiViewModel.java index 66463344..ce4747bf 100644 --- a/app/src/main/java/org/mian/gitnex/viewmodels/WikiViewModel.java +++ b/app/src/main/java/org/mian/gitnex/viewmodels/WikiViewModel.java @@ -7,8 +7,11 @@ import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; import java.util.ArrayList; import java.util.List; +import org.gitnex.tea4j.v2.models.WikiPage; import org.gitnex.tea4j.v2.models.WikiPageMetaData; +import org.mian.gitnex.R; import org.mian.gitnex.clients.RetrofitClient; +import org.mian.gitnex.helpers.AppUtil; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; @@ -23,6 +26,9 @@ public class WikiViewModel extends ViewModel { private final MutableLiveData hasLoadedOnce = new MutableLiveData<>(false); private final MutableLiveData error = new MutableLiveData<>(); private final MutableLiveData actionResult = new MutableLiveData<>(-1); + private final MutableLiveData isLoadingPage = new MutableLiveData<>(false); + private final MutableLiveData pageContent = new MutableLiveData<>(); + private final MutableLiveData pageError = new MutableLiveData<>(); private final List fullList = new ArrayList<>(); @@ -49,6 +55,18 @@ public class WikiViewModel extends ViewModel { return actionResult; } + public LiveData getIsLoadingPage() { + return isLoadingPage; + } + + public LiveData getPageContent() { + return pageContent; + } + + public LiveData getPageError() { + return pageError; + } + public void resetPagination() { fullList.clear(); isLastPage = false; @@ -57,6 +75,14 @@ public class WikiViewModel extends ViewModel { hasLoadedOnce.setValue(false); } + public void clearPageContent() { + pageContent.setValue(null); + } + + public void clearPageError() { + pageError.setValue(null); + } + public void fetchWikiPages( Context ctx, String owner, String repo, int page, int limit, boolean isRefresh) { if (Boolean.TRUE.equals(isLoading.getValue()) && !isRefresh) return; @@ -120,6 +146,50 @@ public class WikiViewModel extends ViewModel { }); } + public void fetchWikiPageContent(Context ctx, String owner, String repo, String pageName) { + isLoadingPage.setValue(true); + pageError.setValue(null); + + Call call = + RetrofitClient.getApiInterface(ctx).repoGetWikiPage(owner, repo, pageName); + + call.enqueue( + new Callback<>() { + @Override + public void onResponse( + @NonNull Call call, @NonNull Response response) { + isLoadingPage.setValue(false); + + if (response.isSuccessful() && response.body() != null) { + WikiPage wikiPage = response.body(); + String decodedContent = + AppUtil.decodeBase64(wikiPage.getContentBase64()); + pageContent.setValue(decodedContent); + } else { + switch (response.code()) { + case 401: + pageError.setValue("UNAUTHORIZED"); + break; + case 403: + pageError.setValue(ctx.getString(R.string.authorizeError)); + break; + case 404: + pageError.setValue(ctx.getString(R.string.apiNotFound)); + break; + default: + pageError.setValue(ctx.getString(R.string.genericError)); + } + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + isLoadingPage.setValue(false); + pageError.setValue(t.getMessage()); + } + }); + } + public void deleteWikiPage(Context ctx, String owner, String repo, String pageName) { isLoading.setValue(true); RetrofitClient.getApiInterface(ctx) diff --git a/app/src/main/res/layout/activity_create_release.xml b/app/src/main/res/layout/activity_create_release.xml deleted file mode 100644 index 8884d104..00000000 --- a/app/src/main/res/layout/activity_create_release.xml +++ /dev/null @@ -1,196 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/bottomsheet_content_viewer.xml b/app/src/main/res/layout/bottomsheet_content_viewer.xml new file mode 100644 index 00000000..20bfa48e --- /dev/null +++ b/app/src/main/res/layout/bottomsheet_content_viewer.xml @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/bottomsheet_create_release.xml b/app/src/main/res/layout/bottomsheet_create_release.xml new file mode 100644 index 00000000..c5db4b68 --- /dev/null +++ b/app/src/main/res/layout/bottomsheet_create_release.xml @@ -0,0 +1,252 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/bottomsheet_fullscreen_editor.xml b/app/src/main/res/layout/bottomsheet_fullscreen_editor.xml index 72c6674a..5d8f73b7 100644 --- a/app/src/main/res/layout/bottomsheet_fullscreen_editor.xml +++ b/app/src/main/res/layout/bottomsheet_fullscreen_editor.xml @@ -2,62 +2,84 @@ + android:layout_height="match_parent"> - + - + android:paddingVertical="@dimen/dimen2dp" + android:paddingHorizontal="@dimen/dimen8dp"> - + - + - + - + + + + + + + android:textAppearance="@style/TextAppearance.Material3.BodyLarge" /> + + + + + + + + + + + + - - - - - - - - - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index dea9d7e9..feb19024 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -321,8 +321,6 @@ Desc Label deleted - Select a branch for release - Authorization Error It seems that the Access Token is revoked OR you are not allowed to see these contents.\n\nIn case of revoked Token, please update the account. Do you really want to delete this label? @@ -465,7 +463,6 @@ - New Release Tag Name Title Content @@ -476,6 +473,14 @@ New release created Do you really want to delete this release? Release deleted + Tag name already exists + Release updated successfully + Create Release + Create Tag + Edit Release + Select a branch for release/tag + Tap to select a branch + Create Release/Tag Open in Browser @@ -655,6 +660,7 @@ Select Collapse Fullscreen + Type Explore users @@ -908,7 +914,7 @@ Avatar Tags Releases/Tags - Create Tag Only + Tag Only Tag created Use as reference Do you really want to delete this tag?