Support for self-signed certificates #318

Merged
mmarif merged 32 commits from :self-signed into master 2020-04-03 06:16:05 +00:00
38 changed files with 1127 additions and 159 deletions

View File

@ -92,5 +92,6 @@ Open source libraries
- Droidsonroids.gif/android-gif-drawable
- Barteksc/AndroidPdfViewer
- Mikepenz/fastadapter
- Ge0rg/MemorizingTrustManager
[Follow me on Fediverse - mastodon.social/@mmarif](https://mastodon.social/@mmarif)

View File

@ -74,6 +74,7 @@
<activity android:name=".activities.OpenRepoInBrowserActivity" />
<activity android:name=".activities.FileDiffActivity" />
<activity android:name=".activities.CommitsActivity" />
<activity android:name=".helpers.ssl.MemorizingActivity" android:theme="@android:style/Theme.Material.Dialog" />
</application>
</manifest>

View File

@ -50,10 +50,10 @@ import android.widget.RelativeLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import com.amulyakhare.textdrawable.TextDrawable;
import com.squareup.picasso.Picasso;
import com.vdurmont.emoji.EmojiParser;
import org.mian.gitnex.R;
import org.mian.gitnex.adapters.IssueCommentsAdapter;
import org.mian.gitnex.clients.PicassoService;
import org.mian.gitnex.clients.RetrofitClient;
import org.mian.gitnex.fragments.BottomSheetSingleIssueFragment;
import org.mian.gitnex.helpers.AlertDialogs;
@ -388,7 +388,7 @@ public class IssueDetailActivity extends BaseActivity {
tinyDb.putString("issueState", singleIssue.getState());
tinyDb.putString("issueTitle", singleIssue.getTitle());
Picasso.get().load(singleIssue.getUser().getAvatar_url()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(assigneeAvatar);
PicassoService.getInstance(ctx).get().load(singleIssue.getUser().getAvatar_url()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(assigneeAvatar);
String issueNumber_ = "<font color='" + getApplicationContext().getResources().getColor(R.color.lightGray) + "'>" + getApplicationContext().getResources().getString(R.string.hash) + singleIssue.getNumber() + "</font>";
issueTitle.setText(Html.fromHtml(issueNumber_ + " " + singleIssue.getTitle()));
String cleanIssueDescription = singleIssue.getBody().trim();
@ -406,7 +406,7 @@ public class IssueDetailActivity extends BaseActivity {
ImageView assigneesView = new ImageView(getApplicationContext());
Picasso.get().load(singleIssue.getAssignees().get(i).getAvatar_url()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(100, 100).centerCrop().into(assigneesView);
PicassoService.getInstance(ctx).get().load(singleIssue.getAssignees().get(i).getAvatar_url()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(100, 100).centerCrop().into(assigneesView);
assigneesLayout.addView(assigneesView);
assigneesView.setLayoutParams(params1);

View File

@ -31,6 +31,7 @@ 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.NoRouteToHostException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
@ -727,7 +728,7 @@ public class LoginActivity extends BaseActivity implements View.OnClickListener
public void onFailure(@NonNull Call<UserTokens> createUserToken, @NonNull Throwable t) {
Log.e("onFailure-token", t.toString());
}
});

View File

@ -1,5 +1,6 @@
package org.mian.gitnex.activities;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
@ -22,8 +23,8 @@ import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.squareup.picasso.NetworkPolicy;
import com.squareup.picasso.Picasso;
import org.mian.gitnex.R;
import org.mian.gitnex.clients.PicassoService;
import org.mian.gitnex.clients.RetrofitClient;
import org.mian.gitnex.fragments.AboutFragment;
import org.mian.gitnex.fragments.ExploreRepositoriesFragment;
@ -101,7 +102,7 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig
boolean connToInternet = AppUtil.haveNetworkConnection(getApplicationContext());
if(!tinyDb.getBoolean("loggedInMode")) {
logout();
logout(this, ctx);
return;
}
@ -207,7 +208,7 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig
userAvatar = hView.findViewById(R.id.userAvatar);
if (!userAvatarNav.equals("")) {
Picasso.get().load(userAvatarNav).networkPolicy(NetworkPolicy.OFFLINE).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(160, 160).centerCrop().into(userAvatar);
PicassoService.getInstance(ctx).get().load(userAvatarNav).networkPolicy(NetworkPolicy.OFFLINE).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(160, 160).centerCrop().into(userAvatar);
}
userAvatar.setOnClickListener(new View.OnClickListener() {
@ -354,7 +355,7 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig
new SettingsFragment()).commit();
break;
case R.id.nav_logout:
logout();
logout(this, ctx);
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
break;
case R.id.nav_about:
@ -392,15 +393,15 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig
}
}
public void logout() {
public static void logout(Activity activity, Context ctx) {
TinyDB tinyDb = new TinyDB(getApplicationContext());
TinyDB tinyDb = new TinyDB(ctx.getApplicationContext());
tinyDb.putBoolean("loggedInMode", false);
tinyDb.remove("basicAuthPassword");
tinyDb.putBoolean("basicAuthFlag", false);
//tinyDb.clear();
finish();
startActivity(new Intent(MainActivity.this, LoginActivity.class));
activity.finish();
ctx.startActivity(new Intent(ctx, LoginActivity.class));
}
@ -488,7 +489,7 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig
userAvatar = hView.findViewById(R.id.userAvatar);
if (!Objects.requireNonNull(userDetails).getAvatar().equals("")) {
Picasso.get().load(userDetails.getAvatar()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(160, 160).centerCrop().into(userAvatar);
PicassoService.getInstance(ctx).get().load(userDetails.getAvatar()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(160, 160).centerCrop().into(userAvatar);
} else {
userAvatar.setImageResource(R.mipmap.app_logo_round);
}

View File

@ -11,8 +11,8 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.amulyakhare.textdrawable.TextDrawable;
import com.squareup.picasso.Picasso;
import org.mian.gitnex.R;
import org.mian.gitnex.clients.PicassoService;
import org.mian.gitnex.helpers.RoundedTransformation;
import org.mian.gitnex.models.UserInfo;
import java.util.ArrayList;
@ -98,7 +98,7 @@ public class AdminGetUsersAdapter extends RecyclerView.Adapter<AdminGetUsersAdap
holder.userRole.setVisibility(View.GONE);
}
Picasso.get().load(currentItem.getAvatar()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(holder.userAvatar);
PicassoService.getInstance(mCtx).get().load(currentItem.getAvatar()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(holder.userAvatar);
}
@Override

View File

@ -13,9 +13,9 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.squareup.picasso.Picasso;
import org.mian.gitnex.R;
import org.mian.gitnex.activities.IssueDetailActivity;
import org.mian.gitnex.clients.PicassoService;
import org.mian.gitnex.helpers.ClickListener;
import org.mian.gitnex.helpers.RoundedTransformation;
import org.mian.gitnex.helpers.TimeHelper;
@ -182,9 +182,9 @@ public class ClosedIssuesAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
}
if (issuesModel.getUser().getAvatar_url() != null) {
Picasso.get().load(issuesModel.getUser().getAvatar_url()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(issueAssigneeAvatar);
PicassoService.getInstance(context).get().load(issuesModel.getUser().getAvatar_url()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(issueAssigneeAvatar);
} else {
Picasso.get().load(issuesModel.getUser().getAvatar_url()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(issueAssigneeAvatar);
PicassoService.getInstance(context).get().load(issuesModel.getUser().getAvatar_url()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(issueAssigneeAvatar);
}
String issueNumber_ = "<font color='" + context.getResources().getColor(R.color.lightGray) + "'>" + context.getResources().getString(R.string.hash) + issuesModel.getNumber() + "</font>";

View File

@ -8,8 +8,8 @@ import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.squareup.picasso.Picasso;
import org.mian.gitnex.R;
import org.mian.gitnex.clients.PicassoService;
import org.mian.gitnex.models.Collaborators;
import org.mian.gitnex.helpers.RoundedTransformation;
import java.util.List;
@ -77,7 +77,7 @@ public class CollaboratorsAdapter extends BaseAdapter {
private void initData(ViewHolder viewHolder, int position) {
Collaborators currentItem = collaboratorsList.get(position);
Picasso.get().load(currentItem.getAvatar_url()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(180, 180).centerCrop().into(viewHolder.collaboratorAvatar);
PicassoService.getInstance(mCtx).get().load(currentItem.getAvatar_url()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(180, 180).centerCrop().into(viewHolder.collaboratorAvatar);
if(!currentItem.getFull_name().equals("")) {
viewHolder.collaboratorName.setText(currentItem.getFull_name());

View File

@ -14,12 +14,12 @@ import androidx.recyclerview.widget.RecyclerView;
import com.amulyakhare.textdrawable.TextDrawable;
import com.amulyakhare.textdrawable.util.ColorGenerator;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.squareup.picasso.Picasso;
import org.mian.gitnex.R;
import org.mian.gitnex.activities.OpenRepoInBrowserActivity;
import org.mian.gitnex.activities.RepoDetailActivity;
import org.mian.gitnex.activities.RepoStargazersActivity;
import org.mian.gitnex.activities.RepoWatchersActivity;
import org.mian.gitnex.clients.PicassoService;
import org.mian.gitnex.helpers.RoundedTransformation;
import org.mian.gitnex.models.UserRepositories;
import org.mian.gitnex.util.TinyDB;
@ -160,7 +160,7 @@ public class ExploreRepositoriesAdapter extends RecyclerView.Adapter<ExploreRepo
if (currentItem.getAvatar_url() != null) {
if (!currentItem.getAvatar_url().equals("")) {
Picasso.get().load(currentItem.getAvatar_url()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(holder.image);
PicassoService.getInstance(mCtx).get().load(currentItem.getAvatar_url()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(holder.image);
} else {
holder.image.setImageDrawable(drawable);
}

View File

@ -12,10 +12,10 @@ import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.squareup.picasso.Picasso;
import com.vdurmont.emoji.EmojiParser;
import org.mian.gitnex.R;
import org.mian.gitnex.activities.ReplyToIssueActivity;
import org.mian.gitnex.clients.PicassoService;
import org.mian.gitnex.helpers.TimeHelper;
import org.mian.gitnex.helpers.UserMentions;
import org.mian.gitnex.models.IssueComments;
@ -153,7 +153,7 @@ public class IssueCommentsAdapter extends RecyclerView.Adapter<IssueCommentsAdap
holder.issueCommenterAvatar.setOnClickListener(new ClickListener(mCtx.getResources().getString(R.string.issueCommenter) + currentItem.getUser().getLogin(), mCtx));
}
Picasso.get().load(currentItem.getUser().getAvatar_url()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(holder.issueCommenterAvatar);
PicassoService.getInstance(mCtx).get().load(currentItem.getUser().getAvatar_url()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(holder.issueCommenterAvatar);
String cleanIssueComments = currentItem.getBody().trim();

View File

@ -13,9 +13,9 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.squareup.picasso.Picasso;
import org.mian.gitnex.R;
import org.mian.gitnex.activities.IssueDetailActivity;
import org.mian.gitnex.clients.PicassoService;
import org.mian.gitnex.helpers.ClickListener;
import org.mian.gitnex.helpers.RoundedTransformation;
import org.mian.gitnex.helpers.TimeHelper;
@ -181,7 +181,7 @@ public class IssuesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
issueAssigneeAvatar.setOnClickListener(new ClickListener(context.getResources().getString(R.string.issueCreator) + issuesModel.getUser().getLogin(), context));
}
Picasso.get().load(issuesModel.getUser().getAvatar_url()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(issueAssigneeAvatar);
PicassoService.getInstance(context).get().load(issuesModel.getUser().getAvatar_url()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(issueAssigneeAvatar);
Outdated
Review

same useless if statement here

same useless if statement here

This issue is not related to this pull request.

This issue is not related to this pull request.
Outdated
Review

I know butit would fit in ...

I know butit would fit in ...
Outdated
Review

You can send an extra PR if you like to divide stuff

You can send an extra PR if you like to divide stuff

I dont know whether mixing these different things up would really fit.

I dont know whether mixing these different things up would really fit.

Maybe a thing for another pull reuqest?

Maybe a thing for another pull reuqest?
String issueNumber_ = "<font color='" + context.getResources().getColor(R.color.lightGray) + "'>" + context.getResources().getString(R.string.hash) + issuesModel.getNumber() + "</font>";
issueTitle.setText(Html.fromHtml(issueNumber_ + " " + issuesModel.getTitle()));

View File

@ -10,8 +10,8 @@ import android.widget.Filter;
import android.widget.Filterable;
import android.widget.ImageView;
import android.widget.TextView;
import com.squareup.picasso.Picasso;
import org.mian.gitnex.R;
import org.mian.gitnex.clients.PicassoService;
import org.mian.gitnex.helpers.RoundedTransformation;
import org.mian.gitnex.models.UserInfo;
import java.util.ArrayList;
@ -83,7 +83,7 @@ public class MembersByOrgAdapter extends BaseAdapter implements Filterable {
private void initData(MembersByOrgAdapter.ViewHolder viewHolder, int position) {
UserInfo currentItem = membersList.get(position);
Picasso.get().load(currentItem.getAvatar()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(viewHolder.memberAvatar);
PicassoService.getInstance(mCtx).get().load(currentItem.getAvatar()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(viewHolder.memberAvatar);
if(!currentItem.getFullname().equals("")) {
viewHolder.memberName.setText(currentItem.getFullname());

View File

@ -14,12 +14,12 @@ import android.widget.TextView;
import com.amulyakhare.textdrawable.TextDrawable;
import com.amulyakhare.textdrawable.util.ColorGenerator;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.squareup.picasso.Picasso;
import org.mian.gitnex.R;
import org.mian.gitnex.activities.OpenRepoInBrowserActivity;
import org.mian.gitnex.activities.RepoDetailActivity;
import org.mian.gitnex.activities.RepoStargazersActivity;
import org.mian.gitnex.activities.RepoWatchersActivity;
import org.mian.gitnex.clients.PicassoService;
import org.mian.gitnex.helpers.RoundedTransformation;
import org.mian.gitnex.models.UserRepositories;
import org.mian.gitnex.util.TinyDB;
@ -162,7 +162,7 @@ public class MyReposListAdapter extends RecyclerView.Adapter<MyReposListAdapter.
if (currentItem.getAvatar_url() != null) {
if (!currentItem.getAvatar_url().equals("")) {
Picasso.get().load(currentItem.getAvatar_url()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(holder.imageMy);
PicassoService.getInstance(mCtx).get().load(currentItem.getAvatar_url()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(holder.imageMy);
} else {
holder.imageMy.setImageDrawable(drawable);
}

View File

@ -12,9 +12,9 @@ import android.widget.Filter;
import android.widget.Filterable;
import android.widget.ImageView;
import android.widget.TextView;
import com.squareup.picasso.Picasso;
import org.mian.gitnex.R;
import org.mian.gitnex.activities.OrganizationDetailActivity;
import org.mian.gitnex.clients.PicassoService;
import org.mian.gitnex.models.UserOrganizations;
import org.mian.gitnex.helpers.RoundedTransformation;
import org.mian.gitnex.util.TinyDB;
@ -86,7 +86,7 @@ public class OrganizationsListAdapter extends RecyclerView.Adapter<Organizations
holder.mTextView2.setVisibility(View.GONE);
holder.organizationId.setText(Integer.toString(currentItem.getId()));
Picasso.get().load(currentItem.getAvatar_url()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(holder.image);
PicassoService.getInstance(mCtx).get().load(currentItem.getAvatar_url()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(holder.image);
holder.mTextView1.setText(currentItem.getUsername());
if (!currentItem.getDescription().equals("")) {
holder.mTextView2.setVisibility(View.VISIBLE);

View File

@ -6,8 +6,8 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.squareup.picasso.Picasso;
import org.mian.gitnex.R;
import org.mian.gitnex.clients.PicassoService;
import org.mian.gitnex.helpers.RoundedTransformation;
import org.mian.gitnex.models.UserInfo;
import java.util.List;
@ -65,7 +65,7 @@ public class ProfileFollowersAdapter extends RecyclerView.Adapter<ProfileFollowe
holder.userName.setVisibility(View.GONE);
}
Picasso.get().load(currentItem.getAvatar()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(holder.userAvatar);
PicassoService.getInstance(mCtx).get().load(currentItem.getAvatar()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(holder.userAvatar);
}
@Override

View File

@ -6,8 +6,8 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.squareup.picasso.Picasso;
import org.mian.gitnex.R;
import org.mian.gitnex.clients.PicassoService;
import org.mian.gitnex.helpers.RoundedTransformation;
import org.mian.gitnex.models.UserInfo;
import java.util.List;
@ -65,7 +65,7 @@ public class ProfileFollowingAdapter extends RecyclerView.Adapter<ProfileFollowi
holder.userName.setVisibility(View.GONE);
}
Picasso.get().load(currentItem.getAvatar()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(holder.userAvatar);
PicassoService.getInstance(mCtx).get().load(currentItem.getAvatar()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(holder.userAvatar);
}
@Override

View File

@ -14,9 +14,9 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.squareup.picasso.Picasso;
import org.mian.gitnex.R;
import org.mian.gitnex.activities.IssueDetailActivity;
import org.mian.gitnex.clients.PicassoService;
import org.mian.gitnex.helpers.ClickListener;
import org.mian.gitnex.helpers.RoundedTransformation;
import org.mian.gitnex.helpers.TimeHelper;
@ -170,9 +170,9 @@ public class PullRequestsAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
}
if (prModel.getUser().getAvatar_url() != null) {
Picasso.get().load(prModel.getUser().getAvatar_url()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(assigneeAvatar);
PicassoService.getInstance(context).get().load(prModel.getUser().getAvatar_url()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(assigneeAvatar);
} else {
Picasso.get().load(prModel.getUser().getAvatar_url()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(assigneeAvatar);
PicassoService.getInstance(context).get().load(prModel.getUser().getAvatar_url()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(assigneeAvatar);
}
String prNumber_ = "<font color='" + context.getResources().getColor(R.color.lightGray) + "'>" + context.getResources().getString(R.string.hash) + prModel.getNumber() + "</font>";

View File

@ -9,8 +9,8 @@ import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.squareup.picasso.Picasso;
import org.mian.gitnex.R;
import org.mian.gitnex.clients.PicassoService;
import org.mian.gitnex.helpers.RoundedTransformation;
import org.mian.gitnex.models.UserInfo;
import org.mian.gitnex.util.TinyDB;
@ -79,7 +79,7 @@ public class RepoStargazersAdapter extends BaseAdapter {
private void initData(RepoStargazersAdapter.ViewHolder viewHolder, int position) {
UserInfo currentItem = stargazersList.get(position);
Picasso.get().load(currentItem.getAvatar()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(180, 180).centerCrop().into(viewHolder.memberAvatar);
PicassoService.getInstance(mCtx).get().load(currentItem.getAvatar()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(180, 180).centerCrop().into(viewHolder.memberAvatar);
final TinyDB tinyDb = new TinyDB(mCtx);
Typeface myTypeface;

View File

@ -9,8 +9,8 @@ import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.squareup.picasso.Picasso;
import org.mian.gitnex.R;
import org.mian.gitnex.clients.PicassoService;
import org.mian.gitnex.helpers.RoundedTransformation;
import org.mian.gitnex.models.UserInfo;
import org.mian.gitnex.util.TinyDB;
@ -79,7 +79,7 @@ public class RepoWatchersAdapter extends BaseAdapter {
private void initData(RepoWatchersAdapter.ViewHolder viewHolder, int position) {
UserInfo currentItem = watchersList.get(position);
Picasso.get().load(currentItem.getAvatar()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(180, 180).centerCrop().into(viewHolder.memberAvatar);
PicassoService.getInstance(mCtx).get().load(currentItem.getAvatar()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(180, 180).centerCrop().into(viewHolder.memberAvatar);
final TinyDB tinyDb = new TinyDB(mCtx);
Typeface myTypeface;

View File

@ -16,12 +16,12 @@ import android.widget.TextView;
import com.amulyakhare.textdrawable.TextDrawable;
import com.amulyakhare.textdrawable.util.ColorGenerator;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.squareup.picasso.Picasso;
import org.mian.gitnex.R;
import org.mian.gitnex.activities.OpenRepoInBrowserActivity;
import org.mian.gitnex.activities.RepoDetailActivity;
import org.mian.gitnex.activities.RepoStargazersActivity;
import org.mian.gitnex.activities.RepoWatchersActivity;
import org.mian.gitnex.clients.PicassoService;
import org.mian.gitnex.helpers.RoundedTransformation;
import org.mian.gitnex.models.UserRepositories;
import org.mian.gitnex.util.TinyDB;
@ -165,7 +165,7 @@ public class ReposListAdapter extends RecyclerView.Adapter<ReposListAdapter.Repo
if (currentItem.getAvatar_url() != null) {
if (!currentItem.getAvatar_url().equals("")) {
Picasso.get().load(currentItem.getAvatar_url()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(holder.image);
PicassoService.getInstance(mCtx).get().load(currentItem.getAvatar_url()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(holder.image);
} else {
holder.image.setImageDrawable(drawable);
}

View File

@ -14,12 +14,12 @@ import android.widget.TextView;
import com.amulyakhare.textdrawable.TextDrawable;
import com.amulyakhare.textdrawable.util.ColorGenerator;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.squareup.picasso.Picasso;
import org.mian.gitnex.R;
import org.mian.gitnex.activities.OpenRepoInBrowserActivity;
import org.mian.gitnex.activities.RepoDetailActivity;
import org.mian.gitnex.activities.RepoStargazersActivity;
import org.mian.gitnex.activities.RepoWatchersActivity;
import org.mian.gitnex.clients.PicassoService;
import org.mian.gitnex.helpers.RoundedTransformation;
import org.mian.gitnex.models.UserRepositories;
import org.mian.gitnex.util.TinyDB;
@ -163,7 +163,7 @@ public class RepositoriesByOrgAdapter extends RecyclerView.Adapter<RepositoriesB
if (currentItem.getAvatar_url() != null) {
if (!currentItem.getAvatar_url().equals("")) {
Picasso.get().load(currentItem.getAvatar_url()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(holder.image);
PicassoService.getInstance(mCtx).get().load(currentItem.getAvatar_url()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(holder.image);
} else {
holder.image.setImageDrawable(drawable);
}

View File

@ -14,12 +14,12 @@ import android.widget.TextView;
import com.amulyakhare.textdrawable.TextDrawable;
import com.amulyakhare.textdrawable.util.ColorGenerator;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.squareup.picasso.Picasso;
import org.mian.gitnex.R;
import org.mian.gitnex.activities.OpenRepoInBrowserActivity;
import org.mian.gitnex.activities.RepoDetailActivity;
import org.mian.gitnex.activities.RepoStargazersActivity;
import org.mian.gitnex.activities.RepoWatchersActivity;
import org.mian.gitnex.clients.PicassoService;
import org.mian.gitnex.helpers.RoundedTransformation;
import org.mian.gitnex.models.UserRepositories;
import org.mian.gitnex.util.TinyDB;
@ -163,7 +163,7 @@ public class StarredReposListAdapter extends RecyclerView.Adapter<StarredReposLi
if (currentItem.getAvatar_url() != null) {
if (!currentItem.getAvatar_url().equals("")) {
Picasso.get().load(currentItem.getAvatar_url()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(holder.image);
PicassoService.getInstance(mCtx).get().load(currentItem.getAvatar_url()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(holder.image);
} else {
holder.image.setImageDrawable(drawable);
}

View File

@ -9,8 +9,8 @@ import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.squareup.picasso.Picasso;
import org.mian.gitnex.R;
import org.mian.gitnex.clients.PicassoService;
import org.mian.gitnex.helpers.RoundedTransformation;
import org.mian.gitnex.models.UserInfo;
import org.mian.gitnex.util.TinyDB;
@ -80,7 +80,7 @@ public class TeamMembersByOrgAdapter extends BaseAdapter {
private void initData(TeamMembersByOrgAdapter.ViewHolder viewHolder, int position) {
UserInfo currentItem = teamMembersList.get(position);
Picasso.get().load(currentItem.getAvatar()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(180, 180).centerCrop().into(viewHolder.memberAvatar);
PicassoService.getInstance(mCtx).get().load(currentItem.getAvatar()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(180, 180).centerCrop().into(viewHolder.memberAvatar);
final TinyDB tinyDb = new TinyDB(mCtx);
Typeface myTypeface;

View File

@ -9,9 +9,9 @@ import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import com.squareup.picasso.Picasso;
import org.mian.gitnex.R;
import org.mian.gitnex.actions.CollaboratorActions;
import org.mian.gitnex.clients.PicassoService;
import org.mian.gitnex.clients.RetrofitClient;
import org.mian.gitnex.helpers.AlertDialogs;
import org.mian.gitnex.helpers.Authorization;
@ -141,7 +141,7 @@ public class UserSearchAdapter extends RecyclerView.Adapter<UserSearchAdapter.Us
}
if (!currentItem.getAvatar().equals("")) {
Picasso.get().load(currentItem.getAvatar()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(holder.userAvatar);
PicassoService.getInstance(mCtx).get().load(currentItem.getAvatar()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(holder.userAvatar);
}
if(getItemCount() > 0) {

View File

@ -1,10 +1,16 @@
package org.mian.gitnex.clients;
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import org.mian.gitnex.helpers.ssl.MemorizingTrustManager;
import org.mian.gitnex.util.AppUtil;
import java.io.File;
import java.io.IOException;
import java.security.SecureRandom;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.X509TrustManager;
import okhttp3.Cache;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
@ -20,42 +26,59 @@ import retrofit2.converter.gson.GsonConverterFactory;
public class IssuesService {

ctx can be kept as is.

`ctx` can be kept as is.
public static <S> S createService(Class<S> serviceClass, String instanceURL, Context ctx) {
public static <S> S createService(Class<S> serviceClass, String instanceURL, Context ctx) {
final boolean connToInternet = AppUtil.haveNetworkConnection(ctx);
File httpCacheDirectory = new File(ctx.getCacheDir(), "responses");
int cacheSize = 50 * 1024 * 1024; // 50MB
Cache cache = new Cache(httpCacheDirectory, cacheSize);
final boolean connToInternet = AppUtil.haveNetworkConnection(ctx);
File httpCacheDirectory = new File(ctx.getCacheDir(), "responses");
int cacheSize = 50 * 1024 * 1024; // 50MB
Cache cache = new Cache(httpCacheDirectory, cacheSize);
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cache(cache)
//.addInterceptor(logging)
.addInterceptor(new Interceptor() {
@NonNull
@Override public Response intercept(@NonNull Chain chain) throws IOException {
Request request = chain.request();
if (connToInternet) {
request = request.newBuilder().header("Cache-Control", "public, max-age=" + 60).build();
} else {
request = request.newBuilder().header("Cache-Control", "public, only-if-cached, max-stale=" + 60 * 60 * 24 * 30).build();
}
return chain.proceed(request);
}
})
.build();
try {
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(instanceURL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create());
SSLContext sslContext = SSLContext.getInstance("TLS");
Retrofit retrofit = builder.build();
MemorizingTrustManager memorizingTrustManager = new MemorizingTrustManager(ctx);
sslContext.init(null, new X509TrustManager[]{memorizingTrustManager}, new SecureRandom());
return retrofit.create(serviceClass);
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cache(cache)
//.addInterceptor(logging)
.sslSocketFactory(sslContext.getSocketFactory(), memorizingTrustManager)
.hostnameVerifier(memorizingTrustManager.wrapHostnameVerifier(HttpsURLConnection.getDefaultHostnameVerifier()))
.addInterceptor(new Interceptor() {
}
@NonNull
@Override
public Response intercept(@NonNull Chain chain) throws IOException {
Request request = chain.request();
if(connToInternet) {
request = request.newBuilder().header("Cache-Control", "public, max-age=" + 60).build();
}
else {
request = request.newBuilder().header("Cache-Control", "public, only-if-cached, max-stale=" + 60 * 60 * 24 * 30).build();
}
return chain.proceed(request);
}

catch should be in new line

catch should be in new line
}).build();

Please use Logger Log.e();

Please use Logger `Log.e();`
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(instanceURL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create());
Retrofit retrofit = builder.build();
return retrofit.create(serviceClass);
}
catch(Exception e) {
Log.e("onFailure", e.toString());
}
return null;
}
}

View File

@ -0,0 +1,71 @@
package org.mian.gitnex.clients;
import android.content.Context;
import android.util.Log;
import com.squareup.picasso.OkHttp3Downloader;
import com.squareup.picasso.Picasso;
import org.mian.gitnex.helpers.ssl.MemorizingTrustManager;
import java.security.SecureRandom;
import java.util.Objects;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.X509TrustManager;
import okhttp3.OkHttpClient;
/**
* Author anonTree1417
*/
public class PicassoService {
private static PicassoService picassoService;
private Picasso picasso;
private PicassoService(Context context) {
Picasso.Builder builder = new Picasso.Builder(context);
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
MemorizingTrustManager memorizingTrustManager = new MemorizingTrustManager(context);
sslContext.init(null, new X509TrustManager[]{memorizingTrustManager}, new SecureRandom());
OkHttpClient.Builder okHttpClient = new OkHttpClient.Builder()
.sslSocketFactory(sslContext.getSocketFactory(), memorizingTrustManager)
.hostnameVerifier(memorizingTrustManager.wrapHostnameVerifier(HttpsURLConnection.getDefaultHostnameVerifier()));
builder.downloader(new OkHttp3Downloader(okHttpClient.build()));
Review

Picasso needs a downloader (in this case OkHttp3Downloader in conjunction with a OkHttpClient).

Picasso needs a downloader (in this case OkHttp3Downloader in conjunction with a OkHttpClient).
builder.listener((picasso, uri, exception) -> {
//Log.e("PicassoService", Objects.requireNonNull(uri.toString()));
//Log.e("PicassoService", exception.toString());
});
picasso = builder.build();
}
catch(Exception e) {
Log.e("PicassoService", e.toString());
}
}
public Picasso get() {
return picasso;
}
public static synchronized PicassoService getInstance(Context context) {
if(picassoService == null) {
picassoService = new PicassoService(context);
}
return picassoService;
}
}

View File

@ -1,10 +1,16 @@
package org.mian.gitnex.clients;
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import org.mian.gitnex.helpers.ssl.MemorizingTrustManager;
import org.mian.gitnex.util.AppUtil;
import java.io.File;
import java.io.IOException;
import java.security.SecureRandom;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.X509TrustManager;
import okhttp3.Cache;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
@ -20,42 +26,58 @@ import retrofit2.converter.gson.GsonConverterFactory;
public class PullRequestsService {

Same comments apply as IssuesService

Same comments apply as IssuesService
public static <S> S createService(Class<S> serviceClass, String instanceURL, Context ctx) {
public static <S> S createService(Class<S> serviceClass, String instanceURL, Context ctx) {
final boolean connToInternet = AppUtil.haveNetworkConnection(ctx);
File httpCacheDirectory = new File(ctx.getCacheDir(), "responses");
int cacheSize = 50 * 1024 * 1024; // 50MB
Cache cache = new Cache(httpCacheDirectory, cacheSize);
final boolean connToInternet = AppUtil.haveNetworkConnection(ctx);
File httpCacheDirectory = new File(ctx.getCacheDir(), "responses");
int cacheSize = 50 * 1024 * 1024; // 50MB
Cache cache = new Cache(httpCacheDirectory, cacheSize);
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cache(cache)
//.addInterceptor(logging)
.addInterceptor(new Interceptor() {
@NonNull
@Override public Response intercept(@NonNull Chain chain) throws IOException {
Request request = chain.request();
if (connToInternet) {
request = request.newBuilder().header("Cache-Control", "public, max-age=" + 60).build();
} else {
request = request.newBuilder().header("Cache-Control", "public, only-if-cached, max-stale=" + 60 * 60 * 24 * 30).build();
}
return chain.proceed(request);
}
})
.build();
try {
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(instanceURL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create());
SSLContext sslContext = SSLContext.getInstance("TLS");
Retrofit retrofit = builder.build();
MemorizingTrustManager memorizingTrustManager = new MemorizingTrustManager(ctx);
sslContext.init(null, new X509TrustManager[]{memorizingTrustManager}, new SecureRandom());

Please keep the new lines as is and keep //.addInterceptor(logging) also for debugging purpose.

Please keep the new lines as is and keep `//.addInterceptor(logging)` also for debugging purpose.
return retrofit.create(serviceClass);
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cache(cache)
//.addInterceptor(logging)
.sslSocketFactory(sslContext.getSocketFactory(), memorizingTrustManager)
.hostnameVerifier(memorizingTrustManager.wrapHostnameVerifier(HttpsURLConnection.getDefaultHostnameVerifier()))
.addInterceptor(new Interceptor() {
}
@NonNull
@Override
public Response intercept(@NonNull Chain chain) throws IOException {
Request request = chain.request();
if(connToInternet) {
request = request.newBuilder().header("Cache-Control", "public, max-age=" + 60).build();
}
else {
request = request.newBuilder().header("Cache-Control", "public, only-if-cached, max-stale=" + 60 * 60 * 24 * 30).build();
}
return chain.proceed(request);
}
}).build();
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(instanceURL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create());
Retrofit retrofit = builder.build();
return retrofit.create(serviceClass);
}
catch(Exception e) {
Log.e("onFailure", e.toString());
}
return null;
}
}

View File

@ -1,17 +1,19 @@
package org.mian.gitnex.clients;
import android.content.Context;
import androidx.annotation.NonNull;
import android.util.Log;
import org.mian.gitnex.interfaces.ApiInterface;
import org.mian.gitnex.interfaces.WebInterface;
import org.mian.gitnex.helpers.ssl.MemorizingTrustManager;
import org.mian.gitnex.util.AppUtil;
import java.io.File;
import java.io.IOException;
import java.security.SecureRandom;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.X509TrustManager;
import okhttp3.Cache;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
@ -23,52 +25,64 @@ import retrofit2.converter.scalars.ScalarsConverterFactory;
public class RetrofitClient {
private Retrofit retrofit;
private Retrofit retrofit;
private RetrofitClient(String instanceUrl, Context ctx) {
private RetrofitClient(String instanceUrl, Context ctx) {
final boolean connToInternet = AppUtil.haveNetworkConnection(ctx);
int cacheSize = 50 * 1024 * 1024; // 50MB
File httpCacheDirectory = new File(ctx.getCacheDir(), "responses");
Cache cache = new Cache(httpCacheDirectory, cacheSize);
final boolean connToInternet = AppUtil.haveNetworkConnection(ctx);
int cacheSize = 50 * 1024 * 1024; // 50MB
File httpCacheDirectory = new File(ctx.getCacheDir(), "responses");
Cache cache = new Cache(httpCacheDirectory, cacheSize);
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cache(cache)
//.addInterceptor(logging)
.addInterceptor(new Interceptor() {
@NonNull
@Override public Response intercept(@NonNull Chain chain) throws IOException {
Request request = chain.request();
if (connToInternet) {
request = request.newBuilder().header("Cache-Control", "public, max-age=" + 60).build();
} else {
request = request.newBuilder().header("Cache-Control", "public, only-if-cached, max-stale=" + 60 * 60 * 24 * 30).build();
}
return chain.proceed(request);
}
})
.build();
try {
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(instanceUrl)
.client(okHttpClient)
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create());
SSLContext sslContext = SSLContext.getInstance("TLS");
retrofit = builder.build();
MemorizingTrustManager memorizingTrustManager = new MemorizingTrustManager(ctx);
sslContext.init(null, new X509TrustManager[]{memorizingTrustManager}, new SecureRandom());
}
OkHttpClient.Builder okHttpClient = new OkHttpClient.Builder()
.cache(cache)
//.addInterceptor(logging)

Please use if statement instead if possible.

Please use if statement instead if possible.
.sslSocketFactory(sslContext.getSocketFactory(), memorizingTrustManager)
.hostnameVerifier(memorizingTrustManager.wrapHostnameVerifier(HttpsURLConnection.getDefaultHostnameVerifier()))
.addInterceptor(chain -> {
public static synchronized RetrofitClient getInstance(String instanceUrl, Context ctx) {
return new RetrofitClient(instanceUrl, ctx);
}
Request request = chain.request();
if(connToInternet) {
request = request.newBuilder().header("Cache-Control", "public, max-age=" + 60).build();
}
else {
request = request.newBuilder().header("Cache-Control", "public, only-if-cached, max-stale=" + 60 * 60 * 24 * 30).build();
}
return chain.proceed(request);
});
public ApiInterface getApiInterface() {
return retrofit.create(ApiInterface.class);
}
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(instanceUrl)
.client(okHttpClient.build())
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create());
retrofit = builder.build();
}
catch(Exception e) {
Log.e("onFailure", e.toString());
}
}
public static synchronized RetrofitClient getInstance(String instanceUrl, Context ctx) {
return new RetrofitClient(instanceUrl, ctx);
}
public ApiInterface getApiInterface() {
return retrofit.create(ApiInterface.class);
}
public WebInterface getWebInterface() {
return retrofit.create(WebInterface.class);

View File

@ -14,8 +14,8 @@ import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.squareup.picasso.Picasso;
import org.mian.gitnex.R;
import org.mian.gitnex.clients.PicassoService;
import org.mian.gitnex.clients.RetrofitClient;
import org.mian.gitnex.helpers.Authorization;
import org.mian.gitnex.helpers.RoundedTransformation;
@ -104,7 +104,7 @@ public class OrganizationInfoFragment extends Fragment {
if (response.code() == 200) {
assert orgInfo != null;
Picasso.get().load(orgInfo.getAvatar_url()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(180, 180).centerCrop().into(orgAvatar);
PicassoService.getInstance(ctx).get().load(orgInfo.getAvatar_url()).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(180, 180).centerCrop().into(orgAvatar);
orgDescInfo.setText(orgInfo.getDescription());
orgWebsiteInfo.setText(orgInfo.getWebsite());
orgLocationInfo.setText(orgInfo.getLocation());

View File

@ -18,9 +18,9 @@ import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.google.android.material.tabs.TabLayout;
import com.squareup.picasso.Picasso;
import org.mian.gitnex.R;
import org.mian.gitnex.activities.MainActivity;
import org.mian.gitnex.clients.PicassoService;
import org.mian.gitnex.helpers.RoundedTransformation;
import org.mian.gitnex.util.TinyDB;
import java.util.Objects;
@ -48,7 +48,7 @@ public class ProfileFragment extends Fragment {
TextView userEmail = v.findViewById(R.id.userEmail);
userFullName.setText(tinyDb.getString("userFullname"));
Picasso.get().load(tinyDb.getString("userAvatar")).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(userAvatar);
PicassoService.getInstance(ctx).get().load(tinyDb.getString("userAvatar")).placeholder(R.drawable.loader_animated).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(userAvatar);
userLogin.setText(getString(R.string.usernameWithAt, tinyDb.getString("userLogin")));
userEmail.setText(tinyDb.getString("userEmail"));

View File

@ -13,7 +13,9 @@ import android.widget.LinearLayout;
import android.widget.Switch;
import android.widget.TextView;
import org.mian.gitnex.R;
import org.mian.gitnex.activities.MainActivity;
import org.mian.gitnex.helpers.Toasty;
import org.mian.gitnex.helpers.ssl.MemorizingTrustManager;
import org.mian.gitnex.util.TinyDB;
import java.util.Objects;
import androidx.annotation.NonNull;
@ -67,6 +69,7 @@ public class SettingsFragment extends Fragment {
LinearLayout homeScreenFrame = v.findViewById(R.id.homeScreenFrame);
LinearLayout customFontFrame = v.findViewById(R.id.customFontFrame);
LinearLayout themeFrame = v.findViewById(R.id.themeSelectionFrame);
LinearLayout certsFrame = v.findViewById(R.id.certsFrame);
Switch issuesSwitch = v.findViewById(R.id.switchIssuesBadge);
Switch pdfModeSwitch = v.findViewById(R.id.switchPdfMode);
@ -144,6 +147,28 @@ public class SettingsFragment extends Fragment {
pdfModeSwitch.setChecked(false);
}
// certs deletion
certsFrame.setOnClickListener(v1 -> {
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
builder.setTitle(getResources().getString(R.string.settingsCertsPopupTitle));
builder.setMessage(getResources().getString(R.string.settingsCertsPopupMessage));
builder.setPositiveButton(R.string.menuDeleteText, (dialog, which) -> {
ctx.getSharedPreferences(MemorizingTrustManager.KEYSTORE_NAME, Context.MODE_PRIVATE)
.edit()
.remove(MemorizingTrustManager.KEYSTORE_KEY)
.apply();
MainActivity.logout(Objects.requireNonNull(getActivity()), ctx);
});
builder.setNeutralButton(R.string.cancelButton, (dialog, which) -> dialog.dismiss());
builder.create().show();
});
// issues badge switcher
issuesSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {

View File

@ -0,0 +1,15 @@
package org.mian.gitnex.helpers.ssl;
/**
* Author Georg Lukas, modified by anonTree1417
*/
class MTMDecision {
final static int DECISION_INVALID = 0;
final static int DECISION_ABORT = 1;
final static int DECISION_ALWAYS = 2;
int state = DECISION_INVALID;
}

View File

@ -0,0 +1,44 @@
package org.mian.gitnex.helpers.ssl;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Intent;
import android.os.Bundle;
import org.mian.gitnex.R;
/**
* Author Georg Lukas, modified by anonTree1417
*/
public class MemorizingActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
int decisionId = intent.getIntExtra("DECISION_INTENT_ID", MTMDecision.DECISION_INVALID);
int titleId = intent.getIntExtra("DECISION_TITLE_ID", R.string.mtm_accept_cert);
String cert = intent.getStringExtra("DECISION_INTENT_CERT");
AlertDialog.Builder builder = new AlertDialog.Builder(MemorizingActivity.this);
builder.setTitle(titleId);
builder.setMessage(cert);
builder.setPositiveButton(R.string.mtm_decision_always, (dialog, which) -> onSendResult(decisionId, MTMDecision.DECISION_ALWAYS));
builder.setNeutralButton(R.string.mtm_decision_abort, (dialog, which) -> onSendResult(decisionId, MTMDecision.DECISION_ABORT));
builder.setOnCancelListener(dialog -> onSendResult(decisionId, MTMDecision.DECISION_ABORT));
builder.create().show();
}
private void onSendResult(int decisionId, int decision) {
MemorizingTrustManager.interactResult(decisionId, decision);
finish();
}
}

View File

@ -0,0 +1,670 @@
package org.mian.gitnex.helpers.ssl;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Handler;
import android.util.Base64;
import android.util.SparseArray;
import androidx.core.app.NotificationCompat;
import org.mian.gitnex.R;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertPathValidatorException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.Locale;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
/**
* Author Georg Lukas, modified by anonTree1417
*/
public class MemorizingTrustManager implements X509TrustManager {
private final static int NOTIFICATION_ID = 100509;
public final static String KEYSTORE_NAME = "keystore";
public final static String KEYSTORE_KEY = "keystore";
private Context context;
private NotificationManager notificationManager;
private static int decisionId = 0;
private static final SparseArray<MTMDecision> openDecisions = new SparseArray<>();
private Handler masterHandler;
private SharedPreferences keyStoreStorage;
private KeyStore appKeyStore;
private X509TrustManager defaultTrustManager;
private X509TrustManager appTrustManager;
/**
* Creates an instance of the MemorizingTrustManager class that falls back to a custom TrustManager.
* <p>
* You need to supply the application context. This has to be one of:
* - Application
* - Activity
* - Service
* <p>
* The context is used for file management, to display the dialog /
* notification and for obtaining translated strings.
*
* @param m Context for the application.
* @param defaultTrustManager Delegate trust management to this TM. If null, the user must accept every certificate.
*/
public MemorizingTrustManager(Context m, X509TrustManager defaultTrustManager) {
init(m);
this.appTrustManager = getTrustManager(appKeyStore);
this.defaultTrustManager = defaultTrustManager;
}
/**
* Creates an instance of the MemorizingTrustManager class using the system X509TrustManager.
* <p>
* You need to supply the application context. This has to be one of:
* - Application
* - Activity
* - Service
* <p>
* The context is used for file management, to display the dialog /
* notification and for obtaining translated strings.
*
* @param m Context for the application.
*/
public MemorizingTrustManager(Context m) {
init(m);
this.appTrustManager = getTrustManager(appKeyStore);
this.defaultTrustManager = getTrustManager(null);
}
private void init(Context m) {
context = m;
masterHandler = new Handler(m.getMainLooper());
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
keyStoreStorage = m.getSharedPreferences(KEYSTORE_NAME, Context.MODE_PRIVATE);
appKeyStore = loadAppKeyStore();
}
/**
* Returns a X509TrustManager list containing a new instance of
* TrustManagerFactory.
* <p>
* This function is meant for convenience only. You can use it
* as follows to integrate TrustManagerFactory for HTTPS sockets:
*
* <pre>
* SSLContext sc = SSLContext.getInstance("TLS");
* sc.init(null, MemorizingTrustManager.getInstanceList(this),
* new java.security.SecureRandom());
* HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
* </pre>
*
* @param c Activity or Service to show the Dialog / Notification
*/
public static X509TrustManager[] getInstanceList(Context c) {
return new X509TrustManager[]{new MemorizingTrustManager(c)};
}
/**
* Get a list of all certificate aliases stored in MTM.
*
* @return an {@link Enumeration} of all certificates
*/
private Enumeration<String> getCertificates() {
try {
return appKeyStore.aliases();
}
catch(KeyStoreException e) {
// this should never happen, however...
throw new RuntimeException(e);
}
}
/**
* Get a certificate for a given alias.
*
* @param alias the certificate's alias as returned by {@link #getCertificates()}.
* @return the certificate associated with the alias or <tt>null</tt> if none found.
*/
public Certificate getCertificate(String alias) {
try {
return appKeyStore.getCertificate(alias);
}
catch(KeyStoreException e) {
// this should never happen, however...
throw new RuntimeException(e);
}
}
/**
* Removes the given certificate from MTMs key store.
*
* <p>
* <b>WARNING</b>: this does not immediately invalidate the certificate. It is
* well possible that (a) data is transmitted over still existing connections or
* (b) new connections are created using TLS renegotiation, without a new cert
* check.
* </p>
*
* @param alias the certificate's alias as returned by {@link #getCertificates()}.
* @throws KeyStoreException if the certificate could not be deleted.
*/
public void deleteCertificate(String alias) throws KeyStoreException {
appKeyStore.deleteEntry(alias);
keyStoreUpdated();
}
/**
* Creates a new hostname verifier supporting user interaction.
*
* <p>This method creates a new {@link HostnameVerifier} that is bound to
* the given instance of {@link MemorizingTrustManager}, and leverages an
* existing {@link HostnameVerifier}. The returned verifier performs the
* following steps, returning as soon as one of them succeeds:
* /p>
* <ol>
* <li>Success, if the wrapped defaultVerifier accepts the certificate.</li>
* <li>Success, if the server certificate is stored in the keystore under the given hostname.</li>
* <li>Ask the user and return accordingly.</li>
* <li>Failure on exception.</li>
* </ol>
*
* @param defaultVerifier the {@link HostnameVerifier} that should perform the actual check
* @return a new hostname verifier using the MTM's key store
* @throws IllegalArgumentException if the defaultVerifier parameter is null
*/
public HostnameVerifier wrapHostnameVerifier(final HostnameVerifier defaultVerifier) {
if(defaultVerifier == null) {
throw new IllegalArgumentException("The default verifier may not be null");
}
return new MemorizingHostnameVerifier(defaultVerifier);
}
private X509TrustManager getTrustManager(KeyStore ks) {
try {
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init(ks);
for(TrustManager t : tmf.getTrustManagers()) {
if(t instanceof X509TrustManager) {
return (X509TrustManager) t;
}
}
}
catch(Exception e) {
e.printStackTrace();
}
return null;
}
private KeyStore loadAppKeyStore() {
KeyStore keyStore;
try {
keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
}
catch(KeyStoreException e) {
e.printStackTrace();
return null;
}
try {
keyStore.load(null, null);
}
catch(NoSuchAlgorithmException | CertificateException | IOException e) {
e.printStackTrace();
}
String keystore = keyStoreStorage.getString(KEYSTORE_KEY, null);
if(keystore != null) {
ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64.decode(keystore, Base64.DEFAULT));
try {
keyStore.load(inputStream, "MTM".toCharArray());
inputStream.close();
}
catch(Exception e) {
e.printStackTrace();
}
}
return keyStore;
}
private void storeCert(String alias, Certificate cert) {
try {
appKeyStore.setCertificateEntry(alias, cert);
}
catch(KeyStoreException e) {
e.printStackTrace();
return;
}
keyStoreUpdated();
}
private void storeCert(X509Certificate cert) {
storeCert(cert.getSubjectDN().toString(), cert);
}
private void keyStoreUpdated() {
// reload appTrustManager
appTrustManager = getTrustManager(appKeyStore);
// store KeyStore to shared preferences
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try {
appKeyStore.store(byteArrayOutputStream, "MTM".toCharArray());
byteArrayOutputStream.flush();
byteArrayOutputStream.close();
keyStoreStorage.edit().putString(KEYSTORE_KEY, Base64.encodeToString(byteArrayOutputStream.toByteArray(), Base64.DEFAULT)).apply();
}
catch(Exception e) {
e.printStackTrace();
}
}
// if the certificate is stored in the app key store, it is considered "known"
private boolean isCertKnown(X509Certificate cert) {
try {
return appKeyStore.getCertificateAlias(cert) != null;
}
catch(KeyStoreException e) {
return false;
}
}
private static boolean isExpiredException(Throwable e) {
do {
if(e instanceof CertificateExpiredException) {
return true;
}
e = e.getCause();
} while(e != null);
return false;
}
private static boolean isPathException(Throwable e) {
do {
if(e instanceof CertPathValidatorException) {
return true;
}
e = e.getCause();
} while(e != null);
return false;
}
private void checkCertTrusted(X509Certificate[] chain, String authType, boolean isServer) throws CertificateException {
try {
if(isServer) {
appTrustManager.checkServerTrusted(chain, authType);
}
else {
appTrustManager.checkClientTrusted(chain, authType);
}
}
catch(CertificateException ae) {
// if the cert is stored in our appTrustManager, we ignore expiredness
if(isExpiredException(ae) || isCertKnown(chain[0])) {
return;
}
try {
if(defaultTrustManager == null) {
throw ae;
}
if(isServer) {
defaultTrustManager.checkServerTrusted(chain, authType);
}
else {
defaultTrustManager.checkClientTrusted(chain, authType);
}
}
catch(CertificateException e) {
interactCert(chain, authType, e);
}
}
}
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
checkCertTrusted(chain, authType, false);
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
checkCertTrusted(chain, authType, true);
}
public X509Certificate[] getAcceptedIssuers() {
return defaultTrustManager.getAcceptedIssuers();
}
private static int createDecisionId(MTMDecision d) {
int myId;
synchronized(openDecisions) {
myId = decisionId;
openDecisions.put(myId, d);
decisionId += 1;
}
return myId;
}
private static String hexString(byte[] data) {
StringBuilder si = new StringBuilder();
for(int i = 0; i < data.length; i++) {
si.append(String.format("%02x", data[i]));
if(i < data.length - 1) {
si.append(":");
}
}
return si.toString();
}
private static String certHash(final X509Certificate cert, String digest) {
try {
MessageDigest md = MessageDigest.getInstance(digest);
md.update(cert.getEncoded());
return hexString(md.digest());
}
catch(CertificateEncodingException | NoSuchAlgorithmException e) {
return e.getMessage();
}
}
private static void certDetails(StringBuilder stringBuilder, X509Certificate c) {
SimpleDateFormat validityDateFormater = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
stringBuilder.append("\n")
.append(c.getSubjectDN().toString())
.append("\n")
.append(validityDateFormater.format(c.getNotBefore()))
.append(" - ")
.append(validityDateFormater.format(c.getNotAfter()))
.append("\nSHA-256: ")
.append(certHash(c, "SHA-256"))
.append("\nSHA-1: ")
.append(certHash(c, "SHA-1"))
.append("\nSigned by: ")
.append(c.getIssuerDN().toString())
.append("\n");
}
private String certChainMessage(final X509Certificate[] chain, CertificateException cause) {
Throwable e = cause;
StringBuilder stringBuilder = new StringBuilder();
if(isPathException(e)) {
stringBuilder.append(context.getString(R.string.mtm_trust_anchor));
}
else if(isExpiredException(e)) {
stringBuilder.append(context.getString(R.string.mtm_cert_expired));
}
else {
// get to the cause
while(e.getCause() != null) {
e = e.getCause();
}
stringBuilder.append(e.getLocalizedMessage());
}
stringBuilder.append("\n\n");
stringBuilder.append(context.getString(R.string.mtm_connect_anyway));
stringBuilder.append("\n\n");
stringBuilder.append(context.getString(R.string.mtm_cert_details));
for(X509Certificate c : chain) {
certDetails(stringBuilder, c);
}
return stringBuilder.toString();
}
private String hostNameMessage(X509Certificate cert, String hostname) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(context.getString(R.string.mtm_hostname_mismatch, hostname));
stringBuilder.append("\n\n");
try {
Collection<List<?>> sans = cert.getSubjectAlternativeNames();
if(sans == null) {
stringBuilder.append(cert.getSubjectDN());
stringBuilder.append("\n");
}
else {
for(List<?> altName : sans) {
Object name = altName.get(1);
if(name instanceof String) {
stringBuilder.append("[");
stringBuilder.append(altName.get(0));
stringBuilder.append("] ");
stringBuilder.append(name);
stringBuilder.append("\n");
}
}
}
}
catch(CertificateParsingException e) {
e.printStackTrace();
stringBuilder.append("<Parsing error: ");
stringBuilder.append(e.getLocalizedMessage());
stringBuilder.append(">\n");
}
stringBuilder.append("\n");
stringBuilder.append(context.getString(R.string.mtm_connect_anyway));
stringBuilder.append("\n\n");
stringBuilder.append(context.getString(R.string.mtm_cert_details));
certDetails(stringBuilder, cert);
return stringBuilder.toString();
}
/**
* Reflectively call
* <code>Notification.setLatestEventInfo(Context, CharSequence, CharSequence, PendingIntent)</code>
* since it was remove in Android API level 23.
*/
private static void setLatestEventInfoReflective(Notification notification, Context context, CharSequence mtmNotification, CharSequence certName, PendingIntent call) {
Method setLatestEventInfo;
try {
setLatestEventInfo = notification.getClass().getMethod("setLatestEventInfo", Context.class, CharSequence.class, CharSequence.class, PendingIntent.class);
}
catch(NoSuchMethodException e) {
throw new IllegalStateException(e);
}
try {
setLatestEventInfo.invoke(notification, context, mtmNotification, certName, call);
}
catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new IllegalStateException(e);
}
}
private void startActivityNotification(Intent intent, int decisionId, String certName) {
final PendingIntent call = PendingIntent.getActivity(context, 0, intent, 0);
final String mtmNotification = context.getString(R.string.mtm_notification);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, "ssl")
.setSmallIcon(android.R.drawable.ic_lock_lock)
.setContentTitle(mtmNotification)
.setContentText(certName)
.setTicker(certName)
.setContentIntent(call)
.setAutoCancel(true)
.setPriority(NotificationCompat.PRIORITY_HIGH);
notificationManager.notify(NOTIFICATION_ID + decisionId, builder.build());
}
private int interact(final String message, final int titleId) {
MTMDecision choice = new MTMDecision();
final int myId = createDecisionId(choice);
masterHandler.post(new Runnable() {
public void run() {
Intent intent = new Intent(context, MemorizingActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
intent.putExtra("DECISION_INTENT_ID", myId);
intent.putExtra("DECISION_INTENT_CERT", message);
intent.putExtra("DECISION_TITLE_ID", titleId);
try {
context.startActivity(intent);
}
catch(Exception e) {
startActivityNotification(intent, myId, message);
}
}
});
try {
synchronized(choice) {
choice.wait();
}
}
catch(InterruptedException e) {
e.printStackTrace();
}
return choice.state;
}
private void interactCert(final X509Certificate[] chain, String authType, CertificateException cause) throws CertificateException {
if(interact(certChainMessage(chain, cause), R.string.mtm_accept_cert) == MTMDecision.DECISION_ALWAYS) {
storeCert(chain[0]); // only store the server cert, not the whole chain
} else {
throw (cause);
}
}
private boolean interactHostname(X509Certificate cert, String hostname) {
if(interact(hostNameMessage(cert, hostname), R.string.mtm_accept_servername) == MTMDecision.DECISION_ALWAYS) {
storeCert(hostname, cert);
return true;
}
return false;
}
static void interactResult(int decisionId, int choice) {
MTMDecision d;
synchronized(openDecisions) {
d = openDecisions.get(decisionId);
openDecisions.remove(decisionId);
}
if(d == null) {
return;
}
synchronized(d) {
d.state = choice;
d.notify();
}
}
class MemorizingHostnameVerifier implements HostnameVerifier {
private HostnameVerifier defaultVerifier;
MemorizingHostnameVerifier(HostnameVerifier wrapped) {
defaultVerifier = wrapped;
}
@Override
public boolean verify(String hostname, SSLSession session) {
// if the default verifier accepts the hostname, we are done
if(defaultVerifier.verify(hostname, session)) {
return true;
}
// otherwise, we check if the hostname is an alias for this cert in our keystore
try {
X509Certificate cert = (X509Certificate) session.getPeerCertificates()[0];
if(cert.equals(appKeyStore.getCertificate(hostname.toLowerCase(Locale.US)))) {
return true;
}
else {
return interactHostname(cert, hostname);
}
}
catch(Exception e) {
e.printStackTrace();
return false;
}
}
}
}

View File

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="#368F73" android:viewportHeight="24.0"
android:viewportWidth="24.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M12,1L3,5v6c0,5.55 3.84,10.74 9,12 5.16,-1.26 9,-6.45 9,-12L21,5l-9,-4zM12,11.99h7c-0.53,4.12 -3.28,7.79 -7,8.94L12,12L5,12L5,6.3l7,-3.11v8.8z"/>
</vector>

View File

@ -33,7 +33,7 @@
layout="@layout/layout_settings_fileview"/>
<View
android:id="@+id/translationDivider"
android:id="@+id/securityDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="44dp"
@ -42,6 +42,20 @@
android:layout_marginBottom="20dp"
android:layout_below="@id/fileViewLayout" />
<include
android:id="@+id/securityLayout"
layout="@layout/layout_settings_security" />
<View
android:id="@+id/translationDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="44dp"
android:layout_marginEnd="5dp"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:layout_below="@id/securityLayout" />
<include
android:id="@+id/langLayout"
layout="@layout/layout_settings_languages" />

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/layoutSettingsCerts"
android:orientation="vertical"
android:layout_below="@+id/securityDivider"
android:background="?attr/primaryBackgroundColor">
<TextView
android:id="@+id/tvSecurity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableStart="@drawable/ic_security_24dp"
android:drawablePadding="20dp"
android:text="@string/settingsSecurityHeader"
android:textColor="@color/colorDarkGreen"
android:textSize="14sp"
android:textStyle="bold" />
<LinearLayout
android:id="@+id/certsFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:orientation="horizontal">
<TextView
android:id="@+id/tvCertHeader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:layout_marginTop="10dp"
android:layout_marginStart="44dp"
android:layout_marginEnd="4dp"
android:text="@string/settingsCertsSelectorHeader"
android:textColor="?attr/primaryTextColor"/>
</LinearLayout>
</LinearLayout>

View File

@ -92,6 +92,7 @@
<string name="urlInfoTooltip">1- Choose the correct protocol(https or http). \n2- Enter Gitea url e.g: try.gitea.io. \n3- If you have enabled 2FA for your account, enter the code in the OTP Code field. \n4- For HTTP basic auth use USERNAME@DOMAIN.COM in the URL field.</string>
<string name="loginFailed">Wrong username/password</string>
<string name="protocolDelimiter" translatable="false">://</string>
<string name="malformedUrl">Couldn\'t connect to host. Please check your URL or port for any errors.</string>
<string name="protocolError">It is not recommended to use HTTP protocol unless you are testing on local network.</string>
<string name="malformedJson">Malformed JSON was received. Server response was not successful.</string>
<string name="emptyFieldURL">Instance URL is required</string>
@ -245,6 +246,10 @@
<!-- settings -->
<string name="settingsLanguageHeaderText">Translation</string>
<string name="settingsSecurityHeader">Security</string>
<string name="settingsCertsSelectorHeader">Delete Trusted Certificates</string>
<string name="settingsCertsPopupTitle">Delete Trusted Certificates?</string>
<string name="settingsCertsPopupMessage">Are you sure to delete any manually trusted certificate or hostname? \n\nYou will also be logged out.</string>
<string name="settingsDateTimeHeaderText">Date &amp; Time</string>
<string name="settingsSave">Settings saved</string>
<string name="settingsLanguageSelectorHeader">Language</string>
@ -569,4 +574,18 @@
<string name="commitCommittedBy">Committed by %1$s</string>
<string name="viewCommits">View Commits</string>

No blank line required here

No blank line required here
<string name="changelogTitle" translatable="false">Changelog</string>
<!-- Memorizing Trust Manager -->
Review

Here too

Here too
<string name="mtm_notification">Certificate Verification</string>
<string name="mtm_accept_cert">Accept Unknown Certificate?</string>
<string name="mtm_trust_anchor">The server certificate is not signed by a known Certificate Authority.</string>
<string name="mtm_cert_expired">The server certificate is expired.</string>

And here too

And here too
<string name="mtm_accept_servername">Accept Mismatching Server Name?</string>
<string name="mtm_hostname_mismatch">Server could not authenticate as \&quot;%s\&quot;. The certificate is only valid for:</string>
<string name="mtm_connect_anyway">Do you want to connect anyway?</string>
<string name="mtm_cert_details">Certificate details:</string>
<string name="mtm_decision_always">Trust</string>
<string name="mtm_decision_once">Once</string>
<string name="mtm_decision_abort">Abort</string>
</resources>