Natively download release attachments (#1114)

Do not use HTML to make a link, open a file chooser and provide native downloads for release attachments.

Co-authored-by: qwerty287 <ndev@web.de>
Co-authored-by: M M Arif <mmarif@swatian.com>
Reviewed-on: https://codeberg.org/gitnex/GitNex/pulls/1114
Reviewed-by: M M Arif <mmarif@noreply.codeberg.org>
Co-authored-by: qwerty287 <qwerty287@noreply.codeberg.org>
Co-committed-by: qwerty287 <qwerty287@noreply.codeberg.org>
This commit is contained in:
qwerty287 2022-04-24 17:15:35 +02:00 committed by M M Arif
parent c67b3c178f
commit e050a63b0d
10 changed files with 173 additions and 68 deletions

View File

@ -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<ReleasesAdapter.ReleasesViewHolder> {
@ -37,6 +37,7 @@ public class ReleasesAdapter extends RecyclerView.Adapter<ReleasesAdapter.Releas
private OnLoadMoreListener loadMoreListener;
private boolean isLoading = false, isMoreDataAvailable = true;
private final FragmentRefreshListener startDownload;
static class ReleasesViewHolder extends RecyclerView.ViewHolder {
@ -49,10 +50,10 @@ public class ReleasesAdapter extends RecyclerView.Adapter<ReleasesAdapter.Releas
private final TextView releaseTag;
private final TextView releaseDate;
private final TextView releaseBodyContent;
private final LinearLayout downloadFrame;
private final LinearLayout downloadCopyFrame;
private final LinearLayout downloads;
private final TextView releaseZipDownload;
private final TextView releaseTarDownload;
private final LinearLayout releaseZipDownloadFrame;
private final LinearLayout releaseTarDownloadFrame;
private final ImageView downloadDropdownIcon;
private final RecyclerView downloadList;
@ -68,10 +69,10 @@ public class ReleasesAdapter extends RecyclerView.Adapter<ReleasesAdapter.Releas
TextView releaseCommitSha = itemView.findViewById(R.id.releaseCommitSha);
releaseDate = itemView.findViewById(R.id.releaseDate);
releaseBodyContent = itemView.findViewById(R.id.releaseBodyContent);
downloadFrame = itemView.findViewById(R.id.downloadFrame);
downloadCopyFrame = itemView.findViewById(R.id.downloadCopyFrame);
downloads = itemView.findViewById(R.id.downloads);
releaseZipDownload = itemView.findViewById(R.id.releaseZipDownload);
releaseTarDownload = itemView.findViewById(R.id.releaseTarDownload);
releaseZipDownloadFrame = itemView.findViewById(R.id.releaseZipDownloadFrame);
releaseTarDownloadFrame = itemView.findViewById(R.id.releaseTarDownloadFrame);
downloadDropdownIcon = itemView.findViewById(R.id.downloadDropdownIcon);
downloadList = itemView.findViewById(R.id.downloadList);
@ -88,9 +89,10 @@ public class ReleasesAdapter extends RecyclerView.Adapter<ReleasesAdapter.Releas
}
}
public ReleasesAdapter(Context ctx, List<Release> releasesMain) {
public ReleasesAdapter(Context ctx, List<Release> releasesMain, FragmentRefreshListener startDownload) {
this.context = ctx;
this.releasesList = releasesMain;
this.startDownload = startDownload;
}
@NonNull
@ -151,7 +153,7 @@ public class ReleasesAdapter extends RecyclerView.Adapter<ReleasesAdapter.Releas
holder.releaseBodyContent.setText(R.string.noReleaseBodyContent);
}
holder.downloadFrame.setOnClickListener(v -> {
holder.downloadCopyFrame.setOnClickListener(v -> {
if(holder.downloads.getVisibility() == View.GONE) {
@ -166,15 +168,10 @@ public class ReleasesAdapter extends RecyclerView.Adapter<ReleasesAdapter.Releas
});
holder.releaseZipDownload.setText(
HtmlCompat.fromHtml("<a href='" + currentItem.getZipballUrl() + "'>" + context.getResources().getString(R.string.zipArchiveDownloadReleasesTab) + "</a> ", 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("<a href='" + currentItem.getTarballUrl() + "'>" + context.getResources().getString(R.string.tarArchiveDownloadReleasesTab) + "</a> ", 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<ReleasesAdapter.Releas
}
}
@SuppressLint("NotifyDataSetChanged")
public void notifyDataChanged() {
notifyDataSetChanged();
isLoading = false;
@ -215,5 +213,4 @@ public class ReleasesAdapter extends RecyclerView.Adapter<ReleasesAdapter.Releas
releasesList = list;
notifyDataChanged();
}
}

View File

@ -1,15 +1,14 @@
package org.mian.gitnex.adapters;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.text.HtmlCompat;
import androidx.recyclerview.widget.RecyclerView;
import org.gitnex.tea4j.v2.models.Attachment;
import org.mian.gitnex.R;
import org.mian.gitnex.structs.FragmentRefreshListener;
import java.util.List;
/**
@ -19,6 +18,7 @@ import java.util.List;
public class ReleasesDownloadsAdapter extends RecyclerView.Adapter<ReleasesDownloadsAdapter.ReleasesDownloadsViewHolder> {
private final List<Attachment> releasesDownloadsList;
private final FragmentRefreshListener startDownload;
static class ReleasesDownloadsViewHolder extends RecyclerView.ViewHolder {
@ -31,9 +31,10 @@ public class ReleasesDownloadsAdapter extends RecyclerView.Adapter<ReleasesDownl
}
}
ReleasesDownloadsAdapter(List<Attachment> releasesDownloadsMain) {
ReleasesDownloadsAdapter(List<Attachment> releasesDownloadsMain, FragmentRefreshListener startDownload) {
this.releasesDownloadsList = releasesDownloadsMain;
this.startDownload = startDownload;
}
@NonNull
@ -50,9 +51,8 @@ public class ReleasesDownloadsAdapter extends RecyclerView.Adapter<ReleasesDownl
if(currentItem.getName() != null) {
holder.downloadName.setText(
HtmlCompat.fromHtml("<a href='" + currentItem.getBrowserDownloadUrl() + "'>" + currentItem.getName() + "</a> ", HtmlCompat.FROM_HTML_MODE_LEGACY));
holder.downloadName.setMovementMethod(LinkMovementMethod.getInstance());
holder.downloadName.setText(currentItem.getName());
holder.downloadName.setOnClickListener(v -> startDownload.onRefresh(currentItem.getBrowserDownloadUrl()));
}
}

View File

@ -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<TagsAdapter.TagsViewHolder
private List<Tag> 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<TagsAdapter.TagsViewHolder
private Tag tagsHolder;
private final TextView tagName;
private final TextView tagBody;
private final LinearLayout downloadFrame;
private final LinearLayout downloadCopyFrame;
private final LinearLayout downloads;
private final TextView releaseZipDownload;
private final TextView releaseTarDownload;
private final LinearLayout releaseZipDownloadFrame;
private final LinearLayout releaseTarDownloadFrame;
private final ImageView downloadDropdownIcon;
private final ImageView options;
@ -57,10 +57,10 @@ public class TagsAdapter extends RecyclerView.Adapter<TagsAdapter.TagsViewHolder
tagName = itemView.findViewById(R.id.tagName);
tagBody = itemView.findViewById(R.id.tagBodyContent);
downloadFrame = itemView.findViewById(R.id.downloadFrame);
downloadCopyFrame = itemView.findViewById(R.id.downloadCopyFrame);
downloads = itemView.findViewById(R.id.downloads);
releaseZipDownload = itemView.findViewById(R.id.releaseZipDownload);
releaseTarDownload = itemView.findViewById(R.id.releaseTarDownload);
releaseZipDownloadFrame = itemView.findViewById(R.id.releaseZipDownloadFrame);
releaseTarDownloadFrame = itemView.findViewById(R.id.releaseTarDownloadFrame);
downloadDropdownIcon = itemView.findViewById(R.id.downloadDropdownIcon);
options = itemView.findViewById(R.id.tagsOptionsMenu);
@ -84,11 +84,12 @@ public class TagsAdapter extends RecyclerView.Adapter<TagsAdapter.TagsViewHolder
}
}
public TagsAdapter(Context ctx, List<Tag> releasesMain, String repoOwner, String repoName) {
public TagsAdapter(Context ctx, List<Tag> 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<TagsAdapter.TagsViewHolder
holder.tagBody.setVisibility(View.GONE);
}
holder.downloadFrame.setOnClickListener(v -> {
holder.downloadCopyFrame.setOnClickListener(v -> {
if(holder.downloads.getVisibility() == View.GONE) {
@ -131,13 +132,8 @@ public class TagsAdapter extends RecyclerView.Adapter<TagsAdapter.TagsViewHolder
holder.options.setVisibility(View.GONE);
}
holder.releaseZipDownload.setText(
HtmlCompat.fromHtml("<a href='" + currentItem.getZipballUrl() + "'>" + context.getResources().getString(R.string.zipArchiveDownloadReleasesTab) + "</a> ", HtmlCompat.FROM_HTML_MODE_LEGACY));
holder.releaseZipDownload.setMovementMethod(LinkMovementMethod.getInstance());
holder.releaseTarDownload.setText(
HtmlCompat.fromHtml("<a href='" + currentItem.getTarballUrl() + "'>" + context.getResources().getString(R.string.tarArchiveDownloadReleasesTab) + "</a> ", 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;

View File

@ -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<Intent> 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);
}
}
});
}

View File

@ -8,7 +8,7 @@
</solid>
<corners
android:radius="3dp">
android:radius="18dp">
</corners>
</shape>

View File

@ -8,7 +8,7 @@
</solid>
<corners
android:radius="3dp">
android:radius="18dp">
</corners>
</shape>

View File

@ -8,7 +8,7 @@
</solid>
<corners
android:radius="3dp">
android:radius="18dp">
</corners>
</shape>

View File

@ -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">
<LinearLayout
android:id="@+id/downloadCopyFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
@ -217,30 +218,34 @@
android:paddingTop="8dp">
<LinearLayout
android:id="@+id/releaseZipDownloadFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:layout_marginStart="8dp"
android:layout_marginBottom="8dp"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_width="16dp"
android:layout_height="16dp"
app:srcCompat="@drawable/ic_download"
android:contentDescription="@string/generalImgContentText" />
<TextView
android:id="@+id/releaseZipDownload"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:textColor="?attr/primaryTextColor"
android:text="@string/zipArchiveDownloadReleasesTab"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/releaseTarDownloadFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
@ -249,17 +254,18 @@
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_width="16dp"
android:layout_height="16dp"
app:srcCompat="@drawable/ic_download"
android:contentDescription="@string/generalImgContentText" />
<TextView
android:id="@+id/releaseTarDownload"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:textColor="?attr/primaryTextColor"
android:text="@string/tarArchiveDownloadReleasesTab"
android:textSize="12sp" />
</LinearLayout>

View File

@ -7,17 +7,18 @@
android:gravity="center_vertical"
android:orientation="horizontal"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_width="16dp"
android:layout_height="16dp"
app:srcCompat="@drawable/ic_download"
android:contentDescription="@string/generalImgContentText" />
<TextView
android:id="@+id/downloadName"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:textColor="?attr/primaryTextColor"

View File

@ -11,7 +11,8 @@
android:id="@+id/headerFrame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
android:orientation="horizontal"
tools:ignore="UseCompoundDrawables">
<TextView
android:id="@+id/tagName"
@ -61,6 +62,7 @@
android:orientation="vertical">
<LinearLayout
android:id="@+id/downloadCopyFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
@ -96,30 +98,34 @@
android:paddingTop="8dp">
<LinearLayout
android:id="@+id/releaseZipDownloadFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:layout_marginStart="8dp"
android:layout_marginBottom="8dp"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_width="16dp"
android:layout_height="16dp"
app:srcCompat="@drawable/ic_download"
android:contentDescription="@string/generalImgContentText" />
<TextView
android:id="@+id/releaseZipDownload"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:textColor="?attr/primaryTextColor"
android:text="@string/zipArchiveDownloadReleasesTab"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/releaseTarDownloadFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
@ -128,17 +134,18 @@
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_width="16dp"
android:layout_height="16dp"
app:srcCompat="@drawable/ic_download"
android:contentDescription="@string/generalImgContentText" />
<TextView
android:id="@+id/releaseTarDownload"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:textColor="?attr/primaryTextColor"
android:text="@string/tarArchiveDownloadReleasesTab"
android:textSize="12sp" />
</LinearLayout>