diff --git a/app/src/main/java/org/mian/gitnex/adapters/ReleasesAdapter.java b/app/src/main/java/org/mian/gitnex/adapters/ReleasesAdapter.java index 9df4091e..bcfc4e70 100644 --- a/app/src/main/java/org/mian/gitnex/adapters/ReleasesAdapter.java +++ b/app/src/main/java/org/mian/gitnex/adapters/ReleasesAdapter.java @@ -1,8 +1,8 @@ package org.mian.gitnex.adapters; +import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; -import android.text.method.LinkMovementMethod; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -10,7 +10,6 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import androidx.annotation.NonNull; -import androidx.core.text.HtmlCompat; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import org.gitnex.tea4j.v2.models.Release; @@ -23,11 +22,12 @@ import org.mian.gitnex.helpers.Markdown; import org.mian.gitnex.helpers.RoundedTransformation; import org.mian.gitnex.helpers.TimeHelper; import org.mian.gitnex.helpers.TinyDB; +import org.mian.gitnex.structs.FragmentRefreshListener; import java.util.List; import java.util.Locale; /** - * Author M M Arif + * @author M M Arif */ public class ReleasesAdapter extends RecyclerView.Adapter { @@ -37,6 +37,7 @@ public class ReleasesAdapter extends RecyclerView.Adapter releasesMain) { + public ReleasesAdapter(Context ctx, List releasesMain, FragmentRefreshListener startDownload) { this.context = ctx; this.releasesList = releasesMain; + this.startDownload = startDownload; } @NonNull @@ -151,7 +153,7 @@ public class ReleasesAdapter extends RecyclerView.Adapter { + holder.downloadCopyFrame.setOnClickListener(v -> { if(holder.downloads.getVisibility() == View.GONE) { @@ -166,15 +168,10 @@ public class ReleasesAdapter extends RecyclerView.Adapter" + context.getResources().getString(R.string.zipArchiveDownloadReleasesTab) + " ", HtmlCompat.FROM_HTML_MODE_LEGACY)); - holder.releaseZipDownload.setMovementMethod(LinkMovementMethod.getInstance()); + holder.releaseZipDownloadFrame.setOnClickListener(v -> startDownload.onRefresh(currentItem.getZipballUrl())); + holder.releaseTarDownloadFrame.setOnClickListener(v -> startDownload.onRefresh(currentItem.getTarballUrl())); - holder.releaseTarDownload.setText( - HtmlCompat.fromHtml("" + context.getResources().getString(R.string.tarArchiveDownloadReleasesTab) + " ", HtmlCompat.FROM_HTML_MODE_LEGACY)); - holder.releaseTarDownload.setMovementMethod(LinkMovementMethod.getInstance()); - - ReleasesDownloadsAdapter adapter = new ReleasesDownloadsAdapter(currentItem.getAssets()); + ReleasesDownloadsAdapter adapter = new ReleasesDownloadsAdapter(currentItem.getAssets(), startDownload); holder.downloadList.setAdapter(adapter); if(position >= getItemCount() - 1 && isMoreDataAvailable && !isLoading && loadMoreListener != null) { @@ -195,6 +192,7 @@ public class ReleasesAdapter extends RecyclerView.Adapter { private final List releasesDownloadsList; + private final FragmentRefreshListener startDownload; static class ReleasesDownloadsViewHolder extends RecyclerView.ViewHolder { @@ -31,9 +31,10 @@ public class ReleasesDownloadsAdapter extends RecyclerView.Adapter releasesDownloadsMain) { + ReleasesDownloadsAdapter(List releasesDownloadsMain, FragmentRefreshListener startDownload) { this.releasesDownloadsList = releasesDownloadsMain; + this.startDownload = startDownload; } @NonNull @@ -50,9 +51,8 @@ public class ReleasesDownloadsAdapter extends RecyclerView.Adapter" + currentItem.getName() + " ", HtmlCompat.FROM_HTML_MODE_LEGACY)); - holder.downloadName.setMovementMethod(LinkMovementMethod.getInstance()); + holder.downloadName.setText(currentItem.getName()); + holder.downloadName.setOnClickListener(v -> startDownload.onRefresh(currentItem.getBrowserDownloadUrl())); } } diff --git a/app/src/main/java/org/mian/gitnex/adapters/TagsAdapter.java b/app/src/main/java/org/mian/gitnex/adapters/TagsAdapter.java index 9664a5b9..7f14bd1d 100644 --- a/app/src/main/java/org/mian/gitnex/adapters/TagsAdapter.java +++ b/app/src/main/java/org/mian/gitnex/adapters/TagsAdapter.java @@ -2,7 +2,6 @@ package org.mian.gitnex.adapters; import android.annotation.SuppressLint; import android.content.Context; -import android.text.method.LinkMovementMethod; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -11,7 +10,6 @@ import android.widget.LinearLayout; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; -import androidx.core.text.HtmlCompat; import androidx.recyclerview.widget.RecyclerView; import com.google.android.material.bottomsheet.BottomSheetDialog; import org.gitnex.tea4j.v2.models.Tag; @@ -20,6 +18,7 @@ import org.mian.gitnex.activities.RepoDetailActivity; import org.mian.gitnex.clients.RetrofitClient; import org.mian.gitnex.helpers.Markdown; import org.mian.gitnex.helpers.Toasty; +import org.mian.gitnex.structs.FragmentRefreshListener; import java.util.List; import retrofit2.Call; import retrofit2.Callback; @@ -33,8 +32,9 @@ public class TagsAdapter extends RecyclerView.Adapter tags; private final Context context; - private static String repo; - private static String owner; + private final String repo; + private final String owner; + private final FragmentRefreshListener startDownload; private OnLoadMoreListener loadMoreListener; private boolean isLoading = false, isMoreDataAvailable = true; @@ -44,10 +44,10 @@ public class TagsAdapter extends RecyclerView.Adapter releasesMain, String repoOwner, String repoName) { + public TagsAdapter(Context ctx, List releasesMain, String repoOwner, String repoName, FragmentRefreshListener startDownload) { this.context = ctx; this.tags = releasesMain; owner = repoOwner; repo = repoName; + this.startDownload = startDownload; } @NonNull @@ -113,7 +114,7 @@ public class TagsAdapter extends RecyclerView.Adapter { + holder.downloadCopyFrame.setOnClickListener(v -> { if(holder.downloads.getVisibility() == View.GONE) { @@ -131,13 +132,8 @@ public class TagsAdapter extends RecyclerView.Adapter" + context.getResources().getString(R.string.zipArchiveDownloadReleasesTab) + " ", HtmlCompat.FROM_HTML_MODE_LEGACY)); - holder.releaseZipDownload.setMovementMethod(LinkMovementMethod.getInstance()); - - holder.releaseTarDownload.setText( - HtmlCompat.fromHtml("" + context.getResources().getString(R.string.tarArchiveDownloadReleasesTab) + " ", HtmlCompat.FROM_HTML_MODE_LEGACY)); - holder.releaseTarDownload.setMovementMethod(LinkMovementMethod.getInstance()); + holder.releaseZipDownloadFrame.setOnClickListener(v -> startDownload.onRefresh(currentItem.getZipballUrl())); + holder.releaseTarDownloadFrame.setOnClickListener(v -> startDownload.onRefresh(currentItem.getTarballUrl())); if(position >= getItemCount() - 1 && isMoreDataAvailable && !isLoading && loadMoreListener != null) { isLoading = true; 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 17491776..53b08dca 100644 --- a/app/src/main/java/org/mian/gitnex/fragments/ReleasesFragment.java +++ b/app/src/main/java/org/mian/gitnex/fragments/ReleasesFragment.java @@ -1,5 +1,10 @@ package org.mian.gitnex.fragments; +import android.app.Activity; +import android.app.NotificationManager; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -8,11 +13,15 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.View; import android.view.ViewGroup; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; +import androidx.core.app.NotificationCompat; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.DividerItemDecoration; import androidx.recyclerview.widget.LinearLayoutManager; +import org.gitnex.tea4j.v2.auth.ApiKeyAuth; import org.gitnex.tea4j.v2.models.Release; import org.mian.gitnex.R; import org.mian.gitnex.activities.BaseActivity; @@ -20,9 +29,22 @@ import org.mian.gitnex.activities.RepoDetailActivity; import org.mian.gitnex.adapters.ReleasesAdapter; import org.mian.gitnex.adapters.TagsAdapter; import org.mian.gitnex.databinding.FragmentReleasesBinding; +import org.mian.gitnex.helpers.AppUtil; +import org.mian.gitnex.helpers.Constants; import org.mian.gitnex.helpers.contexts.RepositoryContext; +import org.mian.gitnex.helpers.ssl.MemorizingTrustManager; +import org.mian.gitnex.notifications.Notifications; import org.mian.gitnex.viewmodels.ReleasesViewModel; +import java.io.IOException; +import java.io.OutputStream; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; import java.util.List; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.X509TrustManager; +import okhttp3.*; /** * @author M M Arif @@ -39,6 +61,8 @@ public class ReleasesFragment extends Fragment { private int page = 1; private int pageReleases = 1; + public static String currentDownloadUrl = null; + public ReleasesFragment() { } @@ -104,7 +128,7 @@ public class ReleasesFragment extends Fragment { releasesModel.getReleasesList(owner, repo, getContext()).observe(getViewLifecycleOwner(), releasesListMain -> { if(!repository.isReleasesViewTypeIsTag()) { - adapter = new ReleasesAdapter(getContext(), releasesListMain); + adapter = new ReleasesAdapter(getContext(), releasesListMain, this::requestFileDownload); adapter.setLoadMoreListener(new ReleasesAdapter.OnLoadMoreListener() { @Override @@ -141,7 +165,7 @@ public class ReleasesFragment extends Fragment { releasesModel.getTagsList(owner, repo, getContext()).observe(getViewLifecycleOwner(), tagList -> { if(repository.isReleasesViewTypeIsTag()) { - tagsAdapter = new TagsAdapter(getContext(), tagList, owner, repo); + tagsAdapter = new TagsAdapter(getContext(), tagList, owner, repo, this::requestFileDownload); tagsAdapter.setLoadMoreListener(new TagsAdapter.OnLoadMoreListener() { @Override @@ -187,4 +211,78 @@ public class ReleasesFragment extends Fragment { inflater.inflate(R.menu.filter_menu_releases, menu); super.onCreateOptionsMenu(menu, inflater); } + + private void requestFileDownload(String url) { + currentDownloadUrl = url; + + Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.putExtra(Intent.EXTRA_TITLE, Uri.parse(url).getLastPathSegment()); + intent.setType("*/*"); + downloadLauncher.launch(intent); + } + + ActivityResultLauncher downloadLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { + + if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) { + + try { + + NotificationCompat.Builder builder = new NotificationCompat.Builder(requireContext(), requireContext().getPackageName()) + .setContentTitle(getString(R.string.fileViewerNotificationTitleStarted)) + .setContentText(getString(R.string.fileViewerNotificationDescriptionStarted, Uri.parse(currentDownloadUrl).getLastPathSegment())) + .setSmallIcon(R.drawable.gitnex_transparent).setPriority(NotificationCompat.PRIORITY_LOW) + .setChannelId(Constants.downloadNotificationChannelId).setOngoing(true); + + int notificationId = Notifications.uniqueNotificationId(requireContext()); + + NotificationManager notificationManager = (NotificationManager) requireContext().getSystemService(Context.NOTIFICATION_SERVICE); + notificationManager.notify(notificationId, builder.build()); + + SSLContext sslContext = SSLContext.getInstance("TLS"); + MemorizingTrustManager memorizingTrustManager = new MemorizingTrustManager(requireContext()); + sslContext.init(null, new X509TrustManager[]{memorizingTrustManager}, new SecureRandom()); + + ApiKeyAuth auth = new ApiKeyAuth("header", "Authorization"); + auth.setApiKey(((BaseActivity) requireActivity()).getAccount().getWebAuthorization()); + OkHttpClient okHttpClient = new OkHttpClient.Builder().addInterceptor(auth) + .sslSocketFactory(sslContext.getSocketFactory(), memorizingTrustManager) + .hostnameVerifier(memorizingTrustManager.wrapHostnameVerifier(HttpsURLConnection.getDefaultHostnameVerifier())).build(); + + okHttpClient.newCall(new Request.Builder().url(currentDownloadUrl).build()).enqueue(new Callback() { + + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + + builder.setContentTitle(getString(R.string.fileViewerNotificationTitleFailed)) + .setContentText(getString(R.string.fileViewerNotificationDescriptionFailed, + Uri.parse(currentDownloadUrl).getLastPathSegment())).setOngoing(false); + notificationManager.notify(notificationId, builder.build()); + } + + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { + + if(!response.isSuccessful() || response.body() == null) { + onFailure(call, new IOException()); + return; + } + + OutputStream outputStream = requireContext().getContentResolver().openOutputStream(result.getData().getData()); + + AppUtil.copyProgress(response.body().byteStream(), outputStream, 0, p -> {}); + builder.setContentTitle(getString(R.string.fileViewerNotificationTitleFinished)) + .setContentText(getString(R.string.fileViewerNotificationDescriptionFinished, + Uri.parse(currentDownloadUrl).getLastPathSegment())).setOngoing(false); + notificationManager.notify(notificationId, builder.build()); + } + }); + } + catch(NoSuchAlgorithmException | KeyManagementException e) { + throw new RuntimeException(e); + } + } + + }); + } diff --git a/app/src/main/res/drawable/shape_draft_release.xml b/app/src/main/res/drawable/shape_draft_release.xml index 65501306..66e02aef 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="18dp"> diff --git a/app/src/main/res/drawable/shape_pre_release.xml b/app/src/main/res/drawable/shape_pre_release.xml index 3181a7f7..1f250589 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="18dp"> diff --git a/app/src/main/res/drawable/shape_stable_release.xml b/app/src/main/res/drawable/shape_stable_release.xml index 20da9d5b..9f3dedc6 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="18dp"> diff --git a/app/src/main/res/layout/list_releases.xml b/app/src/main/res/layout/list_releases.xml index 066203a3..554f867b 100644 --- a/app/src/main/res/layout/list_releases.xml +++ b/app/src/main/res/layout/list_releases.xml @@ -35,8 +35,8 @@ android:layout_height="wrap_content" android:layout_weight="0" android:background="@drawable/shape_stable_release" - android:paddingLeft="5dp" - android:paddingRight="5dp" + android:paddingLeft="8dp" + android:paddingRight="8dp" android:textColor="@color/colorWhite" android:textSize="14sp" /> @@ -182,6 +182,7 @@ android:orientation="vertical"> diff --git a/app/src/main/res/layout/list_releases_downloads.xml b/app/src/main/res/layout/list_releases_downloads.xml index 0a8a0cc5..9981fbae 100644 --- a/app/src/main/res/layout/list_releases_downloads.xml +++ b/app/src/main/res/layout/list_releases_downloads.xml @@ -7,17 +7,18 @@ android:gravity="center_vertical" android:orientation="horizontal" android:layout_marginStart="8dp" + android:layout_marginTop="8dp" tools:ignore="UseCompoundDrawables"> + android:orientation="horizontal" + tools:ignore="UseCompoundDrawables">