GitNex-Android-App/app/src/main/java/org/mian/gitnex/activities/FileViewActivity.java

393 lines
12 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.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.core.app.NotificationCompat;
import com.vdurmont.emoji.EmojiParser;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.gitnex.tea4j.models.Files;
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 java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Response;
/**
* Author M M Arif
*/
public class FileViewActivity extends BaseActivity implements BottomSheetListener {
private ActivityFileViewBinding binding;
private Files file;
private RepositoryContext repository;
private boolean renderMd = false;
public ActivityResultLauncher<Intent> editFileLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),
result -> {
if(result.getResultCode() == 200) {
assert result.getData() != null;
if(result.getData().getBooleanExtra("fileModified", false)) {
getSingleFileContents(repository.getOwner(), repository.getName(), file.getPath(), repository.getBranchRef());
}
}
});
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityFileViewBinding.inflate(getLayoutInflater());
repository = RepositoryContext.fromIntent(getIntent());
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
file = (Files) 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(getAccount().getWebAuthorization(), 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);
boolean processable = false;
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())) {
processable = true;
byte[] pictureBytes = responseBody.bytes();
runOnUiThread(() -> {
binding.contents.setVisibility(View.GONE);
binding.markdownFrame.setVisibility(View.GONE);
binding.photoView.setVisibility(View.VISIBLE);
binding.photoView.setImageBitmap(Images.scaleImage(pictureBytes, 1920));
});
}
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,
getResources().getString(R.string.alertDialogTokenRevokedTitle),
getResources().getString(R.string.alertDialogTokenRevokedMessage),
getResources().getString(R.string.cancelButton),
getResources().getString(R.string.navLogout)));
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(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.generic_nav_dotted_menu, menu);
inflater.inflate(R.menu.files_view_menu, 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();
bottomSheet.setArguments(repository.getBundle());
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());
ctx.startActivity(intent);
} else {
Toasty.error(ctx, getString(R.string.fileTypeCannotBeEdited));
}
}
}
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);
}
ActivityResultLauncher<Intent> activityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
if (result.getResultCode() == Activity.RESULT_OK) {
assert result.getData() != null;
try {
OutputStream outputStream = getContentResolver().openOutputStream(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(getAccount().getWebAuthorization(), repository.getOwner(), repository.getName(), repository.getBranchRef(), file.getPath());
Response<ResponseBody> response = call.execute();
assert response.body() != null;
AppUtil.copyProgress(response.body().byteStream(), outputStream, file.getSize(), progress -> {
builder.setProgress(100, progress, false);
notificationManager.notify(notificationId, builder.build());
});
builder.setContentTitle(getString(R.string.fileViewerNotificationTitleFinished))
.setContentText(getString(R.string.fileViewerNotificationDescriptionFinished, file.getName()));
} catch(IOException ignored) {
builder.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) {}
}
});
@Override
public void onResume() {
super.onResume();
repository.checkAccountSwitch(this);
}
}