526 lines
15 KiB
Java
526 lines
15 KiB
Java
package org.mian.gitnex.activities;
|
|
|
|
import android.app.Activity;
|
|
import android.app.NotificationManager;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.graphics.Bitmap;
|
|
import android.graphics.Typeface;
|
|
import android.os.Bundle;
|
|
import android.text.method.ScrollingMovementMethod;
|
|
import android.view.Gravity;
|
|
import android.view.Menu;
|
|
import android.view.MenuInflater;
|
|
import android.view.MenuItem;
|
|
import android.view.View;
|
|
import androidx.activity.result.ActivityResultLauncher;
|
|
import androidx.activity.result.contract.ActivityResultContracts;
|
|
import androidx.annotation.NonNull;
|
|
import androidx.core.app.NotificationCompat;
|
|
import com.vdurmont.emoji.EmojiParser;
|
|
import java.io.IOException;
|
|
import java.io.OutputStream;
|
|
import java.util.Arrays;
|
|
import java.util.Objects;
|
|
import okhttp3.ResponseBody;
|
|
import org.apache.commons.io.FilenameUtils;
|
|
import org.gitnex.tea4j.v2.models.ContentsResponse;
|
|
import org.mian.gitnex.R;
|
|
import org.mian.gitnex.clients.RetrofitClient;
|
|
import org.mian.gitnex.databinding.ActivityFileViewBinding;
|
|
import org.mian.gitnex.fragments.BottomSheetFileViewerFragment;
|
|
import org.mian.gitnex.helpers.AlertDialogs;
|
|
import org.mian.gitnex.helpers.AppUtil;
|
|
import org.mian.gitnex.helpers.Constants;
|
|
import org.mian.gitnex.helpers.Images;
|
|
import org.mian.gitnex.helpers.Markdown;
|
|
import org.mian.gitnex.helpers.Toasty;
|
|
import org.mian.gitnex.helpers.contexts.RepositoryContext;
|
|
import org.mian.gitnex.notifications.Notifications;
|
|
import org.mian.gitnex.structs.BottomSheetListener;
|
|
import retrofit2.Call;
|
|
import retrofit2.Response;
|
|
|
|
/**
|
|
* @author M M Arif
|
|
*/
|
|
public class FileViewActivity extends BaseActivity implements BottomSheetListener {
|
|
|
|
private ActivityFileViewBinding binding;
|
|
private ContentsResponse file;
|
|
private RepositoryContext repository;
|
|
ActivityResultLauncher<Intent> activityResultLauncher =
|
|
registerForActivityResult(
|
|
new ActivityResultContracts.StartActivityForResult(),
|
|
result -> {
|
|
if (result.getResultCode() == Activity.RESULT_OK) {
|
|
|
|
assert result.getData() != null;
|
|
|
|
try {
|
|
|
|
OutputStream outputStream =
|
|
getContentResolver()
|
|
.openOutputStream(
|
|
Objects.requireNonNull(
|
|
result.getData().getData()));
|
|
|
|
NotificationCompat.Builder builder =
|
|
new NotificationCompat.Builder(ctx, ctx.getPackageName())
|
|
.setContentTitle(
|
|
getString(
|
|
R.string
|
|
.fileViewerNotificationTitleStarted))
|
|
.setContentText(
|
|
getString(
|
|
R.string
|
|
.fileViewerNotificationDescriptionStarted,
|
|
file.getName()))
|
|
.setSmallIcon(R.drawable.gitnex_transparent)
|
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
|
.setChannelId(
|
|
Constants.downloadNotificationChannelId)
|
|
.setProgress(100, 0, false)
|
|
.setOngoing(true);
|
|
|
|
int notificationId = Notifications.uniqueNotificationId(ctx);
|
|
|
|
NotificationManager notificationManager =
|
|
(NotificationManager)
|
|
getSystemService(Context.NOTIFICATION_SERVICE);
|
|
notificationManager.notify(notificationId, builder.build());
|
|
|
|
Thread thread =
|
|
new Thread(
|
|
() -> {
|
|
try {
|
|
|
|
Call<ResponseBody> call =
|
|
RetrofitClient.getWebInterface(ctx)
|
|
.getFileContents(
|
|
repository
|
|
.getOwner(),
|
|
repository
|
|
.getName(),
|
|
repository
|
|
.getBranchRef(),
|
|
file.getPath());
|
|
|
|
Response<ResponseBody> response =
|
|
call.execute();
|
|
|
|
assert response.body() != null;
|
|
|
|
builder.setOngoing(false)
|
|
.setContentTitle(
|
|
getString(
|
|
R.string
|
|
.fileViewerNotificationTitleFinished))
|
|
.setContentText(
|
|
getString(
|
|
R.string
|
|
.fileViewerNotificationDescriptionFinished,
|
|
file.getName()));
|
|
|
|
AppUtil.copyProgress(
|
|
response.body().byteStream(),
|
|
outputStream,
|
|
file.getSize(),
|
|
progress -> {
|
|
builder.setProgress(
|
|
100, progress, false);
|
|
notificationManager.notify(
|
|
notificationId,
|
|
builder.build());
|
|
});
|
|
|
|
} catch (IOException ignored) {
|
|
|
|
builder.setOngoing(false)
|
|
.setContentTitle(
|
|
getString(
|
|
R.string
|
|
.fileViewerNotificationTitleFailed))
|
|
.setContentText(
|
|
getString(
|
|
R.string
|
|
.fileViewerNotificationDescriptionFailed,
|
|
file.getName()));
|
|
|
|
} finally {
|
|
|
|
builder.setProgress(0, 0, false)
|
|
.setOngoing(false);
|
|
|
|
notificationManager.notify(
|
|
notificationId, builder.build());
|
|
}
|
|
});
|
|
|
|
thread.start();
|
|
|
|
} catch (IOException ignored) {
|
|
}
|
|
}
|
|
});
|
|
private boolean renderMd = false;
|
|
private boolean processable = false;
|
|
public ActivityResultLauncher<Intent> editFileLauncher =
|
|
registerForActivityResult(
|
|
new ActivityResultContracts.StartActivityForResult(),
|
|
result -> {
|
|
if (result.getResultCode() == 200) {
|
|
assert result.getData() != null;
|
|
if (result.getData().getBooleanExtra("fileModified", false)) {
|
|
switch (result.getData()
|
|
.getIntExtra(
|
|
"fileAction",
|
|
CreateFileActivity.FILE_ACTION_EDIT)) {
|
|
case CreateFileActivity.FILE_ACTION_CREATE:
|
|
case CreateFileActivity.FILE_ACTION_EDIT:
|
|
getSingleFileContents(
|
|
repository.getOwner(),
|
|
repository.getName(),
|
|
file.getPath(),
|
|
repository.getBranchRef());
|
|
break;
|
|
default:
|
|
finish();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
@Override
|
|
public void onCreate(Bundle savedInstanceState) {
|
|
|
|
super.onCreate(savedInstanceState);
|
|
|
|
binding = ActivityFileViewBinding.inflate(getLayoutInflater());
|
|
repository = RepositoryContext.fromIntent(getIntent());
|
|
|
|
setContentView(binding.getRoot());
|
|
setSupportActionBar(binding.toolbar);
|
|
|
|
file = (ContentsResponse) getIntent().getSerializableExtra("file");
|
|
|
|
binding.close.setOnClickListener(view -> finish());
|
|
|
|
binding.toolbarTitle.setMovementMethod(new ScrollingMovementMethod());
|
|
binding.toolbarTitle.setText(file.getPath());
|
|
|
|
getSingleFileContents(
|
|
repository.getOwner(),
|
|
repository.getName(),
|
|
file.getPath(),
|
|
repository.getBranchRef());
|
|
}
|
|
|
|
private void getSingleFileContents(
|
|
final String owner, String repo, final String filename, String ref) {
|
|
|
|
Thread thread =
|
|
new Thread(
|
|
() -> {
|
|
Call<ResponseBody> call =
|
|
RetrofitClient.getWebInterface(ctx)
|
|
.getFileContents(owner, repo, ref, filename);
|
|
|
|
try {
|
|
|
|
Response<ResponseBody> response = call.execute();
|
|
|
|
if (response.code() == 200) {
|
|
|
|
ResponseBody responseBody = response.body();
|
|
|
|
if (responseBody != null) {
|
|
|
|
runOnUiThread(
|
|
() -> binding.progressBar.setVisibility(View.GONE));
|
|
String fileExtension = FilenameUtils.getExtension(filename);
|
|
|
|
switch (AppUtil.getFileType(fileExtension)) {
|
|
case IMAGE:
|
|
|
|
// See
|
|
// https://developer.android.com/guide/topics/media/media-formats#core
|
|
if (Arrays.asList(
|
|
"bmp", "gif", "jpg", "jpeg", "png",
|
|
"webp", "heic", "heif")
|
|
.contains(fileExtension.toLowerCase())) {
|
|
|
|
byte[] pictureBytes = responseBody.bytes();
|
|
|
|
Bitmap image =
|
|
Images.scaleImage(pictureBytes, 1920);
|
|
processable = image != null;
|
|
if (processable) {
|
|
runOnUiThread(
|
|
() -> {
|
|
binding.contents.setVisibility(
|
|
View.GONE);
|
|
binding.markdownFrame
|
|
.setVisibility(
|
|
View.GONE);
|
|
|
|
binding.photoView.setVisibility(
|
|
View.VISIBLE);
|
|
binding.photoView
|
|
.setImageBitmap(image);
|
|
});
|
|
}
|
|
}
|
|
break;
|
|
|
|
case UNKNOWN:
|
|
case TEXT:
|
|
if (file.getSize()
|
|
> Constants.maximumFileViewerSize) {
|
|
break;
|
|
}
|
|
|
|
processable = true;
|
|
String text = responseBody.string();
|
|
|
|
runOnUiThread(
|
|
() -> {
|
|
binding.photoView.setVisibility(
|
|
View.GONE);
|
|
|
|
binding.contents.setContent(
|
|
text, fileExtension);
|
|
|
|
if (renderMd) {
|
|
Markdown.render(
|
|
ctx,
|
|
EmojiParser.parseToUnicode(
|
|
text),
|
|
binding.markdown,
|
|
repository);
|
|
|
|
binding.contents.setVisibility(
|
|
View.GONE);
|
|
binding.markdownFrame.setVisibility(
|
|
View.VISIBLE);
|
|
} else {
|
|
binding.markdownFrame.setVisibility(
|
|
View.GONE);
|
|
binding.contents.setVisibility(
|
|
View.VISIBLE);
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
|
|
if (!processable) { // While the file could still be
|
|
// non-binary,
|
|
// it's better we don't show it (to prevent any crashes
|
|
// and/or unwanted behavior) and let the user download
|
|
// it instead.
|
|
responseBody.close();
|
|
|
|
runOnUiThread(
|
|
() -> {
|
|
binding.photoView.setVisibility(View.GONE);
|
|
binding.contents.setVisibility(View.GONE);
|
|
|
|
binding.markdownFrame.setVisibility(
|
|
View.VISIBLE);
|
|
binding.markdown.setVisibility(View.GONE);
|
|
binding.markdownTv.setVisibility(
|
|
View.VISIBLE);
|
|
binding.markdownTv.setText(
|
|
getString(
|
|
R.string
|
|
.excludeFilesInFileViewer));
|
|
binding.markdownTv.setGravity(
|
|
Gravity.CENTER);
|
|
binding.markdownTv.setTypeface(
|
|
null, Typeface.BOLD);
|
|
});
|
|
}
|
|
} else {
|
|
|
|
runOnUiThread(
|
|
() -> {
|
|
binding.markdownTv.setText("");
|
|
binding.progressBar.setVisibility(View.GONE);
|
|
});
|
|
}
|
|
} else {
|
|
|
|
switch (response.code()) {
|
|
case 401:
|
|
runOnUiThread(
|
|
() ->
|
|
AlertDialogs
|
|
.authorizationTokenRevokedDialog(
|
|
ctx));
|
|
break;
|
|
|
|
case 403:
|
|
runOnUiThread(
|
|
() ->
|
|
Toasty.error(
|
|
ctx,
|
|
ctx.getString(
|
|
R.string
|
|
.authorizeError)));
|
|
break;
|
|
|
|
case 404:
|
|
runOnUiThread(
|
|
() ->
|
|
Toasty.warning(
|
|
ctx,
|
|
ctx.getString(
|
|
R.string.apiNotFound)));
|
|
break;
|
|
|
|
default:
|
|
runOnUiThread(
|
|
() ->
|
|
Toasty.error(
|
|
ctx,
|
|
getString(
|
|
R.string
|
|
.genericError)));
|
|
}
|
|
}
|
|
} catch (IOException ignored) {
|
|
}
|
|
});
|
|
|
|
thread.start();
|
|
}
|
|
|
|
@Override
|
|
public boolean onCreateOptionsMenu(@NonNull Menu menu) {
|
|
|
|
MenuInflater inflater = getMenuInflater();
|
|
inflater.inflate(R.menu.generic_nav_dotted_menu, menu);
|
|
inflater.inflate(R.menu.markdown_switcher, menu);
|
|
|
|
if (!FilenameUtils.getExtension(file.getName()).equalsIgnoreCase("md")) {
|
|
|
|
menu.getItem(0).setVisible(false);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean onOptionsItemSelected(MenuItem item) {
|
|
|
|
int id = item.getItemId();
|
|
|
|
if (id == android.R.id.home) {
|
|
|
|
finish();
|
|
return true;
|
|
|
|
} else if (id == R.id.genericMenu) {
|
|
|
|
BottomSheetFileViewerFragment bottomSheet = new BottomSheetFileViewerFragment();
|
|
Bundle opts = repository.getBundle();
|
|
opts.putBoolean("editable", processable);
|
|
bottomSheet.setArguments(opts);
|
|
bottomSheet.show(getSupportFragmentManager(), "fileViewerBottomSheet");
|
|
return true;
|
|
|
|
} else if (id == R.id.markdown) {
|
|
|
|
if (!renderMd) {
|
|
if (binding.markdown.getAdapter() == null) {
|
|
Markdown.render(
|
|
ctx,
|
|
EmojiParser.parseToUnicode(binding.contents.getContent()),
|
|
binding.markdown,
|
|
repository);
|
|
}
|
|
|
|
binding.contents.setVisibility(View.GONE);
|
|
binding.markdownFrame.setVisibility(View.VISIBLE);
|
|
|
|
renderMd = true;
|
|
} else {
|
|
binding.markdownFrame.setVisibility(View.GONE);
|
|
binding.contents.setVisibility(View.VISIBLE);
|
|
|
|
renderMd = false;
|
|
}
|
|
|
|
return true;
|
|
|
|
} else {
|
|
return super.onOptionsItemSelected(item);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onButtonClicked(String text) {
|
|
|
|
if ("downloadFile".equals(text)) {
|
|
requestFileDownload();
|
|
}
|
|
|
|
if ("deleteFile".equals(text)) {
|
|
Intent intent = repository.getIntent(ctx, CreateFileActivity.class);
|
|
intent.putExtra("fileAction", CreateFileActivity.FILE_ACTION_DELETE);
|
|
intent.putExtra("filePath", file.getPath());
|
|
intent.putExtra("fileSha", file.getSha());
|
|
|
|
editFileLauncher.launch(intent);
|
|
}
|
|
|
|
if ("editFile".equals(text)) {
|
|
|
|
if (binding.contents.getContent() != null && !binding.contents.getContent().isEmpty()) {
|
|
|
|
Intent intent = repository.getIntent(ctx, CreateFileActivity.class);
|
|
|
|
intent.putExtra("fileAction", CreateFileActivity.FILE_ACTION_EDIT);
|
|
intent.putExtra("filePath", file.getPath());
|
|
intent.putExtra("fileSha", file.getSha());
|
|
intent.putExtra("fileContents", binding.contents.getContent());
|
|
|
|
editFileLauncher.launch(intent);
|
|
|
|
} else {
|
|
Toasty.error(ctx, getString(R.string.fileTypeCannotBeEdited));
|
|
}
|
|
}
|
|
|
|
if ("copyUrl".equals(text)) {
|
|
AppUtil.copyToClipboard(
|
|
this, file.getHtmlUrl(), ctx.getString(R.string.copyIssueUrlToastMsg));
|
|
}
|
|
|
|
if ("share".equals(text)) {
|
|
AppUtil.sharingIntent(this, file.getHtmlUrl());
|
|
}
|
|
|
|
if ("open".equals(text)) {
|
|
AppUtil.openUrlInBrowser(this, file.getHtmlUrl());
|
|
}
|
|
}
|
|
|
|
private void requestFileDownload() {
|
|
|
|
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
|
|
|
|
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
|
intent.putExtra(Intent.EXTRA_TITLE, file.getName());
|
|
intent.setType("*/*");
|
|
|
|
activityResultLauncher.launch(intent);
|
|
}
|
|
|
|
@Override
|
|
public void onResume() {
|
|
super.onResume();
|
|
repository.checkAccountSwitch(this);
|
|
}
|
|
}
|