Frontport and updates (#1429) All checks were successful ci/woodpecker/push/locale Pipeline was successful ci/woodpecker/push/build Pipeline was successful ci/woodpecker/push/check Pipeline was successful ci/woodpecker/push/finish Pipeline was successful ci/woodpecker/cron/check Pipeline was successful ci/woodpecker/cron/build Pipeline was successful ci/woodpecker/cron/locale Pipeline was successful
All checks were successful
ci/woodpecker/push/locale Pipeline was successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/check Pipeline was successful
ci/woodpecker/push/finish Pipeline was successful
ci/woodpecker/cron/check Pipeline was successful
ci/woodpecker/cron/build Pipeline was successful
ci/woodpecker/cron/locale Pipeline was successful
Reviewed-on: #1429 Co-authored-by: M M Arif <mmarif@swatian.com> Co-committed-by: M M Arif <mmarif@swatian.com>
This commit is contained in:
parent 5156eaeac2
commit 93e8075fe7
12 changed files with 163 additions and 221 deletions
| @ -14,6 +14,7 @@ GitNex is licensed under the GPLv3 License. Please refer to the LICENSE file for | |||
[<img alt='Get it on Google Play' src='https://codeberg.org/gitnex/GitNex/raw/branch/main/assets/google-play.png' height="80"/>](https://play.google.com/store/apps/details?id=org.mian.gitnex.pro) | ||||
[<img alt='Download builds and releases' src='https://codeberg.org/gitnex/GitNex/raw/branch/main/assets/apk-badge.png' height="82"/>](https://cloud.swatian.com/s/WS4k3seXnmfQppo) | ||||
[<img alt='Get it on OpenAPK' src='https://codeberg.org/gitnex/GitNex/raw/branch/main/assets/openapk.png' height="82"/>](https://www.openapk.net/gitnex-for-forgejo-and-gitea/org.mian.gitnex/) | ||||
[<img alt='Get it on IzzyOnDroid' src='https://codeberg.org/gitnex/GitNex/raw/branch/main/assets/IzzyOnDroid.png' height="82"/>](https://apt.izzysoft.de/fdroid/index/apk/org.mian.gitnex) | ||||
| ||||
## Note about Forgejo and Gitea version | ||||
| ||||
| @ -53,7 +54,7 @@ We use [Crowdin](https://crowdin.com/project/gitnex) for translations. If your l | |||
| ||||
**Link: https://crowdin.com/project/GitNex** | ||||
| ||||
## Screenshots: | ||||
## Screenshots | ||||
| ||||
[<img src="https://codeberg.org/gitnex/GitNex/raw/branch/main/fastlane/metadata/android/en-US/images/phoneScreenshots/001.png" alt="001.png" width="200"/>](https://codeberg.org/gitnex/GitNex/raw/branch/main/fastlane/metadata/android/en-US/images/phoneScreenshots/001.png) | [<img src="https://codeberg.org/gitnex/GitNex/raw/branch/main/fastlane/metadata/android/en-US/images/phoneScreenshots/002.png" alt="002.png" width="200"/>](https://codeberg.org/gitnex/GitNex/raw/branch/main/fastlane/metadata/android/en-US/images/phoneScreenshots/002.png) | [<img src="https://codeberg.org/gitnex/GitNex/raw/branch/main/fastlane/metadata/android/en-US/images/phoneScreenshots/003.png" alt="003.png" width="200"/>](https://codeberg.org/gitnex/GitNex/raw/branch/main/fastlane/metadata/android/en-US/images/phoneScreenshots/003.png) | [<img src="https://codeberg.org/gitnex/GitNex/raw/branch/main/fastlane/metadata/android/en-US/images/phoneScreenshots/004.png" alt="004.png" width="200"/>](https://codeberg.org/gitnex/GitNex/raw/branch/main/fastlane/metadata/android/en-US/images/phoneScreenshots/004.png) | ||||
---|---|---|--- | ||||
| @ -86,7 +87,6 @@ Thanks to all the open source libraries, contributors, and donors. | |||
- [ramseth001/TextDrawable](https://github.com/ramseth001/TextDrawable) | ||||
- [vdurmont/emoji-java](https://github.com/vdurmont/emoji-java) | ||||
- [skydoves/ColorPickerView](https://github.com/skydoves/ColorPickerView) | ||||
- [HamidrezaAmz/BreadcrumbsView](https://github.com/HamidrezaAmz/BreadcrumbsView) | ||||
- [Baseflow/PhotoView](https://github.com/Baseflow/PhotoView) | ||||
- [apache/commons](https://github.com/apache/commons-io) | ||||
- [ge0rg/MemorizingTrustManager](https://github.com/ge0rg/MemorizingTrustManager) | ||||
| @ -102,6 +102,10 @@ Thanks to all the open source libraries, contributors, and donors. | |||
- [google/material-design-icons](https://github.com/google/material-design-icons) | ||||
- [tabler/tabler-icons](https://github.com/tabler/tabler-icons) | ||||
| ||||
## Social | ||||
| ||||
[Follow me on Fediverse - mastodon.social/@mmarif](https://mastodon.social/@mmarif) | ||||
| ||||
[Follow me on Bluesky - mmarif.bsky.social](https://bsky.app/profile/mmarif.bsky.social) | ||||
| ||||
*All trademarks and logos are the properties of their respective owners.* | ||||
| |
| @ -8,8 +8,8 @@ android { | |||
applicationId "org.mian.gitnex" | ||||
minSdkVersion 23 | ||||
targetSdkVersion 35 | ||||
versionCode 800 | ||||
versionName "8.0.0" | ||||
versionCode 895 | ||||
versionName "9.0.0-dev" | ||||
multiDexEnabled true | ||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | ||||
compileSdk 35 | ||||
| @ -34,6 +34,9 @@ android { | |||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' | ||||
} | ||||
} | ||||
dependenciesInfo { | ||||
includeInApk = false | ||||
} | ||||
compileOptions { | ||||
coreLibraryDesugaringEnabled true | ||||
| ||||
| @ -96,7 +99,6 @@ dependencies { | |||
implementation "com.caverock:androidsvg-aar:1.4" | ||||
implementation "pl.droidsonroids.gif:android-gif-drawable:1.2.29" | ||||
implementation 'com.google.guava:guava:32.1.3-jre' | ||||
implementation "com.github.HamidrezaAmz:BreadcrumbsView:0.2.9" | ||||
//noinspection GradleDependency | ||||
implementation 'commons-io:commons-io:2.5' | ||||
implementation 'org.apache.commons:commons-lang3:3.13.0' | ||||
| |
| @ -169,6 +169,7 @@ public class IssueDetailActivity extends BaseActivity | |||
private final float buttonAlphaStatEnabled = 1F; | ||||
private int loadingFinished = 0; | ||||
private MentionHelper mentionHelper; | ||||
private boolean pullRequestFetchAttempted = false; | ||||
| ||||
private enum Mode { | ||||
EDIT, | ||||
| @ -394,6 +395,7 @@ public class IssueDetailActivity extends BaseActivity | |||
getSingleIssue(repoOwner, repoName, issueIndex); | ||||
getAttachments(); | ||||
fetchDataAsync(repoOwner, repoName, issueIndex); | ||||
getPullRequest(); | ||||
| ||||
viewBinding.statuses.setOnClickListener( | ||||
view -> { | ||||
| @ -1016,7 +1018,7 @@ public class IssueDetailActivity extends BaseActivity | |||
if (issue.hasIssue()) { | ||||
viewBinding.progressBar.setVisibility(View.GONE); | ||||
getSubscribed(); | ||||
initWithIssue(); | ||||
checkAndInitWithIssue(); | ||||
return; | ||||
} | ||||
| ||||
| @ -1037,9 +1039,9 @@ public class IssueDetailActivity extends BaseActivity | |||
| ||||
Issue singleIssue = response.body(); | ||||
assert singleIssue != null; | ||||
| ||||
issue.setIssue(singleIssue); | ||||
initWithIssue(); | ||||
loadingFinishedIssue = true; | ||||
checkAndInitWithIssue(); | ||||
} else if (response.code() == 401) { | ||||
| ||||
AlertDialogs.authorizationTokenRevokedDialog(ctx); | ||||
| @ -1054,6 +1056,8 @@ public class IssueDetailActivity extends BaseActivity | |||
public void onFailure(@NonNull Call<Issue> call, @NonNull Throwable t) { | ||||
| ||||
viewBinding.progressBar.setVisibility(View.GONE); | ||||
loadingFinishedIssue = true; | ||||
checkAndInitWithIssue(); | ||||
} | ||||
}); | ||||
| ||||
| @ -1100,26 +1104,34 @@ public class IssueDetailActivity extends BaseActivity | |||
| ||||
viewBinding.issuePrState.setVisibility(View.VISIBLE); | ||||
| ||||
if (issue.getIssue() == null) { | ||||
return; | ||||
} | ||||
| ||||
if (issue.getIssue().getPullRequest() != null) { | ||||
| ||||
viewBinding.statusesLvMain.setVisibility(View.VISIBLE); | ||||
| ||||
getStatuses(); | ||||
getPullRequest(); | ||||
| ||||
viewBinding.prInfoLayout.setVisibility(View.VISIBLE); | ||||
String displayName; | ||||
if (!issue.getPullRequest().getUser().getFullName().isEmpty()) { | ||||
displayName = issue.getPullRequest().getUser().getFullName(); | ||||
User user = issue.getIssue().getUser(); | ||||
if (user != null && user.getFullName() != null && !user.getFullName().isEmpty()) { | ||||
displayName = user.getFullName(); | ||||
} else { | ||||
displayName = issue.getPullRequest().getUser().getLogin(); | ||||
displayName = user != null && user.getLogin() != null ? user.getLogin() : "Unknown"; | ||||
} | ||||
| ||||
PullRequest pr = issue.getPullRequest(); | ||||
if (pr != null && pr.getHead() != null && pr.getBase() != null) { | ||||
viewBinding.prInfo.setText( | ||||
getString( | ||||
R.string.pr_info, | ||||
displayName, | ||||
pr.getHead().getRef(), | ||||
pr.getBase().getRef())); | ||||
} | ||||
viewBinding.prInfo.setText( | ||||
getString( | ||||
R.string.pr_info, | ||||
displayName, | ||||
issue.getPullRequest().getHead().getRef(), | ||||
issue.getPullRequest().getBase().getRef())); | ||||
| ||||
if (issue.getIssue().getPullRequest().isMerged()) { // merged | ||||
| ||||
| @ -1530,19 +1542,33 @@ public class IssueDetailActivity extends BaseActivity | |||
public void onResponse( | ||||
@NonNull Call<PullRequest> call, | ||||
@NonNull Response<PullRequest> response) { | ||||
pullRequestFetchAttempted = true; | ||||
if (response.isSuccessful() && response.body() != null) { | ||||
issue.setPullRequest(response.body()); | ||||
loadingFinishedPr = true; | ||||
updateMenuState(); | ||||
} else { | ||||
loadingFinishedPr = true; | ||||
} | ||||
checkAndInitWithIssue(); | ||||
} | ||||
| ||||
@Override | ||||
public void onFailure( | ||||
@NonNull Call<PullRequest> call, @NonNull Throwable t) {} | ||||
@NonNull Call<PullRequest> call, @NonNull Throwable t) { | ||||
pullRequestFetchAttempted = true; | ||||
loadingFinishedPr = true; | ||||
checkAndInitWithIssue(); | ||||
} | ||||
}); | ||||
} | ||||
| ||||
private void checkAndInitWithIssue() { | ||||
if (loadingFinishedIssue || pullRequestFetchAttempted) { | ||||
initWithIssue(); | ||||
} | ||||
} | ||||
| ||||
private void getRepoInfo() { | ||||
Call<Repository> call = | ||||
RetrofitClient.getApiInterface(ctx) | ||||
| @ -1800,15 +1826,16 @@ public class IssueDetailActivity extends BaseActivity | |||
| ||||
private void getStatuses() { | ||||
| ||||
PullRequest pr = issue.getPullRequest(); | ||||
if (pr == null || pr.getHead() == null || pr.getHead().getRef() == null) { | ||||
viewBinding.statusesLvMain.setVisibility(View.GONE); | ||||
return; | ||||
} | ||||
| ||||
String headRef = pr.getHead().getSha(); | ||||
| ||||
RetrofitClient.getApiInterface(ctx) | ||||
.repoListStatuses( | ||||
repoOwner, | ||||
repoName, | ||||
issue.getRepository().getBranchRef(), | ||||
null, | ||||
null, | ||||
null, | ||||
null) | ||||
.repoListStatuses(repoOwner, repoName, headRef, null, null, null, null) | ||||
.enqueue( | ||||
new Callback<>() { | ||||
| ||||
| @ -1862,6 +1889,7 @@ public class IssueDetailActivity extends BaseActivity | |||
public void onFailure( | ||||
@NonNull Call<List<CommitStatus>> call, @NonNull Throwable t) { | ||||
| ||||
viewBinding.statusesLvMain.setVisibility(View.GONE); | ||||
checkLoading(); | ||||
if (ctx != null) { | ||||
Toasty.error(ctx, getString(R.string.genericError)); | ||||
| |
| @ -61,7 +61,6 @@ import org.mian.gitnex.helpers.AppUtil; | |||
import org.mian.gitnex.helpers.ChangeLog; | ||||
import org.mian.gitnex.helpers.Toasty; | ||||
import org.mian.gitnex.structs.BottomSheetListener; | ||||
import org.mian.gitnex.structs.FragmentRefreshListener; | ||||
import retrofit2.Call; | ||||
import retrofit2.Callback; | ||||
| ||||
| @ -120,6 +119,8 @@ public class MainActivity extends BaseActivity | |||
| ||||
noConnection = false; | ||||
| ||||
loadUserInfo(); | ||||
| ||||
Toolbar toolbar = activityMainBinding.toolbar; | ||||
toolbarTitle = activityMainBinding.toolbarTitle; | ||||
| ||||
| @ -501,24 +502,6 @@ public class MainActivity extends BaseActivity | |||
} | ||||
} | ||||
| ||||
handler.postDelayed( | ||||
() -> { | ||||
boolean connToInternet = AppUtil.hasNetworkConnection(appCtx); | ||||
if (!connToInternet) { | ||||
| ||||
if (!noConnection) { | ||||
Toasty.error( | ||||
ctx, getResources().getString(R.string.checkNetConnection)); | ||||
} | ||||
noConnection = true; | ||||
} else { | ||||
| ||||
loadUserInfo(); | ||||
noConnection = false; | ||||
} | ||||
}, | ||||
750); | ||||
| ||||
handler.postDelayed( | ||||
() -> { | ||||
boolean connToInternet = AppUtil.hasNetworkConnection(appCtx); | ||||
| |
| @ -11,6 +11,7 @@ import com.bumptech.glide.load.model.GlideUrl; | |||
import com.bumptech.glide.module.AppGlideModule; | ||||
import java.io.InputStream; | ||||
import okhttp3.OkHttpClient; | ||||
import org.mian.gitnex.activities.BaseActivity; | ||||
| ||||
/** | ||||
* @author mmarif | ||||
| @ -26,7 +27,11 @@ public class GlideService extends AppGlideModule { | |||
@Override | ||||
public void registerComponents( | ||||
@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) { | ||||
OkHttpClient okHttpClient = GlideHttpClient.getUnsafeOkHttpClient(); | ||||
String token = ""; | ||||
if (context instanceof BaseActivity) { | ||||
token = ((BaseActivity) context).getAccount().getAuthorization(); | ||||
} | ||||
OkHttpClient okHttpClient = RetrofitClient.getOkHttpClient(context, token); | ||||
registry.replace( | ||||
GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory(okHttpClient)); | ||||
} | ||||
| |
| @ -1,7 +1,6 @@ | |||
package org.mian.gitnex.clients; | ||||
| ||||
import android.content.Context; | ||||
import android.util.Log; | ||||
import androidx.annotation.NonNull; | ||||
import com.google.gson.GsonBuilder; | ||||
import java.io.File; | ||||
| @ -15,12 +14,16 @@ import java.util.Locale; | |||
import java.util.Map; | ||||
import java.util.Objects; | ||||
import java.util.concurrent.ConcurrentHashMap; | ||||
import java.util.concurrent.TimeUnit; | ||||
import javax.net.ssl.HttpsURLConnection; | ||||
import javax.net.ssl.SSLContext; | ||||
import javax.net.ssl.X509TrustManager; | ||||
import okhttp3.Cache; | ||||
import okhttp3.CacheControl; | ||||
import okhttp3.Interceptor; | ||||
import okhttp3.OkHttpClient; | ||||
import okhttp3.Request; | ||||
import okhttp3.Response; | ||||
import org.gitnex.tea4j.v2.apis.AdminApi; | ||||
import org.gitnex.tea4j.v2.apis.IssueApi; | ||||
import org.gitnex.tea4j.v2.apis.MiscellaneousApi; | ||||
| @ -52,89 +55,120 @@ public class RetrofitClient { | |||
| ||||
private static final Map<String, ApiInterface> apiInterfaces = new ConcurrentHashMap<>(); | ||||
private static final Map<String, WebApi> webInterfaces = new ConcurrentHashMap<>(); | ||||
private static final int CACHE_SIZE_MB = 50; | ||||
private static final int MAX_AGE_SECONDS = 60 * 5; | ||||
private static final int MAX_STALE_SECONDS = 60 * 60 * 24 * 30; | ||||
| ||||
private static Retrofit createRetrofit( | ||||
Context context, | ||||
String instanceUrl, | ||||
boolean cacheEnabled, | ||||
String token, | ||||
File cacheFile) { | ||||
private static OkHttpClient buildOkHttpClient(Context context, String token, File cacheFile) { | ||||
| ||||
// HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); | ||||
// logging.setLevel(HttpLoggingInterceptor.Level.BODY); | ||||
| ||||
try { | ||||
| ||||
SSLContext sslContext = SSLContext.getInstance("TLS"); | ||||
| ||||
MemorizingTrustManager memorizingTrustManager = new MemorizingTrustManager(context); | ||||
sslContext.init( | ||||
null, new X509TrustManager[] {memorizingTrustManager}, new SecureRandom()); | ||||
| ||||
ApiKeyAuth auth = new ApiKeyAuth("header", "Authorization"); | ||||
auth.setApiKey(token); | ||||
| ||||
OkHttpClient.Builder okHttpClient = | ||||
new OkHttpClient.Builder() | ||||
// .addInterceptor(logging) | ||||
.addInterceptor(auth) | ||||
.sslSocketFactory(sslContext.getSocketFactory(), memorizingTrustManager) | ||||
.hostnameVerifier( | ||||
memorizingTrustManager.wrapHostnameVerifier( | ||||
HttpsURLConnection.getDefaultHostnameVerifier())); | ||||
| ||||
if (cacheEnabled && cacheFile != null) { | ||||
if (cacheFile != null) { | ||||
int cacheSize = CACHE_SIZE_MB; | ||||
try { | ||||
cacheSize = | ||||
FilesData.returnOnlyNumberFileSize( | ||||
AppDatabaseSettings.getSettingsValue( | ||||
context, AppDatabaseSettings.APP_DATA_CACHE_SIZE_KEY)); | ||||
} catch (Exception ignored) { | ||||
} | ||||
cacheSize = cacheSize * 1024 * 1024; | ||||
| ||||
int cacheSize = | ||||
FilesData.returnOnlyNumberFileSize( | ||||
AppDatabaseSettings.getSettingsValue( | ||||
context, | ||||
AppDatabaseSettings.APP_DATA_CACHE_SIZE_KEY)) | ||||
* 1024 | ||||
* 1024; | ||||
Cache cache = new Cache(cacheFile, cacheSize); | ||||
File cacheDir = new File(context.getCacheDir(), "http-cache"); | ||||
if (!cacheDir.exists()) { | ||||
if (!cacheDir.mkdirs()) { | ||||
throw new RuntimeException( | ||||
"Failed to create cache directory: " + cacheDir.getAbsolutePath()); | ||||
} | ||||
} | ||||
Cache cache = new Cache(cacheDir, cacheSize); | ||||
okHttpClient.cache(cache); | ||||
| ||||
Interceptor cacheInterceptor = | ||||
chain -> { | ||||
Request originalRequest = chain.request(); | ||||
boolean hasNetwork = AppUtil.hasNetworkConnection(context); | ||||
CacheControl.Builder cacheControlBuilder = new CacheControl.Builder(); | ||||
if (hasNetwork) { | ||||
cacheControlBuilder.maxAge(MAX_AGE_SECONDS, TimeUnit.SECONDS); | ||||
} else { | ||||
cacheControlBuilder | ||||
.onlyIfCached() | ||||
.maxStale(MAX_STALE_SECONDS, TimeUnit.SECONDS); | ||||
} | ||||
CacheControl cacheControl = cacheControlBuilder.build(); | ||||
| ||||
Request modifiedRequest = | ||||
originalRequest.newBuilder().cacheControl(cacheControl).build(); | ||||
| ||||
return chain.proceed(modifiedRequest); | ||||
}; | ||||
| ||||
Interceptor forceCacheInterceptor = | ||||
chain -> { | ||||
Request request = chain.request(); | ||||
Response response = chain.proceed(request); | ||||
if (request.method().equals("GET") && response.isSuccessful()) { | ||||
return response.newBuilder() | ||||
.header( | ||||
"Cache-Control", | ||||
"public, max-age=" + MAX_AGE_SECONDS) | ||||
.removeHeader("Pragma") | ||||
.build(); | ||||
} | ||||
return response; | ||||
}; | ||||
| ||||
okHttpClient | ||||
.cache(cache) | ||||
.addInterceptor( | ||||
chain -> { | ||||
Request request = chain.request(); | ||||
| ||||
request = | ||||
AppUtil.hasNetworkConnection(context) | ||||
? request.newBuilder() | ||||
.header( | ||||
"Cache-Control", | ||||
"public, max-age=" + 60) | ||||
.build() | ||||
: request.newBuilder() | ||||
.header( | ||||
"Cache-Control", | ||||
"public, only-if-cached, max-stale=" | ||||
+ 60 * 60 * 24 * 30) | ||||
.build(); | ||||
| ||||
return chain.proceed(request); | ||||
}); | ||||
.addInterceptor(auth) | ||||
.addInterceptor(cacheInterceptor) | ||||
.addNetworkInterceptor(forceCacheInterceptor); | ||||
} | ||||
| ||||
return new Retrofit.Builder() | ||||
.baseUrl(instanceUrl) | ||||
.client(okHttpClient.build()) | ||||
.addConverterFactory(ScalarsConverterFactory.create()) | ||||
.addConverterFactory( | ||||
GsonConverterFactory.create( | ||||
new GsonBuilder() | ||||
.setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") | ||||
.create())) | ||||
.addConverterFactory(DateQueryConverterFactory.create()) | ||||
.build(); | ||||
return okHttpClient.build(); | ||||
| ||||
} catch (Exception e) { | ||||
| ||||
Log.e("onFailureRetrofit", e.toString()); | ||||
throw new RuntimeException(e); | ||||
} | ||||
} | ||||
| ||||
return null; | ||||
private static Retrofit createRetrofit( | ||||
Context context, String instanceUrl, String token, File cacheFile) { | ||||
OkHttpClient okHttpClient = buildOkHttpClient(context, token, cacheFile); | ||||
return new Retrofit.Builder() | ||||
.baseUrl(instanceUrl) | ||||
.client(okHttpClient) | ||||
.addConverterFactory(ScalarsConverterFactory.create()) | ||||
.addConverterFactory( | ||||
GsonConverterFactory.create( | ||||
new GsonBuilder() | ||||
.setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") | ||||
.create())) | ||||
.addConverterFactory(DateQueryConverterFactory.create()) | ||||
.build(); | ||||
} | ||||
| ||||
public static OkHttpClient getOkHttpClient(Context context, String token) { | ||||
File cacheFile = new File(context.getCacheDir(), "http-cache"); | ||||
return buildOkHttpClient(context, token, cacheFile); | ||||
} | ||||
| ||||
public static ApiInterface getApiInterface(Context context) { | ||||
| @ -146,10 +180,8 @@ public class RetrofitClient { | |||
} | ||||
| ||||
public static WebApi getWebInterface(Context context) { | ||||
| ||||
String instanceUrl = ((BaseActivity) context).getAccount().getAccount().getInstanceUrl(); | ||||
instanceUrl = instanceUrl.substring(0, instanceUrl.lastIndexOf("api/v1/")); | ||||
| ||||
return getWebInterface( | ||||
context, | ||||
instanceUrl, | ||||
| @ -158,7 +190,6 @@ public class RetrofitClient { | |||
} | ||||
| ||||
public static WebApi getWebInterface(Context context, String url) { | ||||
| ||||
return getWebInterface( | ||||
context, | ||||
url, | ||||
| @ -168,45 +199,35 @@ public class RetrofitClient { | |||
| ||||
public static ApiInterface getApiInterface( | ||||
Context context, String url, String token, File cacheFile) { | ||||
| ||||
String key = token.hashCode() + "@" + url; | ||||
if (!apiInterfaces.containsKey(key)) { | ||||
synchronized (RetrofitClient.class) { | ||||
if (!apiInterfaces.containsKey(key)) { | ||||
| ||||
ApiInterface apiInterface = | ||||
Objects.requireNonNull( | ||||
createRetrofit(context, url, true, token, cacheFile)) | ||||
Objects.requireNonNull(createRetrofit(context, url, token, cacheFile)) | ||||
.create(ApiInterface.class); | ||||
apiInterfaces.put(key, apiInterface); | ||||
| ||||
return apiInterface; | ||||
} | ||||
} | ||||
} | ||||
| ||||
return apiInterfaces.get(key); | ||||
} | ||||
| ||||
public static WebApi getWebInterface( | ||||
Context context, String url, String token, File cacheFile) { | ||||
| ||||
String key = token.hashCode() + "@" + url; | ||||
if (!webInterfaces.containsKey(key)) { | ||||
synchronized (RetrofitClient.class) { | ||||
if (!webInterfaces.containsKey(key)) { | ||||
| ||||
WebApi webInterface = | ||||
Objects.requireNonNull( | ||||
createRetrofit(context, url, false, token, cacheFile)) | ||||
Objects.requireNonNull(createRetrofit(context, url, token, cacheFile)) | ||||
.create(WebApi.class); | ||||
webInterfaces.put(key, webInterface); | ||||
| ||||
return webInterface; | ||||
} | ||||
} | ||||
} | ||||
| ||||
return webInterfaces.get(key); | ||||
} | ||||
| ||||
| @ -224,7 +245,6 @@ public class RetrofitClient { | |||
PackageApi {} | ||||
| ||||
private static class DateQueryConverterFactory extends Converter.Factory { | ||||
| ||||
public static DateQueryConverterFactory create() { | ||||
return new DateQueryConverterFactory(); | ||||
} | ||||
| @ -240,16 +260,13 @@ public class RetrofitClient { | |||
return null; | ||||
} | ||||
| ||||
private static final class DateQueryConverter implements Converter<Date, String> { | ||||
| ||||
private static class DateQueryConverter implements Converter<Date, String> { | ||||
static final DateQueryConverter INSTANCE = new DateQueryConverter(); | ||||
| ||||
private static final ThreadLocal<DateFormat> DF = | ||||
new ThreadLocal<>() { | ||||
| ||||
@Override | ||||
public DateFormat initialValue() { | ||||
| ||||
return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US); | ||||
} | ||||
}; | ||||
| |
| @ -1,6 +1,5 @@ | |||
package org.mian.gitnex.fragments; | ||||
| ||||
import android.annotation.SuppressLint; | ||||
import android.app.Dialog; | ||||
import android.content.Intent; | ||||
import android.graphics.Rect; | ||||
| @ -24,12 +23,8 @@ import androidx.lifecycle.ViewModelProvider; | |||
import androidx.recyclerview.widget.LinearLayoutManager; | ||||
import androidx.recyclerview.widget.RecyclerView; | ||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder; | ||||
import java.util.ArrayList; | ||||
import java.util.Collections; | ||||
import java.util.List; | ||||
import java.util.Objects; | ||||
import moe.feng.common.view.breadcrumbs.DefaultBreadcrumbsCallback; | ||||
import moe.feng.common.view.breadcrumbs.model.BreadcrumbItem; | ||||
import org.gitnex.tea4j.v2.models.Branch; | ||||
import org.gitnex.tea4j.v2.models.ContentsResponse; | ||||
import org.mian.gitnex.R; | ||||
| @ -95,31 +90,6 @@ public class FilesFragment extends Fragment implements FilesAdapter.FilesAdapter | |||
| ||||
binding.branchTitle.setText(repository.getBranchRef()); | ||||
| ||||
binding.breadcrumbsView.setItems( | ||||
new ArrayList<>( | ||||
Collections.singletonList( | ||||
BreadcrumbItem.createSimpleItem(repository.getBranchRef())))); | ||||
// noinspection unchecked | ||||
binding.breadcrumbsView.setCallback( | ||||
new DefaultBreadcrumbsCallback<BreadcrumbItem>() { | ||||
| ||||
@SuppressLint("SetTextI18n") | ||||
@Override | ||||
public void onNavigateBack(BreadcrumbItem item, int position) { | ||||
| ||||
if (position == 0) { | ||||
path.clear(); | ||||
} else { | ||||
path.pop(path.size() - position); | ||||
} | ||||
refresh(); | ||||
} | ||||
| ||||
@Override | ||||
public void onNavigateNewLocation( | ||||
BreadcrumbItem newItem, int changedPosition) {} | ||||
}); | ||||
| ||||
requireActivity() | ||||
.getOnBackPressedDispatcher() | ||||
.addCallback( | ||||
| @ -136,7 +106,6 @@ public class FilesFragment extends Fragment implements FilesAdapter.FilesAdapter | |||
return; | ||||
} | ||||
path.remove(path.size() - 1); | ||||
binding.breadcrumbsView.removeLastItem(); | ||||
if (path.size() == 0) { | ||||
fetchDataAsync( | ||||
repository.getOwner(), | ||||
| @ -163,19 +132,12 @@ public class FilesFragment extends Fragment implements FilesAdapter.FilesAdapter | |||
repoBranch -> { | ||||
repository.setBranchRef(repoBranch); | ||||
path.clear(); | ||||
binding.breadcrumbsView.setItems( | ||||
new ArrayList<>( | ||||
Collections.singletonList( | ||||
BreadcrumbItem.createSimpleItem( | ||||
repository.getBranchRef())))); | ||||
refresh(); | ||||
}); | ||||
| ||||
String dir = requireActivity().getIntent().getStringExtra("dir"); | ||||
if (dir != null) { | ||||
for (String segment : dir.split("/")) { | ||||
binding.breadcrumbsView.addItem( | ||||
new BreadcrumbItem(Collections.singletonList(segment))); | ||||
path.add(segment); | ||||
} | ||||
} | ||||
| @ -254,8 +216,6 @@ public class FilesFragment extends Fragment implements FilesAdapter.FilesAdapter | |||
switch (file.getType()) { | ||||
case "dir": | ||||
path.addWithoutEncoding(file.getName()); | ||||
binding.breadcrumbsView.addItem( | ||||
new BreadcrumbItem(Collections.singletonList(file.getName()))); | ||||
refresh(); | ||||
break; | ||||
| ||||
| |
| @ -492,13 +492,6 @@ public class RepoInfoFragment extends Fragment { | |||
requireActivity() | ||||
.runOnUiThread( | ||||
() -> { | ||||
Toasty.error( | ||||
ctx, | ||||
ctx | ||||
.getString( | ||||
R | ||||
.string | ||||
.genericError)); | ||||
binding | ||||
.fileContentsFrameHeader | ||||
.setVisibility( | ||||
| |
| @ -15,37 +15,6 @@ | |||
android:visibility="gone" | ||||
tools:visibility="visible"> | ||||
| ||||
<com.google.android.material.card.MaterialCardView | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
style="?attr/materialCardViewElevatedStyle" | ||||
android:layout_marginStart="@dimen/dimen8dp" | ||||
android:layout_marginEnd="@dimen/dimen8dp" | ||||
android:layout_marginTop="@dimen/dimen8dp" | ||||
android:visibility="gone" | ||||
app:cardElevation="@dimen/dimen0dp"> | ||||
| ||||
<LinearLayout | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:foreground="?android:attr/selectableItemBackground" | ||||
android:background="?attr/materialCardBackgroundColor" | ||||
android:orientation="horizontal"> | ||||
| ||||
<moe.feng.common.view.breadcrumbs.BreadcrumbsView | ||||
android:id="@+id/breadcrumbs_view" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:saveEnabled="false" | ||||
android:text="@string/filesBreadcrumbRoot" | ||||
app:CustomTextSize="@dimen/dimen16sp" | ||||
app:SelectedTextColor="?attr/primaryTextColor" | ||||
app:UnSelectedTextColor="?attr/inputSelectedColor" /> | ||||
| ||||
</LinearLayout> | ||||
| ||||
</com.google.android.material.card.MaterialCardView> | ||||
| ||||
<!-- branch section --> | ||||
<com.google.android.material.card.MaterialCardView | ||||
android:id="@+id/branch_section" | ||||
| |
| @ -1,27 +1,8 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | ||||
<changelog> | ||||
| ||||
<release version="8.0.0" versioncode="800"> | ||||
<change>New: Create a branch when creating, editing, or deleting a file</change> | ||||
<change>New: Search within files</change> | ||||
<change>New: Show tags if there are no releases</change> | ||||
<change>New: Display a user activity heatmap on the profile</change> | ||||
<change>New: Add pinch-and-zoom support for files</change> | ||||
<change>New: Mention users in issues, pull requests, and comments</change> | ||||
<change>New: Filter issues by labels</change> | ||||
<change>New: Filter issues where I am mentioned</change> | ||||
<change>New: Manage dependencies for issues and pull requests (view, add, remove)</change> | ||||
<change>New: Manage tracked time (view, add, remove)</change> | ||||
<change>New: Add a Created by Me filter to My Issues</change> | ||||
<change>Improvement: UI enhancements</change> | ||||
<change>Improvement: Paginate repository stargazers and watchers</change> | ||||
<change>Improvement: Paginate organization members and repository collaborators</change> | ||||
<change>Improvement: Add pagination for repository branches</change> | ||||
<change>Improvement: Improve the loading of large files in the file viewer</change> | ||||
<change>Improvement: Update translations</change> | ||||
<change>Bugfix: Fix the commits UI</change> | ||||
<change>Bugfix: Fix the issues progress bar</change> | ||||
<change>Removal: HTTP Basic Authentication (username/password login) has been removed</change> | ||||
<release version="9.0.0-dev" versioncode="895"> | ||||
<change>Under development</change> | ||||
</release> | ||||
| ||||
</changelog> | ||||
| |
BIN assets/IzzyOnDroid.png Normal file
BIN
assets/IzzyOnDroid.png Normal file Binary file not shown.
After Width: | Height: | Size: 20 KiB |
| @ -7,7 +7,7 @@ buildscript { | |||
mavenCentral() | ||||
} | ||||
dependencies { | ||||
classpath 'com.android.tools.build:gradle:8.9.0' | ||||
classpath 'com.android.tools.build:gradle:8.9.1' | ||||
} | ||||
} | ||||
| ||||
| |
Loading…
Add table
Add a link
Reference in a new issue