From e872069093697fca1a8eda32ffd472e8537d8d70 Mon Sep 17 00:00:00 2001 From: opyale Date: Tue, 30 Jun 2020 16:43:27 +0200 Subject: [PATCH] Proper URL parsing, label redesign and other improvements. (#564) Final improvements. Fixing reply mention. Do NOT use "instanceUrlRaw" as of now. Minor fixes Merge remote-tracking branch 'remotes/main/master' into login-fix URL parsing, label and other improvements. Co-authored-by: opyale Reviewed-on: https://codeberg.org/gitnex/GitNex/pulls/564 Reviewed-by: M M Arif --- README.md | 3 +- app/build.gradle | 3 +- .../mian/gitnex/activities/LoginActivity.java | 47 +++++--- .../mian/gitnex/activities/MainActivity.java | 4 +- .../activities/OpenRepoInBrowserActivity.java | 38 +++++-- .../gitnex/adapters/IssueCommentsAdapter.java | 7 +- .../mian/gitnex/adapters/LabelsAdapter.java | 38 +++---- .../BottomSheetSingleIssueFragment.java | 74 ++++++------ .../gitnex/fragments/ProfileFragment.java | 12 +- .../org/mian/gitnex/helpers/PathsHelper.java | 37 ++++++ .../org/mian/gitnex/helpers/UrlHelper.java | 54 ++++----- .../java/org/mian/gitnex/util/AppUtil.java | 2 +- app/src/main/res/layout/list_labels.xml | 105 +++++++++++------- app/src/main/res/layout/list_releases.xml | 29 ++--- app/src/main/res/layout/nav_header.xml | 5 +- app/src/main/res/values/strings.xml | 2 + 16 files changed, 284 insertions(+), 176 deletions(-) create mode 100644 app/src/main/java/org/mian/gitnex/helpers/PathsHelper.java diff --git a/README.md b/README.md index d7203b79..bbf1c3ea 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ We use [Crowdin](https://crowdin.com/project/gitnex) for translation. If your la ## Thanks Thanks to all the open source libraries, contributors and donators. -Open source libraries +#### Open source libraries - Retrofit - Gson - Okhttp @@ -88,5 +88,6 @@ Open source libraries - Barteksc/AndroidPdfViewer - Ge0rg/MemorizingTrustManager - Dimezis/BlurView +- mikaelhg/urlbuilder [Follow me on Fediverse - mastodon.social/@mmarif](https://mastodon.social/@mmarif) diff --git a/app/build.gradle b/app/build.gradle index eb788dae..02338130 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -86,6 +86,7 @@ dependencies { implementation "ch.acra:acra-mail:$acra" implementation "ch.acra:acra-limiter:$acra" implementation "ch.acra:acra-notification:$acra" - implementation 'com.eightbitlab:blurview:1.6.3' + implementation "com.eightbitlab:blurview:1.6.3" + implementation "io.mikael:urlbuilder:2.0.9" } diff --git a/app/src/main/java/org/mian/gitnex/activities/LoginActivity.java b/app/src/main/java/org/mian/gitnex/activities/LoginActivity.java index 537c80d0..9c7c8bba 100644 --- a/app/src/main/java/org/mian/gitnex/activities/LoginActivity.java +++ b/app/src/main/java/org/mian/gitnex/activities/LoginActivity.java @@ -22,6 +22,7 @@ import com.tooltip.Tooltip; import org.mian.gitnex.R; import org.mian.gitnex.clients.RetrofitClient; import org.mian.gitnex.helpers.NetworkObserver; +import org.mian.gitnex.helpers.PathsHelper; import org.mian.gitnex.helpers.SnackBar; import org.mian.gitnex.helpers.UrlHelper; import org.mian.gitnex.helpers.Version; @@ -30,10 +31,11 @@ import org.mian.gitnex.models.UserInfo; import org.mian.gitnex.models.UserTokens; import org.mian.gitnex.util.AppUtil; import org.mian.gitnex.util.TinyDB; -import java.net.URL; +import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.UUID; +import io.mikael.urlbuilder.UrlBuilder; import okhttp3.Credentials; import retrofit2.Call; import retrofit2.Callback; @@ -166,17 +168,23 @@ public class LoginActivity extends BaseActivity { String loginToken = loginTokenCode.getText().toString().trim(); Protocol protocol = (Protocol) protocolSpinner.getSelectedItem(); - URL rawInstanceUrl = new URL(UrlHelper.fixScheme(instanceUrlET.getText().toString(), protocol.name().toLowerCase())); LoginType loginType = (loginMethod.getCheckedRadioButtonId() == R.id.loginUsernamePassword) ? LoginType.BASIC : LoginType.TOKEN; - String portAppendix = (rawInstanceUrl.getPort() > 0) ? ":" + rawInstanceUrl.getPort() : ""; - String instanceUrlWithProtocol = protocol.name().toLowerCase() + "://" + rawInstanceUrl.getHost() + portAppendix; - String instanceUrl = instanceUrlWithProtocol + "/api/v1/"; + URI rawInstanceUrl = UrlBuilder.fromString(UrlHelper.fixScheme(instanceUrlET.getText().toString(), "http")) + .toUri(); + + URI instanceUrlWithProtocol = UrlBuilder.fromUri(rawInstanceUrl) + .withScheme(protocol.name().toLowerCase()) + .toUri(); + + URI instanceUrl = UrlBuilder.fromUri(instanceUrlWithProtocol) + .withPath(PathsHelper.join(instanceUrlWithProtocol.getPath(), "/api/v1/")) + .toUri(); tinyDB.putString("loginType", loginType.name().toLowerCase()); - tinyDB.putString("instanceUrlRaw", rawInstanceUrl.getHost()); - tinyDB.putString("instanceUrl", instanceUrl); - tinyDB.putString("instanceUrlWithProtocol", instanceUrlWithProtocol); + tinyDB.putString("instanceUrlRaw", instanceUrlET.getText().toString()); + tinyDB.putString("instanceUrl", instanceUrl.toString()); + tinyDB.putString("instanceUrlWithProtocol", instanceUrlWithProtocol.toString()); if(instanceUrlET.getText().toString().equals("")) { @@ -188,7 +196,13 @@ public class LoginActivity extends BaseActivity { if(loginType == LoginType.BASIC) { - int loginOTP = (otpCode.getText().toString().length() == 6) ? Integer.parseInt(otpCode.getText().toString().trim()) : 0; + if(otpCode.length() != 0 && otpCode.length() != 6) { + + SnackBar.warning(ctx, layoutView, getResources().getString(R.string.loginOTPTypeError)); + enableProcessButton(); + return; + + } if(rawInstanceUrl.getUserInfo() != null) { @@ -197,8 +211,6 @@ public class LoginActivity extends BaseActivity { } - tinyDB.putString("loginUid", loginUid); - if(loginUid.equals("")) { SnackBar.warning(ctx, layoutView, getResources().getString(R.string.emptyFieldUsername)); @@ -215,7 +227,10 @@ public class LoginActivity extends BaseActivity { } - versionCheck(instanceUrl, loginUid, loginPass, loginOTP, loginToken, 1); + int loginOTP = (otpCode.length() > 0) ? Integer.parseInt(otpCode.getText().toString().trim()) : 0; + tinyDB.putString("loginUid", loginUid); + + versionCheck(instanceUrl.toString(), loginUid, loginPass, loginOTP, loginToken, 1); } else { @@ -228,7 +243,7 @@ public class LoginActivity extends BaseActivity { } - versionCheck(instanceUrl, loginUid, loginPass, 123, loginToken, 2); + versionCheck(instanceUrl.toString(), loginUid, loginPass, 123, loginToken, 2); } @@ -358,8 +373,8 @@ public class LoginActivity extends BaseActivity { switch(response.code()) { case 200: - tinyDB.putBoolean("loggedInMode", true); assert userDetails != null; + tinyDB.putBoolean("loggedInMode", true); tinyDB.putString(userDetails.getLogin() + "-token", loginToken); tinyDB.putString("loginUid", userDetails.getLogin()); tinyDB.putString("userLogin", userDetails.getUsername()); @@ -423,8 +438,8 @@ public class LoginActivity extends BaseActivity { if(response.code() == 200) { - boolean setTokenFlag = false; assert userTokens != null; + boolean setTokenFlag = false; if(userTokens.size() > 0) { // FIXME This is in need of a refactor, but i don't understand what the code is used for. @@ -494,9 +509,9 @@ public class LoginActivity extends BaseActivity { switch(response.code()) { case 200: + assert userDetails != null; tinyDB.remove("loginPass"); tinyDB.putBoolean("loggedInMode", true); - assert userDetails != null; tinyDB.putString("userLogin", userDetails.getUsername()); tinyDB.putString(loginUid + "-token", newToken.getSha1()); tinyDB.putString(loginUid + "-token-last-eight", appUtil.getLastCharactersOfWord(newToken.getSha1(), 8)); diff --git a/app/src/main/java/org/mian/gitnex/activities/MainActivity.java b/app/src/main/java/org/mian/gitnex/activities/MainActivity.java index 119f1025..7e95e9f8 100644 --- a/app/src/main/java/org/mian/gitnex/activities/MainActivity.java +++ b/app/src/main/java/org/mian/gitnex/activities/MainActivity.java @@ -515,7 +515,7 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig tinyDb.putString("userLang", userDetails.getLang()); } else { - tinyDb.putString("userLang", "..."); + tinyDb.putString("userLang", ""); } } } @@ -526,7 +526,7 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig } else { - String toastError = getResources().getString(R.string.genericApiStatusError) + String.valueOf(response.code()); + String toastError = getResources().getString(R.string.genericApiStatusError) + response.code(); Toasty.info(ctx, toastError); } diff --git a/app/src/main/java/org/mian/gitnex/activities/OpenRepoInBrowserActivity.java b/app/src/main/java/org/mian/gitnex/activities/OpenRepoInBrowserActivity.java index 283283d8..f2f533d1 100644 --- a/app/src/main/java/org/mian/gitnex/activities/OpenRepoInBrowserActivity.java +++ b/app/src/main/java/org/mian/gitnex/activities/OpenRepoInBrowserActivity.java @@ -5,7 +5,13 @@ import android.content.Intent; import android.net.Uri; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; +import org.mian.gitnex.R; +import org.mian.gitnex.helpers.PathsHelper; +import org.mian.gitnex.helpers.Toasty; import org.mian.gitnex.util.TinyDB; +import java.net.URI; +import java.net.URISyntaxException; +import io.mikael.urlbuilder.UrlBuilder; /** * Author M M Arif @@ -18,20 +24,28 @@ public class OpenRepoInBrowserActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - appCtx = getApplicationContext(); + super.onCreate(savedInstanceState); + appCtx = getApplicationContext(); + TinyDB tinyDb = new TinyDB(appCtx); - TinyDB tinyDb = new TinyDB(appCtx); - String instanceUrlWithProtocol = "https://" + tinyDb.getString("instanceUrlRaw"); - if (!tinyDb.getString("instanceUrlWithProtocol").isEmpty()) { - instanceUrlWithProtocol = tinyDb.getString("instanceUrlWithProtocol"); - } + try { - String repoFullNameBrowser = getIntent().getStringExtra("repoFullNameBrowser"); - Uri url = Uri.parse(instanceUrlWithProtocol + "/" + repoFullNameBrowser); - Intent i = new Intent(Intent.ACTION_VIEW, url); - startActivity(i); - finish(); + URI instanceUrl = new URI(tinyDb.getString("instanceUrlWithProtocol")); + + String browserPath = PathsHelper.join(instanceUrl.getPath(), getIntent().getStringExtra("repoFullNameBrowser")); + + String browserUrl = UrlBuilder.fromUri(instanceUrl) + .withPath(browserPath) + .toString(); + + Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(browserUrl)); + startActivity(i); + finish(); + + } + catch(URISyntaxException e) { + Toasty.error(appCtx, getString(R.string.genericError)); + } } diff --git a/app/src/main/java/org/mian/gitnex/adapters/IssueCommentsAdapter.java b/app/src/main/java/org/mian/gitnex/adapters/IssueCommentsAdapter.java index 646d66a3..59184a81 100644 --- a/app/src/main/java/org/mian/gitnex/adapters/IssueCommentsAdapter.java +++ b/app/src/main/java/org/mian/gitnex/adapters/IssueCommentsAdapter.java @@ -154,7 +154,12 @@ public class IssueCommentsAdapter extends RecyclerView.Adapter { StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append("@").append(commenterUsername.getText().toString()).append("\n\n"); + String commenterName = commenterUsername.getText().toString(); + + if(!commenterName.equals(tinyDb.getString("userLogin"))) { + + stringBuilder.append("@").append(commenterName).append("\n\n"); + } String[] lines = commendBodyRaw.getText().toString().split("\\R"); diff --git a/app/src/main/java/org/mian/gitnex/adapters/LabelsAdapter.java b/app/src/main/java/org/mian/gitnex/adapters/LabelsAdapter.java index d9cd3852..71803012 100644 --- a/app/src/main/java/org/mian/gitnex/adapters/LabelsAdapter.java +++ b/app/src/main/java/org/mian/gitnex/adapters/LabelsAdapter.java @@ -3,26 +3,25 @@ package org.mian.gitnex.adapters; import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; +import android.content.res.ColorStateList; import android.graphics.Color; -import android.graphics.Typeface; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; -import com.amulyakhare.textdrawable.TextDrawable; +import androidx.annotation.NonNull; +import androidx.cardview.widget.CardView; +import androidx.core.widget.ImageViewCompat; +import androidx.recyclerview.widget.RecyclerView; import com.google.android.material.bottomsheet.BottomSheetDialog; import org.mian.gitnex.R; import org.mian.gitnex.activities.CreateLabelActivity; import org.mian.gitnex.helpers.AlertDialogs; import org.mian.gitnex.helpers.ColorInverter; -import org.mian.gitnex.helpers.LabelWidthCalculator; import org.mian.gitnex.models.Labels; import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; /** * Author M M Arif @@ -39,12 +38,17 @@ public class LabelsAdapter extends RecyclerView.Adapter { - // get url of repo - String repoFullName = tinyDB.getString("repoFullName"); - String instanceUrlWithProtocol = "https://" + tinyDB.getString("instanceUrlRaw"); - if(!tinyDB.getString("instanceUrlWithProtocol").isEmpty()) { - instanceUrlWithProtocol = tinyDB.getString("instanceUrlWithProtocol"); + try { + + URI instanceUrl = new URI(tinyDB.getString("instanceUrlWithProtocol")); + + String issuePath = PathsHelper.join(instanceUrl.getPath(), tinyDB.getString("repoFullName"), "/issues/", tinyDB.getString("issueNumber")); + + String issueUrl = UrlBuilder.fromUri(instanceUrl) + .withPath(issuePath) + .toString(); + + // share issue + Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND); + sharingIntent.setType("text/plain"); + sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, getResources().getString(R.string.hash) + tinyDB.getString("issueNumber") + " " + tinyDB.getString("issueTitle")); + sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, issueUrl); + startActivity(Intent.createChooser(sharingIntent, getResources().getString(R.string.hash) + tinyDB.getString("issueNumber") + " " + tinyDB.getString("issueTitle"))); + + } + catch(URISyntaxException e) { + Toasty.error(ctx, getString(R.string.genericError)); + } + finally { + dismiss(); } - - // get issue Url - String issueUrl = instanceUrlWithProtocol + "/" + repoFullName + "/issues/" + tinyDB.getString("issueNumber"); - - // share issue - Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND); - sharingIntent.setType("text/plain"); - sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, getResources().getString(R.string.hash) + tinyDB.getString("issueNumber") + " " + tinyDB.getString("issueTitle")); - sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, issueUrl); - startActivity(Intent.createChooser(sharingIntent, getResources().getString(R.string.hash) + tinyDB.getString("issueNumber") + " " + tinyDB.getString("issueTitle"))); - - dismiss(); }); - copyIssueUrl.setOnClickListener(new View.OnClickListener() { + copyIssueUrl.setOnClickListener(v12 -> { - @Override - public void onClick(View v) { + try { - // get url of repo - String repoFullName = tinyDB.getString("repoFullName"); - String instanceUrlWithProtocol = "https://" + tinyDB.getString("instanceUrlRaw"); - if(!tinyDB.getString("instanceUrlWithProtocol").isEmpty()) { - instanceUrlWithProtocol = tinyDB.getString("instanceUrlWithProtocol"); - } + URI instanceUrl = new URI(tinyDB.getString("instanceUrlWithProtocol")); - // get issue Url - String issueUrl = instanceUrlWithProtocol + "/" + repoFullName + "/issues/" + tinyDB.getString("issueNumber"); + String issuePath = PathsHelper.join(instanceUrl.getPath(), tinyDB.getString("repoFullName"), "/issues/", tinyDB.getString("issueNumber")); + + String issueUrl = UrlBuilder.fromUri(instanceUrl) + .withPath(issuePath) + .toString(); // copy to clipboard - ClipboardManager clipboard = (ClipboardManager) Objects.requireNonNull(ctx).getSystemService(android.content.Context.CLIPBOARD_SERVICE); + ClipboardManager clipboard = (ClipboardManager) Objects.requireNonNull(ctx).getSystemService(Context.CLIPBOARD_SERVICE); ClipData clip = ClipData.newPlainText("issueUrl", issueUrl); assert clipboard != null; clipboard.setPrimaryClip(clip); - dismiss(); - Toasty.info(ctx, ctx.getString(R.string.copyIssueUrlToastMsg)); } + catch(URISyntaxException e) { + Toasty.error(ctx, getString(R.string.genericError)); + } + finally { + dismiss(); + } + }); if(tinyDB.getString("issueType").equals("issue")) { diff --git a/app/src/main/java/org/mian/gitnex/fragments/ProfileFragment.java b/app/src/main/java/org/mian/gitnex/fragments/ProfileFragment.java index 05dd0a18..49d85632 100644 --- a/app/src/main/java/org/mian/gitnex/fragments/ProfileFragment.java +++ b/app/src/main/java/org/mian/gitnex/fragments/ProfileFragment.java @@ -63,11 +63,19 @@ public class ProfileFragment extends Fragment { ViewGroup aboutFrame = v.findViewById(R.id.aboutFrame); String[] userLanguageCodes = tinyDb.getString("userLang").split("-"); - Locale locale = new Locale(userLanguageCodes[0], userLanguageCodes[1]); + + if(userLanguageCodes.length >= 2) { + + Locale locale = new Locale(userLanguageCodes[0], userLanguageCodes[1]); + userLanguage.setText(locale.getDisplayCountry()); + } + else { + + userLanguage.setText(R.string.notSupported); + } userFullName.setText(tinyDb.getString("userFullname")); userLogin.setText(getString(R.string.usernameWithAt, tinyDb.getString("userLogin"))); - userLanguage.setText(locale.getDisplayCountry()); PicassoService.getInstance(ctx).get() .load(tinyDb.getString("userAvatar")) diff --git a/app/src/main/java/org/mian/gitnex/helpers/PathsHelper.java b/app/src/main/java/org/mian/gitnex/helpers/PathsHelper.java new file mode 100644 index 00000000..9bcbb06c --- /dev/null +++ b/app/src/main/java/org/mian/gitnex/helpers/PathsHelper.java @@ -0,0 +1,37 @@ +package org.mian.gitnex.helpers; + +/** + * Author opyale + */ + +public class PathsHelper { + + public static String join(String... paths) { + + StringBuilder stringBuilder = new StringBuilder(); + + for(String path : paths) { + + if(path != null && !path.isEmpty()) { + + if(!path.startsWith("/")) { + + stringBuilder.append("/"); + } + + if(path.endsWith("/")) { + + path = path.substring(0, path.lastIndexOf("/")); + } + + stringBuilder.append(path); + + } + + } + + return stringBuilder.append("/").toString(); + + } + +} diff --git a/app/src/main/java/org/mian/gitnex/helpers/UrlHelper.java b/app/src/main/java/org/mian/gitnex/helpers/UrlHelper.java index c8d8447d..ee8fd2f8 100644 --- a/app/src/main/java/org/mian/gitnex/helpers/UrlHelper.java +++ b/app/src/main/java/org/mian/gitnex/helpers/UrlHelper.java @@ -9,39 +9,39 @@ import java.net.URISyntaxException; public class UrlHelper { - public static String cleanUrl(String url) { + public static String cleanUrl(String url) { - URI uri = null; - try { - uri = new URI(url); - } catch (URISyntaxException e) { - e.printStackTrace(); - } + URI uri = null; + try { + uri = new URI(url); + } + catch(URISyntaxException e) { + e.printStackTrace(); + } - assert uri != null; - String urlProtocol = uri.getScheme(); - String urlHost = uri.getHost(); - int urlPort = uri.getPort(); + assert uri != null; + String urlProtocol = uri.getScheme(); + String urlHost = uri.getHost(); + int urlPort = uri.getPort(); - String urlFinal = null; - if(urlPort > 0) { - urlFinal = urlProtocol + "://" + urlHost + ":" + urlPort; - } - else if(urlProtocol != null) { - urlFinal = urlProtocol + "://" + urlHost; - } - else { - urlFinal = urlHost; - } + String urlFinal = null; + if(urlPort > 0) { + urlFinal = urlProtocol + "://" + urlHost + ":" + urlPort; + } + else if(urlProtocol != null) { + urlFinal = urlProtocol + "://" + urlHost; + } + else { + urlFinal = urlHost; + } - return urlFinal; + return urlFinal; - } + } - public static String fixScheme(String url, String scheme) { + public static String fixScheme(String url, String scheme) { - return !url.matches("^(http|https)://.+$") ? scheme + "://" + url : url; - } + return !url.matches("^(http|https)://.+$") ? scheme + "://" + url : url; + } } - diff --git a/app/src/main/java/org/mian/gitnex/util/AppUtil.java b/app/src/main/java/org/mian/gitnex/util/AppUtil.java index 19b9404f..7461c370 100644 --- a/app/src/main/java/org/mian/gitnex/util/AppUtil.java +++ b/app/src/main/java/org/mian/gitnex/util/AppUtil.java @@ -95,7 +95,7 @@ public class AppUtil { return str.matches("^[\\w-]+$"); } - public Boolean checkIntegers(String str) { + public static Boolean checkIntegers(String str) { return str.matches("\\d+"); } diff --git a/app/src/main/res/layout/list_labels.xml b/app/src/main/res/layout/list_labels.xml index 39bf1e1e..2aeee088 100644 --- a/app/src/main/res/layout/list_labels.xml +++ b/app/src/main/res/layout/list_labels.xml @@ -1,61 +1,86 @@ - + android:background="?attr/primaryBackgroundColor" + android:orientation="horizontal" + android:padding="10dp"> + android:layout_weight="1" + android:visibility="gone" /> + android:layout_weight="1" + android:visibility="gone" /> + android:layout_weight="1" + android:visibility="gone" /> - + - + - + - + + - + + + + + + + diff --git a/app/src/main/res/layout/list_releases.xml b/app/src/main/res/layout/list_releases.xml index 9aa50f49..12963600 100644 --- a/app/src/main/res/layout/list_releases.xml +++ b/app/src/main/res/layout/list_releases.xml @@ -27,8 +27,7 @@ android:singleLine="true" android:textColor="?attr/primaryTextColor" android:textSize="18sp" - android:textStyle="bold" - tools:text="3.0.0-rc1" /> + android:textStyle="bold" /> + android:textSize="14sp" /> @@ -99,8 +97,7 @@ android:layout_marginStart="5dp" android:singleLine="true" android:textColor="?attr/primaryTextColor" - android:textSize="14sp" - tools:text="3.0.0-rc1" /> + android:textSize="14sp" /> @@ -125,8 +122,7 @@ android:layout_marginStart="5dp" android:singleLine="true" android:textColor="?attr/primaryTextColor" - android:textSize="14sp" - tools:text="8b1c79c0c3" /> + android:textSize="14sp" /> @@ -150,8 +146,7 @@ android:layout_marginStart="5dp" android:singleLine="true" android:textColor="?attr/primaryTextColor" - android:textSize="14sp" - tools:text="1 day ago" /> + android:textSize="14sp" /> @@ -172,9 +167,10 @@ android:id="@+id/releaseBodyContent" android:layout_width="match_parent" android:layout_height="wrap_content" + android:autoLink="web|email" + android:textColorLink="@color/lightBlue" android:textColor="?attr/primaryTextColor" - android:textSize="14sp" - tools:text="Put your release body here" /> + android:textSize="14sp" /> @@ -207,8 +203,7 @@ android:layout_marginStart="3dp" android:textColor="?attr/primaryTextColor" android:textSize="14sp" - android:text="@string/releaseDownloadText" - tools:text="Downloads" /> + android:text="@string/releaseDownloadText" /> @@ -229,8 +224,7 @@ android:drawableStart="@drawable/ic_file_download_24dp" android:drawablePadding="8dp" android:textColor="?attr/primaryTextColor" - android:textSize="14sp" - tools:text="Source code (ZIP)" /> + android:textSize="14sp" /> + android:textSize="14sp" /> + android:contentDescription="@string/generalImgContentText"/> Sorry this file cannot be viewed as API returned an error Root + Not supported + OK Done