diff --git a/app/build.gradle b/app/build.gradle index c2b07d75..337120bc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -44,7 +44,7 @@ android { tasks.whenTaskAdded { task -> if (task.name.contains("assembleRelease")) { task.getDependsOn().add({ - def inputFile = new File("app/google-services.json") + def inputFile = new File("google-services.json") def json = new JsonSlurper().parseText(inputFile.text) if (json.project_info.project_id != "mthmmy-release-3aef0") throw new GradleException('Please supply the correct google-services.json for release (or manually change the id above)!') @@ -73,6 +73,7 @@ dependencies { implementation 'com.google.firebase:firebase-core:16.0.6' implementation 'com.google.firebase:firebase-messaging:17.3.4' implementation 'com.crashlytics.sdk.android:crashlytics:2.9.8' + implementation 'com.snatik:storage:2.1.0' implementation 'com.squareup.okhttp3:okhttp:3.12.0' implementation 'com.squareup.picasso:picasso:2.5.2' implementation 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7620bcf7..1e8bf257 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -160,6 +160,14 @@ + + + + + + { diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsActivity.java index addccf35..e4c3676c 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsActivity.java @@ -1,8 +1,12 @@ package gr.thmmy.mthmmy.activities.downloads; +import android.content.Intent; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; import android.widget.ProgressBar; import android.widget.Toast; @@ -16,7 +20,11 @@ import java.util.Objects; import androidx.recyclerview.widget.DividerItemDecoration; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.floatingactionbutton.FloatingActionButton; + import gr.thmmy.mthmmy.R; +import gr.thmmy.mthmmy.activities.upload.UploadActivity; import gr.thmmy.mthmmy.base.BaseActivity; import gr.thmmy.mthmmy.base.BaseApplication; import gr.thmmy.mthmmy.model.Download; @@ -29,6 +37,8 @@ import okhttp3.Request; import okhttp3.Response; import timber.log.Timber; +import static gr.thmmy.mthmmy.activities.upload.UploadActivity.BUNDLE_UPLOAD_CATEGORY; + public class DownloadsActivity extends BaseActivity implements DownloadsAdapter.OnLoadMoreListener { /** * The key to use when putting download's url String to {@link DownloadsActivity}'s Bundle. @@ -47,7 +57,7 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter. private MaterialProgressBar progressBar; private RecyclerView recyclerView; private DownloadsAdapter downloadsAdapter; - //private FloatingActionButton uploadFAB; + private FloatingActionButton uploadFAB; private ParseDownloadPageTask parseDownloadPageTask; private int numberOfPages = -1; @@ -113,37 +123,37 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter. } }); -// uploadFAB = findViewById(R.id.upload_fab); -// uploadFAB.setEnabled(false); -// uploadFAB.hide(); + uploadFAB = findViewById(R.id.upload_fab); + uploadFAB.setEnabled(false); + uploadFAB.hide(); parseDownloadPageTask = new ParseDownloadPageTask(); parseDownloadPageTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, downloadsUrl); } -// @Override -// public boolean onCreateOptionsMenu(Menu menu) { -// // Inflates the menu; this adds items to the action bar if it is present. -// getMenuInflater().inflate(R.menu.downloads_menu, menu); -// super.onCreateOptionsMenu(menu); -// return true; -// } -// -// @Override -// public boolean onOptionsItemSelected(MenuItem item) { -// // Handle presses on the action bar items -// switch (item.getItemId()) { -// case R.id.menu_upload: -// Intent intent = new Intent(DownloadsActivity.this, UploadActivity.class); -// Bundle extras = new Bundle(); -// extras.putString(BUNDLE_UPLOAD_CATEGORY, downloadsNav); -// intent.putExtras(extras); -// startActivity(intent); -// return true; -// default: -// return super.onOptionsItemSelected(item); -// } -// } + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflates the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.downloads_menu, menu); + super.onCreateOptionsMenu(menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle presses on the action bar items + switch (item.getItemId()) { + case R.id.menu_upload: + Intent intent = new Intent(DownloadsActivity.this, UploadActivity.class); + Bundle extras = new Bundle(); + extras.putString(BUNDLE_UPLOAD_CATEGORY, downloadsNav); + intent.putExtras(extras); + startActivity(intent); + return true; + default: + return super.onOptionsItemSelected(item); + } + } @Override public void onLoadMore() { @@ -198,7 +208,7 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter. @Override protected void onPreExecute() { if (!isLoadingMore) progressBar.setVisibility(ProgressBar.VISIBLE); - //if (uploadFAB.getVisibility() != View.GONE) uploadFAB.setEnabled(false); + if (uploadFAB.getVisibility() != View.GONE) uploadFAB.setEnabled(false); } @Override @@ -296,7 +306,7 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter. toolbar.setTitle(downloadsTitle); ++pagesLoaded; - //if (uploadFAB.getVisibility() != View.GONE) uploadFAB.setEnabled(true); + if (uploadFAB.getVisibility() != View.GONE) uploadFAB.setEnabled(true); progressBar.setVisibility(ProgressBar.INVISIBLE); downloadsAdapter.notifyDataSetChanged(); isLoadingMore = false; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java index dba15a45..5472a742 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java @@ -87,6 +87,7 @@ import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_URL; import static gr.thmmy.mthmmy.activities.topic.TopicParser.USER_COLOR_WHITE; import static gr.thmmy.mthmmy.activities.topic.TopicParser.USER_COLOR_YELLOW; import static gr.thmmy.mthmmy.base.BaseActivity.getSessionManager; +import static gr.thmmy.mthmmy.utils.FileUtils.faIconFromFilename; /** * Custom {@link RecyclerView.Adapter} used for topics. @@ -392,7 +393,7 @@ class TopicAdapter extends RecyclerView.Adapter { attached.setClickable(true); attached.setTypeface(Typeface.createFromAsset(context.getAssets() , "fonts/fontawesome-webfont.ttf")); - attached.setText(faIconFromFilename(attachedFile.getFilename()) + " " + attached.setText(faIconFromFilename(context, attachedFile.getFilename()) + " " + attachedFile.getFilename() + attachedFile.getFileInfo()); attached.setTextColor(filesTextColor); attached.setPadding(0, 3, 0, 3); @@ -1035,37 +1036,4 @@ class TopicAdapter extends RecyclerView.Adapter { public interface OnPostFocusChangeListener { void onPostFocusChange(int position); } - - /** - * Returns a String with a single FontAwesome typeface character corresponding to this file's - * extension. - * - * @param filename String with filename containing file's extension - * @return FontAwesome character according to file's type - * @see FontAwesome - */ - @NonNull - private String faIconFromFilename(String filename) { - filename = filename.toLowerCase(); - - if (filename.contains("jpg") || filename.contains("gif") || filename.contains("jpeg") - || filename.contains("png")) - return context.getResources().getString(R.string.fa_file_image_o); - else if (filename.contains("pdf")) - return context.getResources().getString(R.string.fa_file_pdf_o); - else if (filename.contains("zip") || filename.contains("rar") || filename.contains("tar.gz")) - return context.getResources().getString(R.string.fa_file_zip_o); - else if (filename.contains("txt")) - return context.getResources().getString(R.string.fa_file_text_o); - else if (filename.contains("doc") || filename.contains("docx")) - return context.getResources().getString(R.string.fa_file_word_o); - else if (filename.contains("xls") || filename.contains("xlsx")) - return context.getResources().getString(R.string.fa_file_excel_o); - else if (filename.contains("pps")) - return context.getResources().getString(R.string.fa_file_powerpoint_o); - else if (filename.contains("mpg")) - return context.getResources().getString(R.string.fa_file_video_o); - - return context.getResources().getString(R.string.fa_file); - } } \ No newline at end of file diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadActivity.java index 974b8b20..20fea00f 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadActivity.java @@ -1,54 +1,68 @@ package gr.thmmy.mthmmy.activities.upload; import android.app.Activity; +import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; -import android.content.pm.ResolveInfo; -import android.graphics.Bitmap; +import android.content.pm.PackageManager; +import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.AsyncTask; +import android.os.Build; import android.os.Bundle; -import android.provider.MediaStore; +import androidx.annotation.NonNull; +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import androidx.core.content.FileProvider; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.content.res.AppCompatResources; +import androidx.preference.PreferenceManager; +import androidx.appcompat.widget.AppCompatButton; +import androidx.appcompat.widget.AppCompatImageButton; +import android.text.Editable; +import android.text.Spannable; +import android.text.TextWatcher; +import android.text.method.LinkMovementMethod; +import android.text.style.ForegroundColorSpan; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.EditText; import android.widget.LinearLayout; +import android.widget.PopupWindow; import android.widget.ProgressBar; +import android.widget.TextView; import android.widget.Toast; -import com.google.android.material.floatingactionbutton.FloatingActionButton; - import net.gotev.uploadservice.MultipartUploadRequest; -import net.gotev.uploadservice.ServerResponse; -import net.gotev.uploadservice.UploadInfo; +import net.gotev.uploadservice.UploadNotificationAction; import net.gotev.uploadservice.UploadNotificationConfig; -import net.gotev.uploadservice.UploadStatusDelegate; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import java.io.File; -import java.io.FileOutputStream; +import java.lang.ref.WeakReference; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; -import java.util.List; import java.util.Locale; +import java.util.UUID; -import androidx.appcompat.content.res.AppCompatResources; -import androidx.appcompat.widget.AppCompatButton; -import androidx.appcompat.widget.AppCompatTextView; -import androidx.preference.PreferenceManager; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.base.BaseActivity; import gr.thmmy.mthmmy.base.BaseApplication; import gr.thmmy.mthmmy.model.UploadCategory; +import gr.thmmy.mthmmy.model.UploadFile; +import gr.thmmy.mthmmy.services.UploadsReceiver; import gr.thmmy.mthmmy.utils.AppCompatSpinnerWithoutDefault; +import gr.thmmy.mthmmy.utils.FileUtils; +import gr.thmmy.mthmmy.utils.TakePhoto; import gr.thmmy.mthmmy.utils.parsing.ParseException; import gr.thmmy.mthmmy.utils.parsing.ParseTask; import me.zhanghai.android.materialprogressbar.MaterialProgressBar; @@ -60,6 +74,7 @@ import static gr.thmmy.mthmmy.activities.upload.UploadFieldsBuilderActivity.BUND import static gr.thmmy.mthmmy.activities.upload.UploadFieldsBuilderActivity.RESULT_DESCRIPTION; import static gr.thmmy.mthmmy.activities.upload.UploadFieldsBuilderActivity.RESULT_FILENAME; import static gr.thmmy.mthmmy.activities.upload.UploadFieldsBuilderActivity.RESULT_TITLE; +import static gr.thmmy.mthmmy.utils.FileUtils.faIconFromFilename; public class UploadActivity extends BaseActivity { /** @@ -71,27 +86,40 @@ public class UploadActivity extends BaseActivity { /** * Request codes used in activities for result (AFR) calls */ - private static final int AFR_REQUEST_CODE_CHOOSE_FILE = 8; - private static final int AFR_REQUEST_CODE_CAMERA = 4; - private static final int AFR_REQUEST_CODE_FIELDS_BUILDER = 74; + private static final int AFR_REQUEST_CODE_CHOOSE_FILE = 8; //Arbitrary, application specific + private static final int AFR_REQUEST_CODE_CAMERA = 4; //Arbitrary, application specific + private static final int AFR_REQUEST_CODE_FIELDS_BUILDER = 74; //Arbitrary, application specific + + /** + * Request code to gain read/write permission + */ + private static final int UPLOAD_REQUEST_CODE = 42; //Arbitrary, application specific + + private static final int MAX_FILE_SIZE_SUPPORTED = 45000000; + //private UploadsReceiver uploadsReceiver = new UploadsReceiver(); private ArrayList uploadRootCategories = new ArrayList<>(); private ParseUploadPageTask parseUploadPageTask; private ArrayList bundleCategory; private String categorySelected = "-1"; private String uploaderProfileIndex = "1"; - private String uploadFilename; - private Uri fileUri; + + private ArrayList filesList = new ArrayList<>(); + private File photoFileCreated = null; private String fileIcon; + private AppCompatImageButton uploadFilenameInfo; + private CustomTextWatcher textWatcher; + private boolean hasModifiedFilename = false; //UI elements private MaterialProgressBar progressBar; private LinearLayout categoriesSpinners; private AppCompatSpinnerWithoutDefault rootCategorySpinner; private EditText uploadTitle; + private EditText uploadFilename; private EditText uploadDescription; private AppCompatButton titleDescriptionBuilderButton; - private AppCompatTextView filenameHolder; + private LinearLayout filesListView; @Override protected void onCreate(Bundle savedInstanceState) { @@ -197,9 +225,36 @@ public class UploadActivity extends BaseActivity { uploadTitle = findViewById(R.id.upload_title); uploadDescription = findViewById(R.id.upload_description); - filenameHolder = findViewById(R.id.upload_filename); - Drawable filenameDrawable = AppCompatResources.getDrawable(this, R.drawable.ic_attach_file_white_24dp); - filenameHolder.setCompoundDrawablesRelativeWithIntrinsicBounds(filenameDrawable, null, null, null); + uploadFilenameInfo = findViewById(R.id.upload_filename_info); + uploadFilenameInfo.setOnClickListener(view -> { + //Inflates the popup menu content + LayoutInflater layoutInflater = (LayoutInflater) view.getContext(). + getSystemService(Context.LAYOUT_INFLATER_SERVICE); + if (layoutInflater == null) { + return; + } + + Context wrapper = new ContextThemeWrapper(this, R.style.PopupWindow); + View popUpContent = layoutInflater.inflate(R.layout.activity_upload_filename_info_popup, null); + + //Creates the PopupWindow + PopupWindow popUp = new PopupWindow(wrapper); + popUp.setContentView(popUpContent); + popUp.setWidth(LinearLayout.LayoutParams.WRAP_CONTENT); + popUp.setHeight(LinearLayout.LayoutParams.WRAP_CONTENT); + popUp.setFocusable(true); + + ((TextView) popUpContent.findViewById(R.id.upload_filename_info_text)). + setMovementMethod(LinkMovementMethod.getInstance()); + //Displays the popup + popUp.showAsDropDown(view); + }); + + uploadFilename = findViewById(R.id.upload_filename); + textWatcher = new CustomTextWatcher(); + uploadFilename.addTextChangedListener(textWatcher); + + filesListView = findViewById(R.id.upload_files_list); AppCompatButton selectFileButton = findViewById(R.id.upload_select_file_button); Drawable selectStartDrawable = AppCompatResources.getDrawable(this, R.drawable.ic_insert_drive_file_white_24dp); @@ -212,7 +267,8 @@ public class UploadActivity extends BaseActivity { Intent intent = new Intent(Intent.ACTION_GET_CONTENT) //.setType("*/*") .setType("image/jpeg") - .putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes); + .putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes) + .putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); startActivityForResult(intent, AFR_REQUEST_CODE_CHOOSE_FILE); }); @@ -221,118 +277,153 @@ public class UploadActivity extends BaseActivity { Drawable takePhotoDrawable = AppCompatResources.getDrawable(this, R.drawable.ic_photo_camera_white_24dp); takePhotoButton.setCompoundDrawablesRelativeWithIntrinsicBounds(takePhotoDrawable, null, null, null); takePhotoButton.setOnClickListener(v -> { - Intent takePhotoIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); - takePhotoIntent.putExtra("return-data", true); - takePhotoIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(UploadsHelper.getCacheFile(this))); - - Intent targetedIntent = new Intent(takePhotoIntent); - List resInfo = this.getPackageManager().queryIntentActivities(takePhotoIntent, 0); - for (ResolveInfo resolveInfo : resInfo) { - String packageName = resolveInfo.activityInfo.packageName; - targetedIntent.setPackage(packageName); - } - startActivityForResult(takePhotoIntent, AFR_REQUEST_CODE_CAMERA); + if (checkPerms()) + takePhoto(); + else + requestPerms(UPLOAD_REQUEST_CODE); }); FloatingActionButton uploadFAB = findViewById(R.id.upload_fab); + uploadFAB.setTag(true); uploadFAB.setOnClickListener(view -> { + //Attempts upload progressBar.setVisibility(View.VISIBLE); String uploadTitleText = uploadTitle.getText().toString(); - String uploadDescriptionText = uploadDescription.getText().toString(); - - if (uploadTitleText.equals("")) { - uploadTitle.setError("Required"); - } - if (fileUri == null) { - Toast.makeText(view.getContext(), "Please choose a file to upload or take a photo", Toast.LENGTH_LONG).show(); - } - if (categorySelected.equals("-1")) { - Toast.makeText(view.getContext(), "Please choose category first", Toast.LENGTH_SHORT).show(); - } - - if (categorySelected.equals("-1") || uploadTitleText.equals("") || fileUri == null) { - progressBar.setVisibility(View.GONE); - return; - } - - SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(view.getContext()); - if (sharedPrefs.getBoolean(UPLOADING_APP_SIGNATURE_ENABLE_KEY, true)) { - uploadDescriptionText += uploadedFromThmmyPromptHtml; - } + String editTextFilename = uploadFilename.getText().toString(); + final String[] uploadDescriptionText = {uploadDescription.getText().toString()}; + + //Checks if all required fields are filled + { + boolean shouldReturn = false; + if (uploadTitleText.equals("")) { + uploadTitle.setError("Required"); + shouldReturn = true; + } + if (filesList.isEmpty()) { + Toast.makeText(view.getContext(), "Please choose a file to upload or take a photo", Toast.LENGTH_LONG).show(); + shouldReturn = true; + } + if (categorySelected.equals("-1")) { + Toast.makeText(view.getContext(), "Please choose category first", Toast.LENGTH_SHORT).show(); + shouldReturn = true; + } + if (!filesList.isEmpty()) { + long totalFilesSize = 0; + for (UploadFile file : filesList) { + totalFilesSize += FileUtils.sizeFromUri(this, file.getFileUri()); + } - String tempFilePath = null; - if (uploadFilename != null) { - //File should be uploaded with a certain name. Temporarily copies the file and renames it - tempFilePath = UploadsHelper.createTempFile(this, fileUri, uploadFilename); - if (tempFilePath == null) { - //Something went wrong, abort - Toast.makeText(this, "Could not create temporary file for renaming", Toast.LENGTH_SHORT).show(); + if (totalFilesSize > MAX_FILE_SIZE_SUPPORTED) { + Toast.makeText(view.getContext(), "Your files are too powerful for thmmy. Reduce size or split!", Toast.LENGTH_LONG).show(); + shouldReturn = true; + } + } + if (!editTextFilename.matches("(.+\\.)+.+") || + !FileUtils.getFilenameWithoutExtension(editTextFilename). + matches("[0-9a-zA-Zα-ωΑ-Ω~!@#$%^&()_+=\\-`\\[\\]{};',.]+")) { + uploadFilename.setError("Invalid filename"); + shouldReturn = true; + } + if (shouldReturn) { progressBar.setVisibility(View.GONE); return; } } - try { - new MultipartUploadRequest(view.getContext(), uploadIndexUrl) - .setUtf8Charset() - .addParameter("tp-dluploadtitle", uploadTitleText) - .addParameter("tp-dluploadcat", categorySelected) - .addParameter("tp-dluploadtext", uploadDescriptionText) - .addFileToUpload(tempFilePath == null - ? fileUri.toString() - : tempFilePath - , "tp-dluploadfile") - .addParameter("tp_dluploadicon", fileIcon) - .addParameter("tp-uploaduser", uploaderProfileIndex) - .setNotificationConfig(new UploadNotificationConfig()) - .setMaxRetries(2) - .setDelegate(new UploadStatusDelegate() { - @Override - public void onProgress(Context context, UploadInfo uploadInfo) { - } + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("Upload to thmmy"); + builder.setMessage("Are you sure?"); + builder.setPositiveButton("YES, FIRE AWAY", (dialog, which) -> { + //Checks settings and possibly adds "Uploaded from mTHMMY" string to description + SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(view.getContext()); + if (sharedPrefs.getBoolean(UPLOADING_APP_SIGNATURE_ENABLE_KEY, true)) { + uploadDescriptionText[0] += uploadedFromThmmyPromptHtml; + } - @Override - public void onError(Context context, UploadInfo uploadInfo, ServerResponse serverResponse, - Exception exception) { - Toast.makeText(context, "Upload failed", Toast.LENGTH_SHORT).show(); - UploadsHelper.deleteTempFiles(); - progressBar.setVisibility(View.GONE); - } + for (UploadFile file : filesList) { + if (file.isCameraPhoto()) { + TakePhoto.galleryAddPic(this, file.getPhotoFile()); + } + } - @Override - public void onCompleted(Context context, UploadInfo uploadInfo, ServerResponse serverResponse) { - Toast.makeText(context, "Upload completed successfully", Toast.LENGTH_SHORT).show(); - UploadsHelper.deleteTempFiles(); - BaseApplication.getInstance().logFirebaseAnalyticsEvent("file_upload", null); - - uploadTitle.setText(null); - uploadDescription.setText(null); - fileUri = null; - filenameHolder.setText(null); - filenameHolder.setVisibility(View.GONE); + Uri tempFileUri = null; + if (filesList.size() == 1) { + //Checks if the file needs renaming + UploadFile uploadFile = filesList.get(0); + String selectedFileFilename = FileUtils.filenameFromUri(this, uploadFile.getFileUri()); + + if (!editTextFilename.equals(selectedFileFilename)) { + //File should be uploaded with a different name + if (!uploadFile.isCameraPhoto()) { + //Temporarily copies the file to a another location and renames it + tempFileUri = UploadsHelper.createTempFile(this, storage, + uploadFile.getFileUri(), + FileUtils.getFilenameWithoutExtension(editTextFilename)); + } else { + //Renames the photo taken + String photoPath = uploadFile.getPhotoFile().getPath(); + photoPath = photoPath.substring(0, photoPath.lastIndexOf(File.separator)); + String destinationFilename = photoPath + File.separator + + FileUtils.getFilenameWithoutExtension(editTextFilename) + ".jpg"; + + if (!storage.rename(uploadFile.getPhotoFile().getAbsolutePath(), destinationFilename)) { + //Something went wrong, abort + Toast.makeText(this, "Could not create temporary file for renaming", Toast.LENGTH_SHORT).show(); progressBar.setVisibility(View.GONE); + return; } - @Override - public void onCancelled(Context context, UploadInfo uploadInfo) { - Toast.makeText(context, "Upload canceled", Toast.LENGTH_SHORT).show(); + //Points photoFile and fileUri to the new copied and renamed file + uploadFile.setPhotoFile(storage.getFile(destinationFilename)); + uploadFile.setFileUri(FileProvider.getUriForFile(this, getPackageName() + + ".provider", uploadFile.getPhotoFile())); + } + } + } else { + Uri[] filesListArray = new Uri[filesList.size()]; + for (int i = 0; i < filesList.size(); ++i) { + filesListArray[i] = filesList.get(i).getFileUri(); + } + + new ZipTask(this, editTextFilename, categorySelected, + uploadTitleText, uploadDescriptionText[0], fileIcon, + uploaderProfileIndex).execute(filesListArray); + finish(); + return; + } - UploadsHelper.deleteTempFiles(); - progressBar.setVisibility(View.GONE); - } - }) - .startUpload(); - } catch (Exception exception) { - Timber.e(exception, "AndroidUploadService: %s", exception.getMessage()); + String uploadID = UUID.randomUUID().toString(); + if (uploadFile(this, uploadID, getConfigForUpload(this, uploadID, + editTextFilename), + categorySelected, uploadTitleText, + uploadDescriptionText[0], fileIcon, uploaderProfileIndex, + tempFileUri == null + ? filesList.get(0).getFileUri() + : tempFileUri)) { + finish(); + } else { + Toast.makeText(this, "Couldn't initiate upload.", Toast.LENGTH_SHORT).show(); + } + }); + + builder.setNegativeButton("NOPE", (dialog, which) -> { progressBar.setVisibility(View.GONE); - } + dialog.dismiss(); + }); + + AlertDialog alert = builder.create(); + alert.setOnCancelListener(dialog -> { + progressBar.setVisibility(View.GONE); + dialog.dismiss(); + }); + alert.show(); }); if (uploadRootCategories.isEmpty()) { //Parses the uploads page parseUploadPageTask = new ParseUploadPageTask(); - parseUploadPageTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, uploadIndexUrl); + parseUploadPageTask.execute(uploadIndexUrl); } else { //Renders the already parsed data updateUIElements(); @@ -355,11 +446,17 @@ public class UploadActivity extends BaseActivity { super.onResume(); } + @Override + protected void onPause() { + super.onPause(); + } + @Override protected void onDestroy() { super.onDestroy(); - if (parseUploadPageTask != null && parseUploadPageTask.getStatus() != AsyncTask.Status.RUNNING) + if (parseUploadPageTask != null && parseUploadPageTask.getStatus() != AsyncTask.Status.RUNNING) { parseUploadPageTask.cancel(true); + } } @Override @@ -369,71 +466,119 @@ public class UploadActivity extends BaseActivity { return; } - fileUri = data.getData(); - if (fileUri != null) { - String filename = UploadsHelper.filenameFromUri(this, fileUri); - filenameHolder.setText(filename); - filenameHolder.setVisibility(View.VISIBLE); - - filename = filename.toLowerCase(); - if (filename.endsWith(".jpg")) { - fileIcon = "jpg_image.gif"; - } else if (filename.endsWith(".gif")) { - fileIcon = "gif_image.gif"; - } else if (filename.endsWith(".png")) { - fileIcon = "png_image.gif"; - } else if (filename.endsWith(".html") || filename.endsWith(".htm")) { - fileIcon = "html_file.gif"; - } else if (filename.endsWith(".pdf") || filename.endsWith(".doc") || - filename.endsWith("djvu")) { - fileIcon = "text_file.gif"; - } else if (filename.endsWith(".zip") || filename.endsWith(".rar") || - filename.endsWith(".tar") || filename.endsWith(".tar.gz") || - filename.endsWith(".gz")) { - fileIcon = "archive.gif"; - } else { - fileIcon = "blank.gif"; + if (data.getClipData() != null) { + fileIcon = "archive.gif"; + textWatcher.setFileExtension(".zip"); + + if (!hasModifiedFilename) { + String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.FRANCE).format(new Date()); + String zipFilename = "mThmmy_" + timeStamp + ".zip"; + uploadFilename.setText(zipFilename); + hasModifiedFilename = false; + } + + for (int fileIndex = 0; fileIndex < data.getClipData().getItemCount(); ++fileIndex) { + Uri newFileUri = data.getClipData().getItemAt(fileIndex).getUri(); + String filename = FileUtils.filenameFromUri(this, newFileUri); + addFileViewToList(filename); + filesList.add(new UploadFile(false, newFileUri, null)); + } + } else { + Uri newFileUri = data.getData(); + if (newFileUri != null) { + String filename = FileUtils.filenameFromUri(this, newFileUri); + + if (filesList.isEmpty()) { + textWatcher.setFileExtension(FileUtils.getFileExtension(filename)); + + if (!hasModifiedFilename) { + uploadFilename.setText(filename); + hasModifiedFilename = false; + } + + filename = filename.toLowerCase(); + if (filename.endsWith(".jpg")) { + fileIcon = "jpg_image.gif"; + } else if (filename.endsWith(".gif")) { + fileIcon = "gif_image.gif"; + } else if (filename.endsWith(".png")) { + fileIcon = "png_image.gif"; + } else if (filename.endsWith(".html") || filename.endsWith(".htm")) { + fileIcon = "html_file.gif"; + } else if (filename.endsWith(".pdf") || filename.endsWith(".doc") || + filename.endsWith("djvu")) { + fileIcon = "text_file.gif"; + } else if (filename.endsWith(".zip") || filename.endsWith(".rar") || + filename.endsWith(".tar") || filename.endsWith(".tar.gz") || + filename.endsWith(".gz")) { + fileIcon = "archive.gif"; + } else { + fileIcon = "blank.gif"; + } + } else { + fileIcon = "archive.gif"; + textWatcher.setFileExtension(".zip"); + + if (!hasModifiedFilename) { + String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.FRANCE).format(new Date()); + String zipFilename = "mThmmy_" + timeStamp + ".zip"; + uploadFilename.setText(zipFilename); + hasModifiedFilename = false; + } + } + + addFileViewToList(filename); + filesList.add(new UploadFile(false, newFileUri, null)); } } } else if (requestCode == AFR_REQUEST_CODE_CAMERA) { if (resultCode == Activity.RESULT_CANCELED) { + //Deletes image file + storage.deleteFile(photoFileCreated.getAbsolutePath()); return; } - Bitmap bitmap; - File cacheImageFile = UploadsHelper.getCacheFile(this); + if (filesList.isEmpty()) { + textWatcher.setFileExtension(FileUtils.getFileExtension(photoFileCreated.getName())); - Uri cacheFileUri = Uri.fromFile(cacheImageFile); - fileIcon = "jpg_image.gif"; - - bitmap = UploadsHelper.getImageResized(this, cacheFileUri); - int rotation = UploadsHelper.getRotation(this, cacheFileUri); - bitmap = UploadsHelper.rotate(bitmap, rotation); + if (!hasModifiedFilename) { + uploadFilename.setText(photoFileCreated.getName()); + hasModifiedFilename = false; + } - try { - FileOutputStream out = new FileOutputStream(cacheImageFile); - bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out); - out.flush(); - out.close(); - } catch (Exception e) { - e.printStackTrace(); + fileIcon = "jpg_image.gif"; + } else { + fileIcon = "archive.gif"; + textWatcher.setFileExtension(".zip"); + + if (!hasModifiedFilename) { + String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.FRANCE).format(new Date()); + String zipFilename = "mThmmy_" + timeStamp + ".zip"; + uploadFilename.setText(zipFilename); + hasModifiedFilename = false; + } } - String newFilename = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.FRANCE). - format(new Date()); - fileUri = Uri.parse(UploadsHelper.createTempFile(this, cacheFileUri, newFilename)); - - newFilename += ".jpg"; - filenameHolder.setText(newFilename); - filenameHolder.setVisibility(View.VISIBLE); - - UploadsHelper.deleteCacheFiles(this); + UploadFile newFile = new UploadFile(true, TakePhoto.processResult(this, + photoFileCreated), photoFileCreated); + addFileViewToList(FileUtils.getFilenameWithoutExtension(FileUtils. + filenameFromUri(this, newFile.getFileUri()))); + filesList.add(newFile); } else if (requestCode == AFR_REQUEST_CODE_FIELDS_BUILDER) { if (resultCode == Activity.RESULT_CANCELED) { return; } - uploadFilename = data.getStringExtra(RESULT_FILENAME); + String previousName = uploadFilename.getText().toString(); + if (previousName.isEmpty()) { + uploadFilename.setText(data.getStringExtra(RESULT_FILENAME)); + } else { + String filenameWithExtension = data.getStringExtra(RESULT_FILENAME) + + FileUtils.getFileExtension(previousName); + uploadFilename.setText(filenameWithExtension); + } + hasModifiedFilename = true; + uploadTitle.setText(data.getStringExtra(RESULT_TITLE)); uploadDescription.setText(data.getStringExtra(RESULT_DESCRIPTION)); } else { @@ -441,6 +586,243 @@ public class UploadActivity extends BaseActivity { } } + @Override + public void onRequestPermissionsResult(int permsRequestCode, @NonNull String[] permissions + , @NonNull int[] grantResults) { + switch (permsRequestCode) { + case UPLOAD_REQUEST_CODE: + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) + takePhoto(); + break; + } + } + + // Should only be called after making sure permissions are granted + private void takePhoto() { + // Create the File where the photo should go + photoFileCreated = TakePhoto.createImageFile(this); + + // Continue only if the File was successfully created + if (photoFileCreated != null) { + startActivityForResult(TakePhoto.getIntent(this, photoFileCreated), + AFR_REQUEST_CODE_CAMERA); + } + } + + private void updateUIElements() { + String[] tmpSpinnerArray = new String[uploadRootCategories.size()]; + for (int i = 0; i < uploadRootCategories.size(); ++i) { + tmpSpinnerArray[i] = uploadRootCategories.get(i).getCategoryTitle(); + } + + ArrayAdapter spinnerArrayAdapter = new ArrayAdapter<>(BaseApplication.getInstance().getApplicationContext(), + R.layout.spinner_item, tmpSpinnerArray); + spinnerArrayAdapter.setDropDownViewResource(R.layout.spinner_dropdown_item); + rootCategorySpinner.setAdapter(spinnerArrayAdapter); + + //Sets bundle selection + if (bundleCategory != null) { + int bundleSelectionIndex = -1, currentIndex = 0; + + for (UploadCategory category : uploadRootCategories) { + if (bundleCategory.get(0).contains(category.getCategoryTitle())) { + bundleSelectionIndex = currentIndex; + break; + } + ++currentIndex; + } + + if (bundleSelectionIndex != -1) { + rootCategorySpinner.setSelection(bundleSelectionIndex, true); + bundleCategory.remove(0); + } + } + } + + private void addFileViewToList(String filename) { + LayoutInflater layoutInflater = getLayoutInflater(); + LinearLayout newFileRow = (LinearLayout) layoutInflater. + inflate(R.layout.activity_upload_file_list_row, null); + + TextView itemText = newFileRow.findViewById(R.id.upload_file_item_text); + itemText.setTypeface(Typeface.createFromAsset(this.getAssets() + , "fonts/fontawesome-webfont.ttf")); + String filenameWithIcon = faIconFromFilename(this, filename) + " " + filename; + itemText.setText(filenameWithIcon); + + newFileRow.findViewById(R.id.upload_file_item_remove).setOnClickListener(view -> { + int fileIndex = filesListView.indexOfChild(newFileRow); + filesListView.removeViewAt(fileIndex); + + if (filesList.get(fileIndex).isCameraPhoto()) { + storage.deleteFile(filesList.get(fileIndex).getPhotoFile().getAbsolutePath()); + } + filesList.remove(fileIndex); + if (filesList.isEmpty()) { + filesListView.setVisibility(View.GONE); + } else if (filesList.size() == 1) { + textWatcher.setFileExtension(FileUtils.getFileExtension(FileUtils. + filenameFromUri(this, filesList.get(0).getFileUri()))); + } + }); + + filesListView.addView(newFileRow); + filesListView.setVisibility(View.VISIBLE); + } + + public static UploadNotificationConfig getConfigForUpload(Context context, String uploadID, + String filename) { + UploadNotificationConfig uploadNotificationConfig = new UploadNotificationConfig(); + uploadNotificationConfig.setIconForAllStatuses(android.R.drawable.stat_sys_upload); + uploadNotificationConfig.setTitleForAllStatuses("Uploading " + filename); + + uploadNotificationConfig.getProgress().iconResourceID = android.R.drawable.stat_sys_upload; + uploadNotificationConfig.getCompleted().iconResourceID = android.R.drawable.stat_sys_upload_done; + uploadNotificationConfig.getError().iconResourceID = android.R.drawable.stat_sys_upload_done; + uploadNotificationConfig.getError().iconColorResourceID = R.color.error_red; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + uploadNotificationConfig.getError().message = "Error during upload. Click for options"; + } + uploadNotificationConfig.getCancelled().iconColorResourceID = android.R.drawable.stat_sys_upload_done; + uploadNotificationConfig.getCancelled().autoClear = true; + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + Intent combinedActionsIntent = new Intent(UploadsReceiver.ACTION_COMBINED_UPLOAD); + combinedActionsIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + combinedActionsIntent.putExtra(UploadsReceiver.UPLOAD_ID_KEY, uploadID); + + /*combinedActionsIntent.putExtra(UploadsReceiver.UPLOAD_RETRY_FILENAME, filename); + combinedActionsIntent.putExtra(UploadsReceiver.UPLOAD_RETRY_CATEGORY, retryCategory); + combinedActionsIntent.putExtra(UploadsReceiver.UPLOAD_RETRY_TITLE, retryTitleText); + combinedActionsIntent.putExtra(UploadsReceiver.UPLOAD_RETRY_DESCRIPTION, retryDescription); + combinedActionsIntent.putExtra(UploadsReceiver.UPLOAD_RETRY_ICON, retryIcon); + combinedActionsIntent.putExtra(UploadsReceiver.UPLOAD_RETRY_UPLOADER, retryUploaderProfile); + combinedActionsIntent.putExtra(UploadsReceiver.UPLOAD_RETRY_FILE_URI, retryFileUri);*/ + + uploadNotificationConfig.setClickIntentForAllStatuses(PendingIntent.getBroadcast(context, + 1, combinedActionsIntent, PendingIntent.FLAG_UPDATE_CURRENT)); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + Intent retryIntent = new Intent(context, UploadsReceiver.class); + retryIntent.setAction(UploadsReceiver.ACTION_RETRY_UPLOAD); + retryIntent.putExtra(UploadsReceiver.UPLOAD_ID_KEY, uploadID); + + Intent cancelIntent = new Intent(context, UploadsReceiver.class); + cancelIntent.setAction(UploadsReceiver.ACTION_CANCEL_UPLOAD); + cancelIntent.putExtra(UploadsReceiver.UPLOAD_ID_KEY, uploadID); + + uploadNotificationConfig.getProgress().actions.add(new UploadNotificationAction( + R.drawable.ic_cancel_accent_24dp, + context.getString(R.string.upload_notification_cancel), + PendingIntent.getBroadcast(context, 0, cancelIntent, + PendingIntent.FLAG_UPDATE_CURRENT) + )); + uploadNotificationConfig.getError().actions.add(new UploadNotificationAction( + R.drawable.ic_notification, + context.getString(R.string.upload_notification_retry), + PendingIntent.getBroadcast(context, 0, retryIntent, + PendingIntent.FLAG_UPDATE_CURRENT) + )); + } + + return uploadNotificationConfig; + } + + public static boolean uploadFile(Context context, String uploadID, + UploadNotificationConfig uploadNotificationConfig, + String categorySelected, String uploadTitleText, + String uploadDescriptionText, String fileIcon, + String uploaderProfileIndex, Uri fileUri) { + try { + new MultipartUploadRequest(context, uploadID, uploadIndexUrl) + .setUtf8Charset() + .setNotificationConfig(uploadNotificationConfig) + .addParameter("tp-dluploadtitle", uploadTitleText) + .addParameter("tp-dluploadcat", categorySelected) + .addParameter("tp-dluploadtext", uploadDescriptionText) + .addFileToUpload(fileUri.toString() + , "tp-dluploadfile") + .addParameter("tp_dluploadicon", fileIcon) + .addParameter("tp-uploaduser", uploaderProfileIndex) + .setNotificationConfig(uploadNotificationConfig) + .setMaxRetries(2) + .startUpload(); + + Toast.makeText(context, "Uploading files in the background.", Toast.LENGTH_SHORT).show(); + return true; + } catch (Exception exception) { + Timber.e(exception, "AndroidUploadService: %s", exception.getMessage()); + return false; + } + } + + private class CustomTextWatcher implements TextWatcher { + String oldFilename, fileExtension; + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + //Saves an instance of the filename before changing + oldFilename = s.toString(); + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + @Override + public void afterTextChanged(Editable s) { + //Warns user for bad filenames + String filenameWithoutExtension = FileUtils.getFilenameWithoutExtension(s.toString()); + if (filenameWithoutExtension != null && !filenameWithoutExtension.isEmpty() && + !filenameWithoutExtension.matches("[0-9a-zA-Z~!@#$%^&()_+=\\-`\\[\\]{};',.]+")) { + uploadFilenameInfo.setImageResource(R.drawable.ic_info_outline_warning_24dp); + } else { + uploadFilenameInfo.setImageResource(R.drawable.ic_info_outline_white_24dp); + } + + if (fileExtension == null) { + hasModifiedFilename = !s.toString().isEmpty(); + return; + } + + if (!s.toString().endsWith(fileExtension)) { + //User tried to alter the extension + //Prevents the change + uploadFilename.setText(oldFilename); + return; + } + + //User has modified the filename + hasModifiedFilename = true; + if (s.toString().isEmpty() || (filesList.size() == 1 && s.toString().equals(FileUtils. + filenameFromUri(getApplicationContext(), filesList.get(0).getFileUri())))) { + //After modification the filename falls back to the original + hasModifiedFilename = false; + } + + //Adds the grey colored span to the extension + s.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.secondary_text)), + s.length() - fileExtension.length(), s.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + void setFileExtension(String extension) { + boolean oldHasModifiedFilename = hasModifiedFilename; + oldFilename = uploadFilename.getText().toString(); + fileExtension = extension; + String newFilename; + + if (!oldFilename.isEmpty()) { + newFilename = FileUtils.getFilenameWithoutExtension(oldFilename) + extension; + } else { + newFilename = extension; + } + + uploadFilename.setText(newFilename); + hasModifiedFilename = oldHasModifiedFilename; + } + } + private class CustomOnItemSelectedListener implements AdapterView.OnItemSelectedListener { private ArrayList parentCategories, childCategories; @@ -574,32 +956,79 @@ public class UploadActivity extends BaseActivity { } } - private void updateUIElements() { - String[] tmpSpinnerArray = new String[uploadRootCategories.size()]; - for (int i = 0; i < uploadRootCategories.size(); ++i) { - tmpSpinnerArray[i] = uploadRootCategories.get(i).getCategoryTitle(); + public static class ZipTask extends AsyncTask { + // Weak references will still allow the Activity to be garbage-collected + private final WeakReference weakActivity; + final String zipFilename, categorySelected, uploadTitleText, uploadDescriptionText, + fileIcon, uploaderProfileIndex; + Uri zipFileUri; + + // Suppresses default constructor + @SuppressWarnings("unused") + private ZipTask() { + weakActivity = null; + this.zipFilename = null; + this.categorySelected = null; + this.uploadTitleText = null; + this.uploadDescriptionText = null; + this.fileIcon = null; + this.uploaderProfileIndex = null; } - ArrayAdapter spinnerArrayAdapter = new ArrayAdapter<>(BaseApplication.getInstance().getApplicationContext(), - R.layout.spinner_item, tmpSpinnerArray); - spinnerArrayAdapter.setDropDownViewResource(R.layout.spinner_dropdown_item); - rootCategorySpinner.setAdapter(spinnerArrayAdapter); + ZipTask(Activity uploadsActivity, @NonNull String zipFilename, + @NonNull String categorySelected, @NonNull String uploadTitleText, + @NonNull String uploadDescriptionText, @NonNull String fileIcon, + @NonNull String uploaderProfileIndex) { + weakActivity = new WeakReference<>(uploadsActivity); + this.zipFilename = zipFilename; + this.categorySelected = categorySelected; + this.uploadTitleText = uploadTitleText; + this.uploadDescriptionText = uploadDescriptionText; + this.fileIcon = fileIcon; + this.uploaderProfileIndex = uploaderProfileIndex; + } - //Sets bundle selection - if (bundleCategory != null) { - int bundleSelectionIndex = -1, currentIndex = 0; + @Override + protected void onPreExecute() { + assert weakActivity != null; + Toast.makeText(weakActivity.get(), "Zipping files", Toast.LENGTH_SHORT).show(); + } - for (UploadCategory category : uploadRootCategories) { - if (bundleCategory.get(0).contains(category.getCategoryTitle())) { - bundleSelectionIndex = currentIndex; - break; - } - ++currentIndex; + @Override + protected Boolean doInBackground(Uri... filesToZip) { + if (weakActivity == null || zipFilename == null) { + return false; } + File zipFile = UploadsHelper.createZipFile(zipFilename); - if (bundleSelectionIndex != -1) { - rootCategorySpinner.setSelection(bundleSelectionIndex, true); - bundleCategory.remove(0); + if (zipFile == null) { + return false; + } + zipFileUri = FileProvider.getUriForFile(weakActivity.get(), + weakActivity.get().getPackageName() + + ".provider", zipFile); + + UploadsHelper.zip(weakActivity.get(), filesToZip, zipFileUri); + return true; + } + + @Override + protected void onPostExecute(Boolean result) { + if (weakActivity == null) { + return; + } + + if (!result) { + Toast.makeText(weakActivity.get(), "Couldn't create zip!", Toast.LENGTH_SHORT).show(); + return; + } + + String uploadID = UUID.randomUUID().toString(); + if (!uploadFile(weakActivity.get(), uploadID, + getConfigForUpload(weakActivity.get(), uploadID, zipFilename), categorySelected, + uploadTitleText, uploadDescriptionText, fileIcon, uploaderProfileIndex, + zipFileUri)) { + Toast.makeText(weakActivity.get(), "Couldn't initiate upload.", Toast.LENGTH_SHORT).show(); } } } diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadFieldsBuilderActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadFieldsBuilderActivity.java index f9fd9407..e89fda41 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadFieldsBuilderActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadFieldsBuilderActivity.java @@ -3,6 +3,7 @@ package gr.thmmy.mthmmy.activities.upload; import android.app.Activity; import android.content.Intent; import android.os.Bundle; +import androidx.annotation.Nullable; import android.text.Editable; import android.text.TextWatcher; import android.view.View; @@ -12,14 +13,14 @@ import android.widget.RadioGroup; import android.widget.Toast; import java.util.Calendar; +import java.util.regex.Matcher; +import java.util.regex.Pattern; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.Toolbar; import gr.thmmy.mthmmy.R; +import gr.thmmy.mthmmy.base.BaseActivity; import timber.log.Timber; -public class UploadFieldsBuilderActivity extends AppCompatActivity { +public class UploadFieldsBuilderActivity extends BaseActivity { static final String BUNDLE_UPLOAD_FIELD_BUILDER_COURSE = "UPLOAD_FIELD_BUILDER_COURSE"; static final String BUNDLE_UPLOAD_FIELD_BUILDER_SEMESTER = "UPLOAD_FIELD_BUILDER_SEMESTER"; @@ -28,6 +29,7 @@ public class UploadFieldsBuilderActivity extends AppCompatActivity { static final String RESULT_DESCRIPTION = "RESULT_DESCRIPTION"; private String course, semester; + private boolean isValidYear; private LinearLayout semesterChooserLinear; private RadioGroup typeRadio, semesterRadio; @@ -38,18 +40,17 @@ public class UploadFieldsBuilderActivity extends AppCompatActivity { @Override public void onTextChanged(CharSequence s, int start, int before, int count) { String working = s.toString(); - boolean isValid; if (working.length() == 4) { int currentYear = Calendar.getInstance().get(Calendar.YEAR); int inputYear = Integer.parseInt(working); - isValid = inputYear <= currentYear && inputYear > 2000; + isValidYear = inputYear <= currentYear && inputYear > 1980; } else { - isValid = false; + isValidYear = false; } - if (!isValid) { + if (!isValidYear) { year.setError("Please enter a valid year"); } else { year.setError(null); @@ -86,7 +87,7 @@ public class UploadFieldsBuilderActivity extends AppCompatActivity { } //Initialize toolbar - Toolbar toolbar = findViewById(R.id.toolbar); + toolbar = findViewById(R.id.toolbar); toolbar.setTitle(R.string.upload_fields_builder_toolbar_title); setSupportActionBar(toolbar); if (getSupportActionBar() != null) { @@ -94,6 +95,9 @@ public class UploadFieldsBuilderActivity extends AppCompatActivity { getSupportActionBar().setDisplayShowHomeEnabled(true); } + createDrawer(); + drawer.setSelection(UPLOAD_ID, false); + semesterChooserLinear = findViewById(R.id.upload_fields_builder_choose_semester); semesterRadio = findViewById(R.id.upload_fields_builder_semester_radio_group); semesterRadio.check(Integer.parseInt(semester) % 2 == 0 @@ -121,8 +125,8 @@ public class UploadFieldsBuilderActivity extends AppCompatActivity { } else if (semesterChooserLinear.getVisibility() == View.VISIBLE && semesterId == -1) { Toast.makeText(view.getContext(), "Please choose a semester for the upload", Toast.LENGTH_SHORT).show(); return; - } else if (year.getText().toString().isEmpty()) { - Toast.makeText(view.getContext(), "Please choose a year for the upload", Toast.LENGTH_SHORT).show(); + } else if (year.getText().toString().isEmpty() || !isValidYear) { + Toast.makeText(view.getContext(), "Please choose a valid year for the upload", Toast.LENGTH_SHORT).show(); return; } @@ -212,304 +216,324 @@ public class UploadFieldsBuilderActivity extends AppCompatActivity { return getGreeklishOrMinifiedCourseName(false); } + private String normalizeLatinNumbers(String stringWithLatinNumbers) { + String greekLatinOne = "Ι", englishLatinOne = "I"; + String normalisedString; + + //Separates the latin number suffix from the course name + final String regex = "(.+)\\ ([IΙ]+)"; + final Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE); + final Matcher matcher = pattern.matcher(stringWithLatinNumbers); + + if (matcher.matches() && matcher.groupCount() == 2) { + normalisedString = matcher.group(1) + " " + matcher.group(2).replaceAll(greekLatinOne, englishLatinOne); + } else { + normalisedString = stringWithLatinNumbers; + } + + return normalisedString; + } @Nullable private String getGreeklishOrMinifiedCourseName(boolean greeklish) { - if (course.contains("Ψηφιακή Επεξεργασία Σήματος")) { + String normalisedCourse = normalizeLatinNumbers(course); + + if (normalisedCourse.contains(("Ψηφιακή Επεξεργασία Σήματος"))) { return greeklish ? "PSES" : "ΨΕΣ"; - } else if (course.contains("Ψηφιακή Επεξεργασία Εικόνας")) { + } else if (normalisedCourse.contains(("Ψηφιακή Επεξεργασία Εικόνας"))) { return greeklish ? "psee" : "ΨΕΕ"; - } else if (course.contains("Ψηφιακές Τηλεπικοινωνίες ΙΙ")) { + } else if (normalisedCourse.contains(("Ψηφιακές Τηλεπικοινωνίες II"))) { return greeklish ? "pshf_thlep_II" : "Ψηφιακές Τηλεπ. 2"; - } else if (course.contains("Ψηφιακές Τηλεπικοινωνίες Ι")) { + } else if (normalisedCourse.contains(("Ψηφιακές Τηλεπικοινωνίες I"))) { return greeklish ? "pshf_thlep_I" : "Ψηφιακές Τηλεπ. 1"; - } else if (course.contains("Ψηφιακά Φίλτρα")) { + } else if (normalisedCourse.contains(("Ψηφιακά Φίλτρα"))) { return greeklish ? "filtra" : "Φίλτρα"; - } else if (course.contains("Ψηφιακά Συστήματα ΙΙΙ")) { + } else if (normalisedCourse.contains(("Ψηφιακά Συστήματα III"))) { return greeklish ? "pshfiaka_III" : "Ψηφιακά 3"; - } else if (course.contains("Ψηφιακά Συστήματα ΙΙ")) { + } else if (normalisedCourse.contains(("Ψηφιακά Συστήματα II"))) { return greeklish ? "pshfiaka_II" : "Ψηφιακά 2"; - } else if (course.contains("Ψηφιακά Συστήματα Ι")) { + } else if (normalisedCourse.contains(("Ψηφιακά Συστήματα I"))) { return greeklish ? "pshfiaka_I" : "Ψηφιακά 1"; - } else if (course.contains("Φωτονική Τεχνολογία")) { + } else if (normalisedCourse.contains(("Φωτονική Τεχνολογία"))) { return greeklish ? "fwtonikh" : "Φωτονική"; - } else if (course.contains("Φυσική Ι")) { + } else if (normalisedCourse.contains(("Φυσική I"))) { return greeklish ? "fysikh_I" : "Φυσική 1"; - } else if (course.contains("Υψηλές Τάσεις ΙΙΙ")) { + } else if (normalisedCourse.contains(("Υψηλές Τάσεις III"))) { return greeklish ? "ypshles_III" : "Υψηλές 3"; - } else if (course.contains("Υψηλές Τάσεις ΙΙ")) { + } else if (normalisedCourse.contains(("Υψηλές Τάσεις II"))) { return greeklish ? "ypshles_II" : "Υψηλές 2"; - } else if (course.contains("Υψηλές Τάσεις Ι")) { + } else if (normalisedCourse.contains(("Υψηλές Τάσεις I"))) { return greeklish ? "ypshles_I" : "Υψηλές 1"; - } else if (course.contains("Υψηλές Τάσεις 4")) { + } else if (normalisedCourse.contains(("Υψηλές Τάσεις 4"))) { return greeklish ? "ypshles_IV" : "Υψηλές 4"; - } else if (course.contains("Υπολογιστικός Ηλεκτρομαγνητισμός")) { + } else if (normalisedCourse.contains(("Υπολογιστικός Ηλεκτρομαγνητισμός"))) { return greeklish ? "ypologistikos_HM" : "Υπολογιστικός Η/Μ"; - } else if (course.contains("Υπολογιστικές Μέθοδοι στα Ενεργειακά Συστήματα")) { + } else if (normalisedCourse.contains(("Υπολογιστικές Μέθοδοι στα Ενεργειακά Συστήματα"))) { return greeklish ? "ymes" : "ΥΜΕΣ"; - } else if (course.contains("Τηλεπικοινωνιακή Ηλεκτρονική")) { + } else if (normalisedCourse.contains(("Τηλεπικοινωνιακή Ηλεκτρονική"))) { return greeklish ? "tilep_ilektr" : "Τηλεπ. Ηλεκτρ."; - } else if (course.contains("Τηλεοπτικά Συστήματα")) { + } else if (normalisedCourse.contains(("Τηλεοπτικά Συστήματα"))) { return greeklish ? "tileoptika" : "Τηλεοπτικά"; - } else if (course.contains("Τεχνολογία Λογισμικού")) { + } else if (normalisedCourse.contains(("Τεχνολογία Λογισμικού"))) { return greeklish ? "SE" : "Τεχνολογία Λογισμικού"; - } else if (course.contains("Τεχνολογία Ηλεκτροτεχνικών Υλικών")) { + } else if (normalisedCourse.contains(("Τεχνολογία Ηλεκτροτεχνικών Υλικών"))) { return greeklish ? "Hlektrotexnika_Ylika" : "Ηλεκτροτεχνικά Υλικά"; - } else if (course.contains("Τεχνολογία Ήχου και Εικόνας")) { + } else if (normalisedCourse.contains(("Τεχνολογία Ήχου και Εικόνας"))) { return greeklish ? "texn_hxoy_eikonas" : "Τεχνολογία Ήχου και Εικόνας"; - } else if (course.contains("Τεχνική Μηχανική")) { + } else if (normalisedCourse.contains(("Τεχνική Μηχανική"))) { return greeklish ? "texn_mhxan" : "Τεχν. Μηχαν."; - } else if (course.contains("Τεχνικές μη Καταστρεπτικών Δοκιμών")) { + } else if (normalisedCourse.contains(("Τεχνικές μη Καταστρεπτικών Δοκιμών"))) { return greeklish ? "non_destructive_tests" : "Μη Καταστρεπτικές Δοκιμές"; - } else if (course.contains("Τεχνικές Σχεδίασης με Η/Υ")) { + } else if (normalisedCourse.contains(("Τεχνικές Σχεδίασης με Η/Υ"))) { return greeklish ? "sxedio" : "Σχέδιο"; - } else if (course.contains("Τεχνικές Κωδικοποίησης")) { + } else if (normalisedCourse.contains(("Τεχνικές Κωδικοποίησης"))) { return greeklish ? "texn_kwdikopoihshs" : "Τεχνικές Κωδικοποίησης"; - } else if (course.contains("Τεχνικές Βελτιστοποίησης")) { + } else if (normalisedCourse.contains(("Τεχνικές Βελτιστοποίησης"))) { return greeklish ? "veltistopoihsh" : "Βελτιστοποίηση"; - } else if (course.contains("Σύνθεση Τηλεπικοινωνιακών Διατάξεων")) { + } else if (normalisedCourse.contains(("Σύνθεση Τηλεπικοινωνιακών Διατάξεων"))) { return greeklish ? "synth_thlep_diataksewn" : "Σύνθεση Τηλεπ. Διατάξεων"; - } else if (course.contains("Σύνθεση Ενεργών και Παθητικών Κυκλωμάτων")) { + } else if (normalisedCourse.contains(("Σύνθεση Ενεργών και Παθητικών Κυκλωμάτων"))) { return greeklish ? "synthesh" : "Σύνθεση"; - } else if (course.contains("Σχεδίαση Συστημάτων VLSI")) { + } else if (normalisedCourse.contains(("Σχεδίαση Συστημάτων VLSI"))) { return greeklish ? "VLSI" : "VLSI"; - } else if (course.contains("Συστήματα Υπολογιστών (Υπολογιστικά Συστήματα)")) { + } else if (normalisedCourse.contains(("Συστήματα Υπολογιστών (Υπολογιστικά Συστήματα)"))) { return greeklish ? "sys_ypologistwn" : "Συσ. Υπολογιστών"; - } else if (course.contains("Συστήματα Πολυμέσων και Εικονική Πραγματικότητα")) { + } else if (normalisedCourse.contains(("Συστήματα Πολυμέσων και Εικονική Πραγματικότητα"))) { return greeklish ? "polymesa" : "Πολυμέσα"; - } else if (course.contains("Συστήματα Μικροϋπολογιστών")) { + } else if (normalisedCourse.contains(("Συστήματα Μικροϋπολογιστών"))) { return greeklish ? "mikro_I" : "Μίκρο 1"; - } else if (course.contains("Συστήματα Ηλεκτροκίνησης")) { + } else if (normalisedCourse.contains(("Συστήματα Ηλεκτροκίνησης"))) { return greeklish ? "hlektrokinhsh" : "Ηλεκτροκίνηση"; - } else if (course.contains("Συστήματα Ηλεκτρικής Ενέργειας ΙΙΙ")) { + } else if (normalisedCourse.contains(("Συστήματα Ηλεκτρικής Ενέργειας III"))) { return greeklish ? "SHE_III" : "ΣΗΕ 3"; - } else if (course.contains("Συστήματα Ηλεκτρικής Ενέργειας ΙΙ")) { + } else if (normalisedCourse.contains(("Συστήματα Ηλεκτρικής Ενέργειας II"))) { return greeklish ? "SHE_II" : "ΣΗΕ 2"; - } else if (course.contains("Συστήματα Ηλεκτρικής Ενέργειας Ι")) { + } else if (normalisedCourse.contains(("Συστήματα Ηλεκτρικής Ενέργειας I"))) { return greeklish ? "SHE_I" : "ΣΗΕ 1"; - } else if (course.contains("Συστήματα Αυτομάτου Ελέγχου ΙΙI")) { + } else if (normalisedCourse.contains(("Συστήματα Αυτομάτου Ελέγχου III"))) { return greeklish ? "SAE_III" : "ΣΑΕ 3"; - } else if (course.contains("Συστήματα Αυτομάτου Ελέγχου ΙΙ")) { + } else if (normalisedCourse.contains(("Συστήματα Αυτομάτου Ελέγχου II"))) { return greeklish ? "SAE_II" : "ΣΑΕ 2"; - } else if (course.contains("Συστήματα Αυτομάτου Ελέγχου Ι")) { + } else if (normalisedCourse.contains(("Συστήματα Αυτομάτου Ελέγχου I"))) { return greeklish ? "SAE_1" : "ΣΑΕ 1"; - } else if (course.contains("Στοχαστικό Σήμα")) { + } else if (normalisedCourse.contains(("Στοχαστικό Σήμα"))) { return greeklish ? "stox_shma" : "Στοχ. Σήμα"; - } else if (course.contains("Σταθμοί Παραγωγής Ηλεκτρικής Ενέργειας")) { + } else if (normalisedCourse.contains(("Σταθμοί Παραγωγής Ηλεκτρικής Ενέργειας"))) { return greeklish ? "SPHE" : "ΣΠΗΕ"; - } else if (course.contains("Σερβοκινητήρια Συστήματα")) { + } else if (normalisedCourse.contains(("Σερβοκινητήρια Συστήματα"))) { return greeklish ? "servo" : "Σέρβο"; - } else if (course.contains("Σήματα και Συστήματα")) { + } else if (normalisedCourse.contains(("Σήματα και Συστήματα"))) { return greeklish ? "analog_shma" : "Σύματα & Συστήματα"; - } else if (course.contains("Ρομποτική")) { + } else if (normalisedCourse.contains(("Ρομποτική"))) { return greeklish ? "rompotikh" : "Ρομποτική"; - } else if (course.contains("Προσομοίωση και Μοντελοποίηση Συστημάτων")) { + } else if (normalisedCourse.contains(("Προσομοίωση και Μοντελοποίηση Συστημάτων"))) { return greeklish ? "montelopoihsh" : "Μοντελοποίηση"; - } else if (course.contains("Προηγμένες Τεχνικές Επεξεργασίας Σήματος")) { + } else if (normalisedCourse.contains(("Προηγμένες Τεχνικές Επεξεργασίας Σήματος"))) { return greeklish ? "ptes" : "ΠΤΕΣ"; - } else if (course.contains("Προγραμματιστικές Τεχνικές")) { + } else if (normalisedCourse.contains(("Προγραμματιστικές Τεχνικές"))) { return greeklish ? "cpp" : "Προγραμματ. Τεχν."; - } else if (course.contains("Προγραμματιζόμενα Κυκλώματα ASIC")) { + } else if (normalisedCourse.contains(("Προγραμματιζόμενα Κυκλώματα ASIC"))) { return greeklish ? "asic" : "ASIC"; - } else if (course.contains("Παράλληλα και Κατανεμημένα Συστήματα")) { + } else if (normalisedCourse.contains(("Παράλληλα και Κατανεμημένα Συστήματα"))) { return greeklish ? "parallhla" : "Παράλληλα"; - } else if (course.contains("Οργάνωση και Διοίκηση Εργοστασίων")) { + } else if (normalisedCourse.contains(("Οργάνωση και Διοίκηση Εργοστασίων"))) { return greeklish ? "organ_dioik_ergostasiwn" : "Οργάνωση και Διοίκηση Εργοστασίων"; - } else if (course.contains("Οργάνωση Υπολογιστών")) { + } else if (normalisedCourse.contains(("Οργάνωση Υπολογιστών"))) { return greeklish ? "org_ypol" : "Οργάνωση Υπολ."; - } else if (course.contains("Οπτική ΙΙ")) { + } else if (normalisedCourse.contains(("Οπτική II"))) { return greeklish ? "optikh_II" : "Οπτική 2"; - } else if (course.contains("Οπτική Ι")) { + } else if (normalisedCourse.contains(("Οπτική I"))) { return greeklish ? "optikh_I" : "Οπτική 1"; - } else if (course.contains("Οπτικές Επικοινωνίες")) { + } else if (normalisedCourse.contains(("Οπτικές Επικοινωνίες"))) { return greeklish ? "optikes_thlep" : "Οπτικές Τηλεπ."; - } else if (course.contains("Μικροκύματα II")) { + } else if (normalisedCourse.contains(("Μικροκύματα II"))) { return greeklish ? "mikrokymata_II" : "Μικροκύματα 2"; - } else if (course.contains("Μικροκύματα I")) { + } else if (normalisedCourse.contains(("Μικροκύματα I"))) { return greeklish ? "mikrokymata_I" : "Μικροκύματα 1"; - } else if (course.contains("Μικροκυματική Τηλεπισκόπηση")) { + } else if (normalisedCourse.contains(("Μικροκυματική Τηλεπισκόπηση"))) { return greeklish ? "thlepiskophsh" : "Τηλεπισκόπηση"; - } else if (course.contains("Μικροεπεξεργαστές και Περιφερειακά")) { + } else if (normalisedCourse.contains(("Μικροεπεξεργαστές και Περιφερειακά"))) { return greeklish ? "mikro_II" : "Μίκρο 2"; - } else if (course.contains("Μετάδοση Θερμότητας")) { + } else if (normalisedCourse.contains(("Μετάδοση Θερμότητας"))) { return greeklish ? "metadosi_therm" : "Μετάδοση Θερμ."; - } else if (course.contains("Λογισμός ΙΙ")) { + } else if (normalisedCourse.contains(("Λογισμός II"))) { return greeklish ? "logismos_II" : "Λογισμός 2"; - } else if (course.contains("Λογισμός Ι")) { + } else if (normalisedCourse.contains(("Λογισμός I"))) { return greeklish ? "logismos_I" : "Λογισμός 1"; - } else if (course.contains("Λογική Σχεδίαση")) { + } else if (normalisedCourse.contains(("Λογική Σχεδίαση"))) { return greeklish ? "logiki_sxediash" : "Λογική Σχεδίαση"; - } else if (course.contains("Λειτουργικά Συστήματα")) { + } else if (normalisedCourse.contains(("Λειτουργικά Συστήματα"))) { return greeklish ? "OS" : "Λειτουργικά"; - } else if (course.contains("Κινητές και Δορυφορικές Επικοινωνίες")) { + } else if (normalisedCourse.contains(("Κινητές και Δορυφορικές Επικοινωνίες"))) { return greeklish ? "kinhtes_doryforikes_epik" : "Κινητές & Δορυφορικές Επικοινωνίες"; - } else if (course.contains("Κβαντική Φυσική")) { + } else if (normalisedCourse.contains(("Κβαντική Φυσική"))) { return greeklish ? "kvantikh" : "Κβαντική"; - } else if (course.contains("Θεωρία και Τεχνολογία Πυρηνικών Αντιδραστήρων")) { + } else if (normalisedCourse.contains(("Θεωρία και Τεχνολογία Πυρηνικών Αντιδραστήρων"))) { return greeklish ? "texn_antidrasthrwn" : "Τεχνολογία Αντιδραστήρων"; - } else if (course.contains("Θεωρία Υπολογισμών και Αλγορίθμων")) { + } else if (normalisedCourse.contains(("Θεωρία Υπολογισμών και Αλγορίθμων"))) { return greeklish ? "thya" : "ΘΥΑ"; - } else if (course.contains("Θεωρία Σκέδασης")) { + } else if (normalisedCourse.contains(("Θεωρία Σκέδασης"))) { return greeklish ? "skedash" : "Σκέδαση"; - } else if (course.contains("Θεωρία Σημάτων και Γραμμικών Συστημάτων")) { + } else if (normalisedCourse.contains(("Θεωρία Σημάτων και Γραμμικών Συστημάτων"))) { return greeklish ? "analog_shma" : "Σύματα & Συστήματα"; - } else if (course.contains("Θεωρία Πληροφοριών")) { + } else if (normalisedCourse.contains(("Θεωρία Πληροφοριών"))) { return greeklish ? "theoria_plir" : "Θεωρία Πληρ."; - } else if (course.contains("Θεωρία Πιθανοτήτων και Στατιστική")) { + } else if (normalisedCourse.contains(("Θεωρία Πιθανοτήτων και Στατιστική"))) { return greeklish ? "pithanothtes" : "Πιθανότητες"; - } else if (course.contains("Ημιαγωγά Υλικά: Θεωρία-Διατάξεις")) { + } else if (normalisedCourse.contains(("Ημιαγωγά Υλικά: Θεωρία-Διατάξεις"))) { return greeklish ? "Hmiagwga_Ylika" : "Ημιαγωγά Υλικά"; - } else if (course.contains("Ηλεκτρονική ΙΙΙ")) { + } else if (normalisedCourse.contains(("Ηλεκτρονική III"))) { return greeklish ? "hlektronikh_III" : "Ηλεκτρονική 3"; - } else if (course.contains("Ηλεκτρονική ΙΙ")) { + } else if (normalisedCourse.contains(("Ηλεκτρονική II"))) { return greeklish ? "hlektronikh_2" : "Ηλεκτρονική 2"; - } else if (course.contains("Ηλεκτρονική Ι")) { + } else if (normalisedCourse.contains(("Ηλεκτρονική I"))) { return greeklish ? "hlektronikh_1" : "Ηλεκτρονική 1"; - } else if (course.contains("Ηλεκτρονικές Διατάξεις και Μετρήσεις")) { + } else if (normalisedCourse.contains(("Ηλεκτρονικές Διατάξεις και Μετρήσεις"))) { return greeklish ? "hlektron_diatakseis_metrhseis" : "Ηλεκτρονικές Διατάξεις και Μετρήσεις"; - } else if (course.contains("Ηλεκτρονικά Ισχύος ΙΙ")) { + } else if (normalisedCourse.contains(("Ηλεκτρονικά Iσχύος II"))) { return greeklish ? "isxyos_II" : "Ισχύος 2"; - } else if (course.contains("Ηλεκτρονικά Ισχύος Ι")) { + } else if (normalisedCourse.contains(("Ηλεκτρονικά Iσχύος I"))) { return greeklish ? "isxyos_I" : "Ισχύος 1"; - } else if (course.contains("Ηλεκτρομαγνητικό Πεδίο ΙΙ")) { + } else if (normalisedCourse.contains(("Ηλεκτρομαγνητικό Πεδίο II"))) { return greeklish ? "pedio_II" : "Πεδίο 2"; - } else if (course.contains("Ηλεκτρομαγνητικό Πεδίο Ι")) { + } else if (normalisedCourse.contains(("Ηλεκτρομαγνητικό Πεδίο I"))) { return greeklish ? "pedio_I" : "Πεδίο 1"; - } else if (course.contains("Ηλεκτρομαγνητική Συμβατότητα")) { + } else if (normalisedCourse.contains(("Ηλεκτρομαγνητική Συμβατότητα"))) { return greeklish ? "HM_symvatothta" : "H/M Συμβατότητα"; - } else if (course.contains("Ηλεκτρολογικά Υλικά")) { + } else if (normalisedCourse.contains(("Ηλεκτρολογικά Υλικά"))) { return greeklish ? "ylika" : "Ηλεκτρ. Υλικά"; - } else if (course.contains("Ηλεκτρική Οικονομία")) { + } else if (normalisedCourse.contains(("Ηλεκτρική Οικονομία"))) { return greeklish ? "hlektr_oikonomia" : "Ηλεκτρική Οικονομία"; - } else if (course.contains("Ηλεκτρικές Μηχανές Γ'")) { + } else if (normalisedCourse.contains(("Ηλεκτρικές Μηχανές Γ'"))) { return greeklish ? "mhxanes_C" : "Μηχανές Γ"; - } else if (course.contains("Ηλεκτρικές Μηχανές Β'")) { + } else if (normalisedCourse.contains(("Ηλεκτρικές Μηχανές Β'"))) { return greeklish ? "mhxanes_B" : "Μηχανές Β"; - } else if (course.contains("Ηλεκτρικές Μηχανές Α'")) { + } else if (normalisedCourse.contains(("Ηλεκτρικές Μηχανές Α'"))) { return greeklish ? "mhxanes_A" : "Μηχανές Α"; - } else if (course.contains("Ηλεκτρικές Μετρήσεις ΙΙ")) { + } else if (normalisedCourse.contains(("Ηλεκτρικές Μετρήσεις II"))) { return greeklish ? "metrhseis_II" : "Μετρήσεις 2"; - } else if (course.contains("Ηλεκτρικές Μετρήσεις Ι")) { + } else if (normalisedCourse.contains(("Ηλεκτρικές Μετρήσεις I"))) { return greeklish ? "metrhseis_1" : "Μετρήσεις 1"; - } else if (course.contains("Ηλεκτρικά Κυκλώματα ΙΙΙ")) { + } else if (normalisedCourse.contains(("Ηλεκτρικά Κυκλώματα III"))) { return greeklish ? "kyklwmata_I" : "Κυκλώματα 3"; - } else if (course.contains("Ηλεκτρικά Κυκλώματα ΙΙ")) { + } else if (normalisedCourse.contains(("Ηλεκτρικά Κυκλώματα II"))) { return greeklish ? "kyklwmata_II" : "Κυκλώματα 2"; - } else if (course.contains("Ηλεκτρικά Κυκλώματα Ι")) { + } else if (normalisedCourse.contains(("Ηλεκτρικά Κυκλώματα I"))) { return greeklish ? "kyklwmata_I" : "Κυκλώματα 1"; - } else if (course.contains("Ηλεκτρακουστική ΙΙ")) { + } else if (normalisedCourse.contains(("Ηλεκτρακουστική II"))) { return greeklish ? "hlektroakoystikh_II" : "Ηλεκτροακουστική 2"; - } else if (course.contains("Ηλεκτρακουστική Ι")) { + } else if (normalisedCourse.contains(("Ηλεκτρακουστική I"))) { return greeklish ? "hlektroakoystikh_I" : "Ηλεκτροακουστική 1"; - } else if (course.contains("Εφαρμοσμένη Θερμοδυναμική")) { + } else if (normalisedCourse.contains(("Εφαρμοσμένη Θερμοδυναμική"))) { return greeklish ? "thermodynamikh" : "Θερμοδυναμική"; - } else if (course.contains("Εφαρμοσμένα Μαθηματικά ΙΙ")) { + } else if (normalisedCourse.contains(("Εφαρμοσμένα Μαθηματικά II"))) { return greeklish ? "efarmosmena_math_II" : "Εφαρμοσμένα 2"; - } else if (course.contains("Εφαρμοσμένα Μαθηματικά Ι")) { + } else if (normalisedCourse.contains(("Εφαρμοσμένα Μαθηματικά I"))) { return greeklish ? "efarmosmena_math_I" : "Εφαρμοσμένα 1"; - } else if (course.contains("Εφαρμογές Τηλεπικοινωνιακών Διατάξεων")) { + } else if (normalisedCourse.contains(("Εφαρμογές Τηλεπικοινωνιακών Διατάξεων"))) { return greeklish ? "efarm_thlep_diataksewn" : "Εφαρμογές Τηλεπ. Διατάξεων"; - } else if (course.contains("Ευφυή Συστήματα Ρομπότ")) { + } else if (normalisedCourse.contains(("Ευφυή Συστήματα Ρομπότ"))) { return greeklish ? "eufuh" : "Ευφυή"; - } else if (course.contains("Ευρυζωνικά Δίκτυα")) { + } else if (normalisedCourse.contains(("Ευρυζωνικά Δίκτυα"))) { return greeklish ? "eyryzwnika" : "Ευρυζωνικά"; - } else if (course.contains("Επιχειρησιακή Έρευνα")) { + } else if (normalisedCourse.contains(("Επιχειρησιακή Έρευνα"))) { return greeklish ? "epixeirisiaki" : "Επιχειρησιακή Έρευνα"; - } else if (course.contains("Ενσωματωμένα Συστήματα Πραγματικού Χρόνου")) { + } else if (normalisedCourse.contains(("Ενσωματωμένα Συστήματα Πραγματικού Χρόνου"))) { return greeklish ? "enswmatwmena" : "Ενσωματωμένα"; - } else if (course.contains("Εισαγωγή στις εφαρμογές Πυρηνικής Τεχνολογίας")) { + } else if (normalisedCourse.contains(("Εισαγωγή στις εφαρμογές Πυρηνικής Τεχνολογίας"))) { return greeklish ? "Intro_Purhnikh_Texn" : "Εισ. Πυρηνικη Τεχν."; - } else if (course.contains("Εισαγωγή στην Πολιτική Οικονομία")) { + } else if (normalisedCourse.contains(("Εισαγωγή στην Πολιτική Οικονομία"))) { return greeklish ? "polit_oik" : "Πολιτική Οικονομία"; - } else if (course.contains("Εισαγωγή στην Ενεργειακή Τεχνολογία ΙΙ")) { + } else if (normalisedCourse.contains(("Εισαγωγή στην Ενεργειακή Τεχνολογία II"))) { return greeklish ? "EET_2" : "ΕΕΤ2"; - } else if (course.contains("Εισαγωγή στην Ενεργειακή Τεχνολογία Ι")) { + } else if (normalisedCourse.contains(("Εισαγωγή στην Ενεργειακή Τεχνολογία I"))) { return greeklish ? "EET_I" : "ΕΕΤ 1"; - } else if (course.contains("Ειδικές Κεραίες, Σύνθεση Κεραιών")) { + } else if (normalisedCourse.contains(("Ειδικές Κεραίες, Σύνθεση Κεραιών"))) { return greeklish ? "eidikes_keraies" : "Ειδικές Κεραίες, Σύνθεση Κεραιών"; - } else if (course.contains("Ειδικές Αρχιτεκτονικές Υπολογιστών")) { + } else if (normalisedCourse.contains(("Ειδικές Αρχιτεκτονικές Υπολογιστών"))) { return greeklish ? "eidikes_arx_ypolog" : "Ειδικές Αρχιτεκτονικές Υπολογιστών"; - } else if (course.contains("Ειδικά Κεφάλαια Συστημάτων Ηλεκτρικής Ενέργειας")) { + } else if (normalisedCourse.contains(("Ειδικά Κεφάλαια Συστημάτων Ηλεκτρικής Ενέργειας"))) { return greeklish ? "ekshe" : "ΕΚΣΗΕ"; - } else if (course.contains("Ειδικά Κεφάλαια Ηλεκτρομαγνητικού Πεδίου Ι")) { - return greeklish ? "eidika_kef_HM_pedioy_I" : "Ειδικά Κεφάλαια Ηλεκτρομαγνητικού Πεδίου Ι"; - } else if (course.contains("Ειδικά Κεφάλαια Διαφορικών Εξισώσεων")) { + } else if (normalisedCourse.contains(("Ειδικά Κεφάλαια Ηλεκτρομαγνητικού Πεδίου I"))) { + return greeklish ? "eidika_kef_HM_pedioy_I" : "Ειδικά Κεφάλαια Ηλεκτρομαγνητικού Πεδίου I"; + } else if (normalisedCourse.contains(("Ειδικά Κεφάλαια Διαφορικών Εξισώσεων"))) { return greeklish ? "eidika_kef_diaf_eksis" : "Ειδικά Κεφάλαια Διαφορικών Εξισώσεων"; - } else if (course.contains("Δομημένος Προγραμματισμός")) { + } else if (normalisedCourse.contains(("Δομημένος Προγραμματισμός"))) { return greeklish ? "C" : "Δομ. Προγραμμ."; - } else if (course.contains("Δομές Δεδομένων")) { + } else if (normalisedCourse.contains(("Δομές Δεδομένων"))) { return greeklish ? "dom_dedomenwn" : "Δομ. Δεδομ."; - } else if (course.contains("Διαχείριση Συστημάτων Ηλεκτρικής Ενέργειας")) { + } else if (normalisedCourse.contains(("Διαχείριση Συστημάτων Ηλεκτρικής Ενέργειας"))) { return greeklish ? "dshe" : "ΔΣΗΕ"; - } else if (course.contains("Διαφορικές Εξισώσεις")) { + } else if (normalisedCourse.contains(("Διαφορικές Εξισώσεις"))) { return greeklish ? "diaforikes" : "Διαφορικές"; - } else if (course.contains("Διανεμημένη Παραγωγή")) { + } else if (normalisedCourse.contains(("Διανεμημένη Παραγωγή"))) { return greeklish ? "dian_paragwgh" : "Διανεμημένη Παραγωγή"; - } else if (course.contains("Διακριτά μαθηματικά")) { + } else if (normalisedCourse.contains(("Διακριτά μαθηματικά"))) { return greeklish ? "diakrita" : "Διακριτά Μαθηματικά"; - } else if (course.contains("Διακριτά Μαθηματικά")) { + } else if (normalisedCourse.contains(("Διακριτά Μαθηματικά"))) { return greeklish ? "diakrita" : "Διακριτά Μαθηματικά"; - } else if (course.contains("Διάδοση Ηλεκτρομαγνητικού Κύματος Ι (πρώην Πεδίο ΙΙΙ)")) { + } else if (normalisedCourse.contains(("Διάδοση Ηλεκτρομαγνητικού Κύματος I (πρώην Πεδίο III)"))) { return greeklish ? "diadosi_1" : "Διάδοση 1"; - } else if (course.contains("Διάδοση Η/Μ Κύματος ΙΙ")) { + } else if (normalisedCourse.contains(("Διάδοση Η/Μ Κύματος II"))) { return greeklish ? "diadosi_II" : "Διάδοση 2"; - } else if (course.contains("Δίκτυα Υπολογιστών ΙΙ")) { + } else if (normalisedCourse.contains(("Δίκτυα Υπολογιστών II"))) { return greeklish ? "diktya_II" : "Δίκτυα 2"; - } else if (course.contains("Δίκτυα Υπολογιστών Ι")) { + } else if (normalisedCourse.contains(("Δίκτυα Υπολογιστών I"))) { return greeklish ? "diktya_I" : "Δίκτυα 1"; - } else if (course.contains("Δίκτυα Τηλεπικοινωνιών")) { + } else if (normalisedCourse.contains(("Δίκτυα Τηλεπικοινωνιών"))) { return greeklish ? "diktya_thlep" : "Δίκτυα Τηλέπ."; - } else if (course.contains("Γραφική με Υπολογιστές")) { + } else if (normalisedCourse.contains(("Γραφική με Υπολογιστές"))) { return greeklish ? "grafikh" : "Γραφική"; - } else if (course.contains("Γραμμική Άλγεβρα")) { + } else if (normalisedCourse.contains(("Γραμμική Άλγεβρα"))) { return greeklish ? "grammikh_algebra" : "Γραμμ. Άλγεβρ."; - } else if (course.contains("Γεωηλεκτρομαγνητισμός")) { + } else if (normalisedCourse.contains(("Γεωηλεκτρομαγνητισμός"))) { return greeklish ? "geohlektromagnitismos" : "Γεωηλεκτρομαγνητισμός"; - } else if (course.contains("Βιοϊατρική Τεχνολογία")) { + } else if (normalisedCourse.contains(("Βιοϊατρική Τεχνολογία"))) { return greeklish ? "vioiatriki" : "Βιοιατρική"; - } else if (course.contains("Βιομηχανική Πληροφορική")) { + } else if (normalisedCourse.contains(("Βιομηχανική Πληροφορική"))) { return greeklish ? "viomix_plir" : "Βιομηχανική Πληρ"; - } else if (course.contains("Βιομηχανικά Ηλεκτρονικά")) { + } else if (normalisedCourse.contains(("Βιομηχανικά Ηλεκτρονικά"))) { return greeklish ? "bhomix_hlektronika" : "Βιομηχανικά Ηλεκτρονικά"; - } else if (course.contains("Βάσεις Δεδομένων")) { + } else if (normalisedCourse.contains(("Βάσεις Δεδομένων"))) { return greeklish ? "vaseis" : "Βάσεις"; - } else if (course.contains("Ασύρματος Τηλεπικοινωνία ΙΙ")) { + } else if (normalisedCourse.contains(("Ασύρματος Τηλεπικοινωνία II"))) { return greeklish ? "asyrmatos_II" : "Ασύρματος 2"; - } else if (course.contains("Ασύρματος Τηλεπικοινωνία Ι")) { + } else if (normalisedCourse.contains(("Ασύρματος Τηλεπικοινωνία I"))) { return greeklish ? "asyrmatos_I" : "Ασύρματος 1"; - } else if (course.contains("Ασφάλεια Πληροφοριακών Συστημάτων")) { + } else if (normalisedCourse.contains(("Ασφάλεια Πληροφοριακών Συστημάτων"))) { return greeklish ? "asfaleia" : "Ασφάλεια"; - } else if (course.contains("Ασαφή Συστήματα")) { + } else if (normalisedCourse.contains(("Ασαφή Συστήματα"))) { return greeklish ? "asafh" : "Ασαφή"; - } else if (course.contains("Αρχιτεκτονική Υπολογιστών")) { + } else if (normalisedCourse.contains(("Αρχιτεκτονική Υπολογιστών"))) { return greeklish ? "arx_ypologistwn" : "Αρχ. Υπολογιστών"; - } else if (course.contains("Αρχές Παράλληλης Επεξεργασίας")) { + } else if (normalisedCourse.contains(("Αρχές Παράλληλης Επεξεργασίας"))) { return greeklish ? "arxes_parall_epeksergasias" : "Αρχές Παράλληλης Επεξεργασίας"; - } else if (course.contains("Αρχές Οικονομίας")) { + } else if (normalisedCourse.contains(("Αρχές Οικονομίας"))) { return greeklish ? "arx_oikonomias" : "Αρχές Οικονομίας"; - } else if (course.contains("Αριθμητική Ανάλυση")) { + } else if (normalisedCourse.contains(("Αριθμητική Ανάλυση"))) { return greeklish ? "arith_anal" : "Αριθμ. Ανάλυση"; - } else if (course.contains("Αξιοπιστία Συστημάτων")) { + } else if (normalisedCourse.contains(("Αξιοπιστία Συστημάτων"))) { return greeklish ? "aksiopistia_systhmatwn" : "Αξιοπιστία Συστημάτων"; - } else if (course.contains("Αντικειμενοστραφής Προγραμματισμός")) { + } else if (normalisedCourse.contains(("Αντικειμενοστραφής Προγραμματισμός"))) { return greeklish ? "OOP" : "Αντικειμενοστραφής"; - } else if (course.contains("Αναλογικές Τηλεπικοινωνίες (πρώην Τηλεπικοινωνιακά Συστήματα Ι)")) { + } else if (normalisedCourse.contains(("Αναλογικές Τηλεπικοινωνίες (πρώην Τηλεπικοινωνιακά Συστήματα I)"))) { return greeklish ? "anal_thlep" : "Αναλογικές Τηλεπ."; - } else if (course.contains("Αναγνώριση Προτύπων")) { + } else if (normalisedCourse.contains(("Αναγνώριση Προτύπων"))) { return greeklish ? "protipa" : "Αναγνώριση Προτύπων"; - } else if (course.contains("Ανάλυση και Σχεδίαση Αλγορίθμων")) { + } else if (normalisedCourse.contains(("Ανάλυση και Σχεδίαση Αλγορίθμων"))) { return greeklish ? "algorithms" : "Αλγόριθμοι"; - } else if (course.contains("Ανάλυση Χρονοσειρών")) { + } else if (normalisedCourse.contains(("Ανάλυση Χρονοσειρών"))) { return greeklish ? "xronoseires" : "Χρονοσειρές"; - } else if (course.contains("Ανάλυση Συστημάτων Ηλεκτρικής Ενέργειας")) { + } else if (normalisedCourse.contains(("Ανάλυση Συστημάτων Ηλεκτρικής Ενέργειας"))) { return greeklish ? "ASHE" : "ΑΣΗΕ"; - } else if (course.contains("Ανάλυση Ηλεκτρικών Κυκλωμάτων με Υπολογιστή")) { + } else if (normalisedCourse.contains(("Ανάλυση Ηλεκτρικών Κυκλωμάτων με Υπολογιστή"))) { return greeklish ? "analysh_hlektr_kykl" : "Ανάλυση Ηλεκτρικ. Κυκλ. με Υπολογιστή"; - } else if (course.contains("Ακουστική ΙΙ")) { + } else if (normalisedCourse.contains(("Ακουστική II"))) { return greeklish ? "akoystikh_II" : "Ακουστική 2"; - } else if (course.contains("Ακουστική Ι")) { + } else if (normalisedCourse.contains(("Ακουστική I"))) { return greeklish ? "akoystikh_I" : "Ακουστική 1"; } else { + Timber.wtf("Unrecognised course came in the upload fields generator! Course string = %s", course); return null; } } diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadsHelper.java b/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadsHelper.java index e5f5301b..1bb45c84 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadsHelper.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadsHelper.java @@ -1,64 +1,44 @@ package gr.thmmy.mthmmy.activities.upload; import android.content.Context; -import android.content.res.AssetFileDescriptor; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Matrix; -import android.media.ExifInterface; import android.net.Uri; import android.os.Environment; -import android.provider.OpenableColumns; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.FileProvider; + +import android.util.Log; import android.widget.Toast; +import com.snatik.storage.Storage; + import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; +import gr.thmmy.mthmmy.utils.FileUtils; import timber.log.Timber; -class UploadsHelper { - private static final int DEFAULT_MIN_WIDTH_QUALITY = 400; - private static final String CACHE_IMAGE_NAME = "tempUploadFile.jpg"; - - @NonNull - static String filenameFromUri(Context context, Uri uri) { - String filename = null; - if (uri.getScheme().equals("content")) { - try (Cursor cursor = context.getContentResolver().query(uri, null, null, null, null)) { - if (cursor != null && cursor.moveToFirst()) { - filename = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); - } - } - } - if (filename == null) { - filename = uri.getPath(); - int cut = filename.lastIndexOf('/'); - if (cut != -1) { - filename = filename.substring(cut + 1); - } - } - - return filename; - } +public class UploadsHelper { + private final static int BUFFER = 4096; + private static final String TEMP_FILES_DIRECTORY = "~tmp_mThmmy_uploads"; @SuppressWarnings("ResultOfMethodCallIgnored") @Nullable - static String createTempFile(Context context, Uri fileUri, String newFilename) { - String oldFilename = filenameFromUri(context, fileUri); + static Uri createTempFile(Context context, Storage storage, Uri fileUri, String newFilename) { + String oldFilename = FileUtils.filenameFromUri(context, fileUri); String fileExtension = oldFilename.substring(oldFilename.indexOf(".")); String destinationFilename = Environment.getExternalStorageDirectory().getPath() + - File.separatorChar + "~tmp_mThmmy_uploads" + File.separatorChar + newFilename + fileExtension; + File.separatorChar + TEMP_FILES_DIRECTORY + File.separatorChar + newFilename + fileExtension; File tempDirectory = new File(android.os.Environment.getExternalStorageDirectory().getPath() + - File.separatorChar + "~tmp_mThmmy_uploads"); + File.separatorChar + TEMP_FILES_DIRECTORY); if (!tempDirectory.exists()) { if (!tempDirectory.mkdirs()) { @@ -97,103 +77,65 @@ class UploadsHelper { } } - return destinationFilename; - } + return FileProvider.getUriForFile(context, context.getPackageName() + + ".provider", storage.getFile(destinationFilename)); - static File getCacheFile(Context context) { - File imageFile = new File(context.getExternalCacheDir(), CACHE_IMAGE_NAME); - //noinspection ResultOfMethodCallIgnored - imageFile.getParentFile().mkdirs(); - return imageFile; } - @SuppressWarnings("ResultOfMethodCallIgnored") - static void deleteTempFiles() { - File tempFilesDirectory = new File(Environment.getExternalStorageDirectory().getPath() + - File.separatorChar + "~tmp_mThmmy_uploads"); - - if (tempFilesDirectory.isDirectory()) { - String[] tempFilesArray = tempFilesDirectory.list(); - for (String tempFile : tempFilesArray) { - new File(tempFilesDirectory, tempFile).delete(); + @Nullable + public static File createZipFile(@NonNull String zipFilename) { + // Create a zip file name + File zipFolder = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) + + File.separator + "mThmmy"); + + if (!zipFolder.exists()) { + if (!zipFolder.mkdirs()) { + Timber.w("Zip folder build returned false in %s", UploadsHelper.class.getSimpleName()); + return null; } - tempFilesDirectory.delete(); - } - } - - @SuppressWarnings("ResultOfMethodCallIgnored") - static void deleteCacheFiles(Context context) { - File cacheFilesDirectory = context.getExternalCacheDir(); - assert cacheFilesDirectory != null; - String[] tempFilesArray = cacheFilesDirectory.list(); - for (String tempFile : tempFilesArray) { - new File(cacheFilesDirectory, tempFile).delete(); - } - } - - /** - * Resize to avoid using too much memory loading big images (e.g.: 2560*1920) - **/ - static Bitmap getImageResized(Context context, Uri selectedImage) { - Bitmap bm; - int[] sampleSizes = new int[]{5, 3, 2, 1}; - int i = 0; - do { - bm = decodeBitmap(context, selectedImage, sampleSizes[i]); - i++; - } while (bm.getWidth() < DEFAULT_MIN_WIDTH_QUALITY && i < sampleSizes.length); - return bm; - } - - private static Bitmap decodeBitmap(Context context, Uri theUri, int sampleSize) { - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inSampleSize = sampleSize; - - AssetFileDescriptor fileDescriptor = null; - try { - fileDescriptor = context.getContentResolver().openAssetFileDescriptor(theUri, "r"); - } catch (FileNotFoundException e) { - e.printStackTrace(); } - assert fileDescriptor != null; - return BitmapFactory.decodeFileDescriptor( - fileDescriptor.getFileDescriptor(), null, options); + return new File(zipFolder, zipFilename); } - static int getRotation(Context context, Uri imageUri) { - int rotation = 0; + public static void zip(Context context, Uri[] files, Uri zipFile) { try { - - context.getContentResolver().notifyChange(imageUri, null); - ExifInterface exif = new ExifInterface(imageUri.getPath()); - int orientation = exif.getAttributeInt( - ExifInterface.TAG_ORIENTATION, - ExifInterface.ORIENTATION_NORMAL); - - switch (orientation) { - case ExifInterface.ORIENTATION_ROTATE_270: - rotation = 270; - break; - case ExifInterface.ORIENTATION_ROTATE_180: - rotation = 180; - break; - case ExifInterface.ORIENTATION_ROTATE_90: - rotation = 90; - break; + BufferedInputStream origin; + OutputStream dest = context.getContentResolver().openOutputStream(zipFile); + assert dest != null; + ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(dest)); + byte data[] = new byte[BUFFER]; + + for (Uri file : files) { + InputStream inputStream = context.getContentResolver().openInputStream(file); + assert inputStream != null; + origin = new BufferedInputStream(inputStream, BUFFER); + + ZipEntry entry = new ZipEntry(FileUtils.filenameFromUri(context, file)); + out.putNextEntry(entry); + int count; + + while ((count = origin.read(data, 0, BUFFER)) != -1) { + out.write(data, 0, count); + } + origin.close(); } + + out.close(); } catch (Exception e) { e.printStackTrace(); } - return rotation; } - static Bitmap rotate(Bitmap bm, int rotation) { - if (rotation != 0) { - Matrix matrix = new Matrix(); - matrix.postRotate(rotation); - return Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), matrix, true); + public static void deleteTempFiles(Storage storage) { + File tempFilesDirectory = new File(Environment.getExternalStorageDirectory().getPath() + + File.separatorChar + TEMP_FILES_DIRECTORY); + + if (storage.isDirectoryExists(tempFilesDirectory.getAbsolutePath())) { + for (File tempFile : storage.getFiles(tempFilesDirectory.getAbsolutePath())) { + storage.deleteFile(tempFile.getAbsolutePath()); + } + storage.deleteDirectory(tempFilesDirectory.getAbsolutePath()); } - return bm; } } diff --git a/app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java b/app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java index d8019545..0671b7e3 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java @@ -2,21 +2,34 @@ package gr.thmmy.mthmmy.base; import android.Manifest; import android.app.ProgressDialog; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; +import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.widget.Button; import android.widget.ImageButton; +import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.core.content.ContextCompat; +import androidx.core.content.FileProvider; +import androidx.lifecycle.ViewModelProviders; +import androidx.preference.PreferenceManager; + import com.google.android.material.bottomsheet.BottomSheetDialog; import com.google.firebase.messaging.FirebaseMessaging; import com.mikepenz.fontawesome_typeface_library.FontAwesome; @@ -28,6 +41,9 @@ import com.mikepenz.materialdrawer.Drawer; import com.mikepenz.materialdrawer.DrawerBuilder; import com.mikepenz.materialdrawer.model.PrimaryDrawerItem; import com.mikepenz.materialdrawer.model.ProfileDrawerItem; +import com.snatik.storage.Storage; + +import net.gotev.uploadservice.UploadService; import java.io.BufferedReader; import java.io.File; @@ -35,14 +51,6 @@ import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; -import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.Toolbar; -import androidx.core.content.ContextCompat; -import androidx.core.content.FileProvider; -import androidx.lifecycle.ViewModelProviders; -import androidx.preference.PreferenceManager; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.activities.AboutActivity; import gr.thmmy.mthmmy.activities.LoginActivity; @@ -52,12 +60,15 @@ import gr.thmmy.mthmmy.activities.main.MainActivity; import gr.thmmy.mthmmy.activities.profile.ProfileActivity; import gr.thmmy.mthmmy.activities.settings.SettingsActivity; import gr.thmmy.mthmmy.activities.shoutbox.ShoutboxActivity; +import gr.thmmy.mthmmy.activities.upload.UploadActivity; import gr.thmmy.mthmmy.model.Bookmark; import gr.thmmy.mthmmy.model.ThmmyFile; import gr.thmmy.mthmmy.services.DownloadHelper; +import gr.thmmy.mthmmy.services.UploadsReceiver; import gr.thmmy.mthmmy.session.SessionManager; import gr.thmmy.mthmmy.utils.FileUtils; import gr.thmmy.mthmmy.viewmodel.BaseViewModel; +import me.zhanghai.android.materialprogressbar.MaterialProgressBar; import okhttp3.OkHttpClient; import ru.noties.markwon.LinkResolverDef; import ru.noties.markwon.Markwon; @@ -72,6 +83,7 @@ import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_ import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_USERNAME; import static gr.thmmy.mthmmy.activities.settings.SettingsActivity.DEFAULT_HOME_TAB; import static gr.thmmy.mthmmy.services.DownloadHelper.SAVE_DIR; +import static gr.thmmy.mthmmy.services.UploadsReceiver.UPLOAD_ID_KEY; import static gr.thmmy.mthmmy.session.SessionManager.SUCCESS; import static gr.thmmy.mthmmy.utils.FileUtils.getMimeType; @@ -82,6 +94,9 @@ public abstract class BaseActivity extends AppCompatActivity { //SessionManager protected static SessionManager sessionManager; + //Storage manager + protected Storage storage; + //Bookmarks public static final String BOOKMARKS_SHARED_PREFS = "bookmarksSharedPrefs"; public static final String BOOKMARKED_TOPICS_KEY = "bookmarkedTopicsKey"; @@ -96,6 +111,9 @@ public abstract class BaseActivity extends AppCompatActivity { //Common UI elements protected Toolbar toolbar; protected Drawer drawer; + //Uploads progress dialog + UploadsShowDialogReceiver uploadsShowDialogReceiver; + AlertDialog uploadsProgressDialog; private MainActivity mainActivity; private boolean isMainActivity; @@ -123,16 +141,25 @@ public abstract class BaseActivity extends AppCompatActivity { BaseViewModel baseViewModel = ViewModelProviders.of(this).get(BaseViewModel.class); baseViewModel.getCurrentPageBookmark().observe(this, thisPageBookmark -> setTopicBookmark(thisPageBookmarkMenuButton)); + + storage = new Storage(getApplicationContext()); } @Override protected void onResume() { super.onResume(); updateDrawer(); - if (!sharedPreferences.getBoolean(getString(R.string.user_consent_shared_preference_key), false) && !isUserConsentDialogShown){ - isUserConsentDialogShown=true; + if (!sharedPreferences.getBoolean(getString(R.string.user_consent_shared_preference_key), false) && !isUserConsentDialogShown) { + isUserConsentDialogShown = true; showUserConsentDialog(); } + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + if (uploadsShowDialogReceiver == null) { + uploadsShowDialogReceiver = new UploadsShowDialogReceiver(this); + } + this.registerReceiver(uploadsShowDialogReceiver, new IntentFilter(UploadsReceiver.ACTION_COMBINED_UPLOAD)); + } } @Override @@ -140,6 +167,10 @@ public abstract class BaseActivity extends AppCompatActivity { super.onPause(); if (drawer != null) //close drawer animation after returning to activity drawer.closeDrawer(); + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP && uploadsShowDialogReceiver != null) { + this.unregisterReceiver(uploadsShowDialogReceiver); + } } @@ -151,6 +182,10 @@ public abstract class BaseActivity extends AppCompatActivity { return sessionManager; } + public Storage getStorage() { + return storage; + } + //TODO: move stuff below (?) //------------------------------------------DRAWER STUFF---------------------------------------- protected static final int HOME_ID = 0; @@ -254,14 +289,15 @@ public abstract class BaseActivity extends AppCompatActivity { .withName(R.string.downloads) .withIcon(downloadsIcon) .withSelectedIcon(downloadsIconSelected); -// uploadItem = new PrimaryDrawerItem() -// .withTextColor(primaryColor) -// .withSelectedColor(selectedPrimaryColor) -// .withSelectedTextColor(selectedSecondaryColor) -// .withIdentifier(UPLOAD_ID) -// .withName(R.string.upload) -// .withIcon(uploadIcon) -// .withSelectedIcon(uploadIconSelected); + + uploadItem = new PrimaryDrawerItem() + .withTextColor(primaryColor) + .withSelectedColor(selectedPrimaryColor) + .withSelectedTextColor(selectedSecondaryColor) + .withIdentifier(UPLOAD_ID) + .withName(R.string.upload) + .withIcon(uploadIcon) + .withSelectedIcon(uploadIconSelected); shoutboxItem = new PrimaryDrawerItem() .withTextColor(primaryColor) @@ -377,11 +413,11 @@ public abstract class BaseActivity extends AppCompatActivity { intent.putExtras(extras); startActivity(intent); } -// } else if (drawerItem.equals(UPLOAD_ID)) { -// if (!(BaseActivity.this instanceof UploadActivity)) { -// Intent intent = new Intent(BaseActivity.this, UploadActivity.class); -// startActivity(intent); -// } + } else if (drawerItem.equals(UPLOAD_ID)) { + if (!(BaseActivity.this instanceof UploadActivity)) { + Intent intent = new Intent(BaseActivity.this, UploadActivity.class); + startActivity(intent); + } } else if (drawerItem.equals(BOOKMARKS_ID)) { if (!(BaseActivity.this instanceof BookmarksActivity)) { Intent intent = new Intent(BaseActivity.this, BookmarksActivity.class); @@ -432,7 +468,7 @@ public abstract class BaseActivity extends AppCompatActivity { if (!sessionManager.isLoggedIn()) //When logged out or if user is guest { drawer.removeItem(DOWNLOADS_ID); -// drawer.removeItem(UPLOAD_ID); + drawer.removeItem(UPLOAD_ID); loginLogoutItem.withName(R.string.login).withIcon(loginIcon); //Swap logout with login profileDrawerItem.withName(sessionManager.getUsername()); setDefaultAvatar(); @@ -440,9 +476,9 @@ public abstract class BaseActivity extends AppCompatActivity { if (!drawer.getDrawerItems().contains(downloadsItem)) { drawer.addItemAtPosition(downloadsItem, 4); } -// if (!drawer.getDrawerItems().contains(uploadItem)) { -// drawer.addItemAtPosition(uploadItem, 5); -// } + if (!drawer.getDrawerItems().contains(uploadItem)) { + drawer.addItemAtPosition(uploadItem, 5); + } loginLogoutItem.withName(R.string.logout).withIcon(logoutIcon); //Swap login with logout profileDrawerItem.withName(sessionManager.getUsername()); if (sessionManager.hasAvatar()) @@ -667,10 +703,10 @@ public abstract class BaseActivity extends AppCompatActivity { //-------------------------------------------BOOKMARKS END------------------------------------------ //-------PERMS--------- - private static final int PERMISSIONS_REQUEST_CODE = 69; + private static final int DOWNLOAD_REQUEST_CODE = 69; //Arbitrary, application specific //True if permissions are OK - private boolean checkPerms() { + protected boolean checkPerms() { if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) { String[] PERMISSIONS_STORAGE = { Manifest.permission.READ_EXTERNAL_STORAGE, @@ -683,13 +719,13 @@ public abstract class BaseActivity extends AppCompatActivity { } //Display popup for user to grant permission - private void requestPerms() { //Runtime permissions request for devices with API >= 23 + protected void requestPerms(int code) { //Runtime permissions request for devices with API >= 23 if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) { String[] PERMISSIONS_STORAGE = { Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}; - requestPermissions(PERMISSIONS_STORAGE, PERMISSIONS_REQUEST_CODE); + requestPermissions(PERMISSIONS_STORAGE, code); } } @@ -698,8 +734,9 @@ public abstract class BaseActivity extends AppCompatActivity { public void onRequestPermissionsResult(int permsRequestCode, @NonNull String[] permissions , @NonNull int[] grantResults) { switch (permsRequestCode) { - case PERMISSIONS_REQUEST_CODE: - downloadFile(); + case DOWNLOAD_REQUEST_CODE: + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) + prepareDownload(tempThmmyFile); break; } } @@ -713,7 +750,7 @@ public abstract class BaseActivity extends AppCompatActivity { prepareDownload(thmmyFile); else { tempThmmyFile = thmmyFile; - requestPerms(); + requestPerms(DOWNLOAD_REQUEST_CODE); } } @@ -835,6 +872,93 @@ public abstract class BaseActivity extends AppCompatActivity { editor.putBoolean(getString(R.string.pref_privacy_analytics_enable_key), enabled).apply(); } + //------------------------------------------ UPLOADS ------------------------------------------- + private class UploadsShowDialogReceiver extends BroadcastReceiver { + private final Context activityContext; + + UploadsShowDialogReceiver(Context activityContext) { + this.activityContext = activityContext; + } + + @Override + public void onReceive(Context context, Intent intent) { + Bundle intentBundle = intent.getExtras(); + if (intentBundle == null) { + return; + } + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + String dialogUploadID = intentBundle.getString(UPLOAD_ID_KEY); + + /*String retryFilename = intentBundle.getString(UPLOAD_RETRY_FILENAME); + String retryCategory = intentBundle.getString(UPLOAD_RETRY_CATEGORY); + String retryTitleText = intentBundle.getString(UPLOAD_RETRY_TITLE); + String retryDescription = intentBundle.getString(UPLOAD_RETRY_DESCRIPTION); + String retryIcon = intentBundle.getString(UPLOAD_RETRY_ICON); + String retryUploaderProfile = intentBundle.getString(UPLOAD_RETRY_UPLOADER); + Uri retryFileUri = (Uri) intentBundle.get(UPLOAD_RETRY_FILE_URI); + + Intent retryIntent = new Intent(context, UploadsReceiver.class); + retryIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + retryIntent.setAction(UploadsReceiver.ACTION_RETRY_UPLOAD); + + retryIntent.putExtra(UPLOAD_RETRY_FILENAME, retryFilename); + retryIntent.putExtra(UPLOAD_RETRY_CATEGORY, retryCategory); + retryIntent.putExtra(UPLOAD_RETRY_TITLE, retryTitleText); + retryIntent.putExtra(UPLOAD_RETRY_DESCRIPTION, retryDescription); + retryIntent.putExtra(UPLOAD_RETRY_ICON, retryIcon); + retryIntent.putExtra(UPLOAD_RETRY_UPLOADER, retryUploaderProfile); + retryIntent.putExtra(UPLOAD_RETRY_FILE_URI, retryFileUri);*/ + + if (uploadsProgressDialog == null) { + AlertDialog.Builder progressDialogBuilder = new AlertDialog.Builder(activityContext); + LayoutInflater inflater = LayoutInflater.from(activityContext); + LinearLayout progressDialogLayout = (LinearLayout) inflater.inflate(R.layout.dialog_upload_progress, null); + + MaterialProgressBar dialogProgressBar = progressDialogLayout.findViewById(R.id.dialogProgressBar); + dialogProgressBar.setMax(100); + + progressDialogBuilder.setView(progressDialogLayout); + + uploadsProgressDialog = progressDialogBuilder.create(); + if (!UploadService.getTaskList().contains("" + dialogUploadID)) { + //Upload probably failed at this point + uploadsProgressDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "Retry", (progressDialog, progressWhich) -> { + /*LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context.getApplicationContext()); + localBroadcastManager.sendBroadcast(multipartUploadRetryIntent);*/ + uploadsProgressDialog.dismiss(); + + //context.sendBroadcast(retryIntent); + }); + uploadsProgressDialog.setButton(AlertDialog.BUTTON_NEGATIVE, "Cancel", (progressDialog, progressWhich) -> { + uploadsProgressDialog.dismiss(); + }); + + TextView dialogProgressText = progressDialogLayout.findViewById(R.id.dialog_upload_progress_text); + dialogProgressBar.setVisibility(View.GONE); + dialogProgressText.setText("Upload failed."); + + uploadsProgressDialog.show(); + } else { + //Empty buttons are needed, they are updated with correct values in the receiver + uploadsProgressDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "placeholder", (progressDialog, progressWhich) -> { + }); + uploadsProgressDialog.setButton(AlertDialog.BUTTON_NEGATIVE, "placeholder", (progressDialog, progressWhich) -> { + }); + + UploadsReceiver.setDialogDisplay(uploadsProgressDialog, dialogUploadID, null); + //UploadsReceiver.setDialogDisplay(uploadsProgressDialog, dialogUploadID, retryIntent); + uploadsProgressDialog.show(); + } + } else { + UploadsReceiver.setDialogDisplay(uploadsProgressDialog, dialogUploadID, null); + //UploadsReceiver.setDialogDisplay(uploadsProgressDialog, dialogUploadID, retryIntent); + uploadsProgressDialog.show(); + } + } + } + } + //----------------------------------MISC---------------------- protected void setMainActivity(MainActivity mainActivity) { this.mainActivity = mainActivity; diff --git a/app/src/main/java/gr/thmmy/mthmmy/model/UploadFile.java b/app/src/main/java/gr/thmmy/mthmmy/model/UploadFile.java new file mode 100644 index 00000000..c1bdb539 --- /dev/null +++ b/app/src/main/java/gr/thmmy/mthmmy/model/UploadFile.java @@ -0,0 +1,44 @@ +package gr.thmmy.mthmmy.model; + +import android.net.Uri; +import androidx.annotation.Nullable; + +import java.io.File; + +public class UploadFile { + private final boolean isCameraPhoto; + private Uri fileUri; + private File photoFile; + + private UploadFile() { + isCameraPhoto = false; + fileUri = null; + photoFile = null; + } + + public UploadFile(boolean isCameraPhoto, Uri fileUri, @Nullable File photoFile) { + this.isCameraPhoto = isCameraPhoto; + this.fileUri = fileUri; + this.photoFile = photoFile; + } + + public boolean isCameraPhoto() { + return isCameraPhoto; + } + + public Uri getFileUri() { + return fileUri; + } + + public File getPhotoFile() { + return photoFile; + } + + public void setFileUri(Uri fileUri) { + this.fileUri = fileUri; + } + + public void setPhotoFile(File photoFile) { + this.photoFile = photoFile; + } +} diff --git a/app/src/main/java/gr/thmmy/mthmmy/services/UploadsReceiver.java b/app/src/main/java/gr/thmmy/mthmmy/services/UploadsReceiver.java new file mode 100644 index 00000000..c7c56e86 --- /dev/null +++ b/app/src/main/java/gr/thmmy/mthmmy/services/UploadsReceiver.java @@ -0,0 +1,223 @@ +package gr.thmmy.mthmmy.services; + +import android.app.NotificationManager; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import androidx.appcompat.app.AlertDialog; + +import android.view.View; +import android.view.Window; +import android.widget.Button; +import android.widget.TextView; +import android.widget.Toast; + +import com.snatik.storage.Storage; + +import net.gotev.uploadservice.ServerResponse; +import net.gotev.uploadservice.UploadInfo; +import net.gotev.uploadservice.UploadService; +import net.gotev.uploadservice.UploadServiceBroadcastReceiver; + +import gr.thmmy.mthmmy.R; +import gr.thmmy.mthmmy.activities.upload.UploadsHelper; +import gr.thmmy.mthmmy.base.BaseApplication; +import me.zhanghai.android.materialprogressbar.MaterialProgressBar; + +public class UploadsReceiver extends UploadServiceBroadcastReceiver { + public static final String UPLOAD_ID_KEY = "UPLOAD_ID_KEY"; + + public static final String ACTION_COMBINED_UPLOAD = "ACTION_COMBINED_UPLOAD"; + public static final String ACTION_CANCEL_UPLOAD = "ACTION_CANCEL_UPLOAD"; + public static final String ACTION_RETRY_UPLOAD = "ACTION_RETRY_UPLOAD"; + + /*public static final String UPLOAD_RETRY_FILENAME = "UPLOAD_RETRY_FILENAME"; + public static final String UPLOAD_RETRY_CATEGORY = "UPLOAD_RETRY_CATEGORY"; + public static final String UPLOAD_RETRY_TITLE = "UPLOAD_RETRY_TITLE"; + public static final String UPLOAD_RETRY_DESCRIPTION = "UPLOAD_RETRY_DESCRIPTION"; + public static final String UPLOAD_RETRY_ICON = "UPLOAD_RETRY_ICON"; + public static final String UPLOAD_RETRY_UPLOADER = "UPLOAD_RETRY_UPLOADER"; + public static final String UPLOAD_RETRY_FILE_URI = "UPLOAD_RETRY_FILE_URI";*/ + + private Storage storage; + private static AlertDialog uploadProgressDialog; + private static String dialogUploadID; + //private static Intent multipartUploadRetryIntent; + + @Override + public void onReceive(Context context, Intent intent) { + String intentAction = intent.getAction(); + Bundle intentBundle = intent.getExtras(); + if (intentAction == null || intentBundle == null) { + super.onReceive(context, intent); + return; + } + + switch (intentAction) { + case ACTION_CANCEL_UPLOAD: + String uploadID = intentBundle.getString(UPLOAD_ID_KEY); + UploadService.stopUpload(uploadID); + break; + /*case ACTION_RETRY_UPLOAD: + String retryFilename = intentBundle.getString(UPLOAD_RETRY_FILENAME); + String retryCategory = intentBundle.getString(UPLOAD_RETRY_CATEGORY); + String retryTitleText = intentBundle.getString(UPLOAD_RETRY_TITLE); + String retryDescription = intentBundle.getString(UPLOAD_RETRY_DESCRIPTION); + String retryIcon = intentBundle.getString(UPLOAD_RETRY_ICON); + String retryUploaderProfile = intentBundle.getString(UPLOAD_RETRY_UPLOADER); + Uri retryFileUri = (Uri) intentBundle.get(UPLOAD_RETRY_FILE_URI); + String retryUploadID = UUID.randomUUID().toString(); + + UploadActivity.uploadFile(context, retryUploadID, + UploadActivity.getConfigForUpload(context, retryUploadID, retryFilename, retryCategory, + retryTitleText, retryDescription, retryIcon, retryUploaderProfile, retryFileUri), + retryCategory, retryTitleText, retryDescription, retryIcon, + retryUploaderProfile, retryFileUri); + + break;*/ + default: + super.onReceive(context, intent); + break; + } + } + + @Override + public void onProgress(Context context, UploadInfo uploadInfo) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP && + uploadInfo.getUploadId().equals(dialogUploadID) && + uploadProgressDialog != null) { + Button alertDialogNeutral = uploadProgressDialog.getButton(AlertDialog.BUTTON_NEUTRAL); + alertDialogNeutral.setText("Resume on background"); + alertDialogNeutral.setOnClickListener(v -> uploadProgressDialog.dismiss()); + + Button alertDialogNegative = uploadProgressDialog.getButton(AlertDialog.BUTTON_NEGATIVE); + alertDialogNegative.setText("Cancel"); + alertDialogNegative.setOnClickListener(v -> { + UploadService.stopUpload(dialogUploadID); + uploadProgressDialog.dismiss(); + }); + + if (uploadProgressDialog.isShowing()) { + Window progressWindow = uploadProgressDialog.getWindow(); + if (progressWindow != null) { + MaterialProgressBar dialogProgressBar = progressWindow.findViewById(R.id.dialogProgressBar); + TextView dialogProgressText = progressWindow.findViewById(R.id.dialog_upload_progress_text); + + dialogProgressBar.setProgress(uploadInfo.getProgressPercent()); + dialogProgressText.setText(context.getResources().getString( + R.string.upload_progress_dialog_bytes_uploaded, + (float) uploadInfo.getUploadRate(), + (int) uploadInfo.getUploadedBytes() / 1000, + (int) uploadInfo.getTotalBytes() / 1000)); + } + + if (uploadInfo.getUploadedBytes() == uploadInfo.getTotalBytes()) { + uploadProgressDialog.dismiss(); + } + } + } + } + + @Override + public void onError(Context context, UploadInfo uploadInfo, ServerResponse serverResponse, + Exception exception) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP && + uploadInfo.getUploadId().equals(dialogUploadID) && + uploadProgressDialog != null) { + /*Button alertDialogNeutral = uploadProgressDialog.getButton(AlertDialog.BUTTON_NEUTRAL); + alertDialogNeutral.setText("Retry"); + alertDialogNeutral.setOnClickListener(v -> { + if (multipartUploadRetryIntent != null) { + context.sendBroadcast(multipartUploadRetryIntent); + } + uploadProgressDialog.dismiss(); + });*/ + + Button alertDialogNegative = uploadProgressDialog.getButton(AlertDialog.BUTTON_NEGATIVE); + alertDialogNegative.setText("Cancel"); + alertDialogNegative.setOnClickListener(v -> { + NotificationManager notificationManager = (NotificationManager) context.getApplicationContext(). + getSystemService(Context.NOTIFICATION_SERVICE); + if (notificationManager != null) { + notificationManager.cancel(uploadInfo.getNotificationID()); + } + UploadsHelper.deleteTempFiles(storage); + uploadProgressDialog.dismiss(); + }); + + if (uploadProgressDialog.isShowing()) { + Window progressWindow = uploadProgressDialog.getWindow(); + if (progressWindow != null) { + MaterialProgressBar dialogProgressBar = progressWindow.findViewById(R.id.dialogProgressBar); + TextView dialogProgressText = progressWindow.findViewById(R.id.dialog_upload_progress_text); + + dialogProgressBar.setVisibility(View.GONE); + dialogProgressText.setText("Upload failed."); + } + + if (uploadInfo.getUploadedBytes() == uploadInfo.getTotalBytes()) { + uploadProgressDialog.dismiss(); + } + } + } else { + NotificationManager notificationManager = (NotificationManager) context.getApplicationContext(). + getSystemService(Context.NOTIFICATION_SERVICE); + if (notificationManager != null) { + notificationManager.cancel(uploadInfo.getNotificationID()); + } + + Intent combinedActionsIntent = new Intent(UploadsReceiver.ACTION_COMBINED_UPLOAD); + combinedActionsIntent.putExtra(UploadsReceiver.UPLOAD_ID_KEY, uploadInfo.getUploadId()); + context.sendBroadcast(combinedActionsIntent); + } + + Toast.makeText(context.getApplicationContext(), "Upload failed", Toast.LENGTH_SHORT).show(); + if (storage == null) { + storage = new Storage(context.getApplicationContext()); + } + } + + @Override + public void onCompleted(Context context, UploadInfo uploadInfo, ServerResponse serverResponse) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + uploadProgressDialog = null; + dialogUploadID = null; + } + + Toast.makeText(context.getApplicationContext(), "Upload completed successfully", Toast.LENGTH_SHORT).show(); + if (storage == null) { + storage = new Storage(context.getApplicationContext()); + } + + BaseApplication.getInstance().logFirebaseAnalyticsEvent("file_upload", null); + UploadsHelper.deleteTempFiles(storage); + } + + @Override + public void onCancelled(Context context, UploadInfo uploadInfo) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + uploadProgressDialog = null; + dialogUploadID = null; + } + + Toast.makeText(context.getApplicationContext(), "Upload canceled", Toast.LENGTH_SHORT).show(); + if (storage == null) { + storage = new Storage(context.getApplicationContext()); + } + + /*NotificationManager notificationManager = (NotificationManager) context.getApplicationContext(). + getSystemService(Context.NOTIFICATION_SERVICE); + if (notificationManager != null) { + notificationManager.cancel(uploadInfo.getNotificationID()); + }*/ + UploadsHelper.deleteTempFiles(storage); + } + + public static void setDialogDisplay(AlertDialog uploadProgressDialog, String dialogUploadID, + Intent multipartUploadRetryIntent) { + UploadsReceiver.uploadProgressDialog = uploadProgressDialog; + UploadsReceiver.dialogUploadID = dialogUploadID; + //UploadsReceiver.multipartUploadRetryIntent = multipartUploadRetryIntent; + } +} \ No newline at end of file diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/FileUtils.java b/app/src/main/java/gr/thmmy/mthmmy/utils/FileUtils.java index 1c8eb0b0..7beffeb6 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/utils/FileUtils.java +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/FileUtils.java @@ -1,10 +1,16 @@ package gr.thmmy.mthmmy.utils; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.provider.OpenableColumns; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import android.webkit.MimeTypeMap; import java.io.File; -import androidx.annotation.NonNull; +import gr.thmmy.mthmmy.R; import static gr.thmmy.mthmmy.services.DownloadHelper.SAVE_DIR; @@ -21,7 +27,94 @@ public class FileUtils { return type; } - public static boolean fileNameExists (String fileName) { + public static boolean fileNameExists(String fileName) { return fileName != null && (new File(SAVE_DIR.getAbsolutePath(), fileName)).isFile(); } + + @Nullable + public static String getFileExtension(@NonNull String filename) { + String fileExtension; + + if (!filename.contains(".")) { + return null; + } + if (filename.toLowerCase().endsWith(".tar.gz")) { + fileExtension = filename.substring(filename.length() - 7); + } else { + fileExtension = filename.substring(filename.lastIndexOf(".")); + } + + return fileExtension; + } + + public static String getFilenameWithoutExtension(String filename) { + String fileExtension = getFileExtension(filename); + + return fileExtension == null + ? filename + : filename.substring(0, filename.indexOf(fileExtension)); + } + + @NonNull + public static String filenameFromUri(Context context, Uri uri) { + String filename = null; + if (uri.getScheme().equals("content")) { + try (Cursor cursor = context.getContentResolver().query(uri, null, null, null, null)) { + if (cursor != null && cursor.moveToFirst()) { + filename = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); + } + } + } + if (filename == null) { + filename = uri.getPath(); + int cut = filename.lastIndexOf('/'); + if (cut != -1) { + filename = filename.substring(cut + 1); + } + } + + return filename; + } + + public static long sizeFromUri(Context context, @NonNull Uri uri) { + try (Cursor cursor = context.getContentResolver().query(uri, null, null, null, null)) { + if (cursor != null && cursor.moveToFirst()) { + return cursor.getLong(cursor.getColumnIndex(OpenableColumns.SIZE)); + } + } + return -1; + } + + /** + * Returns a String with a single FontAwesome typeface character corresponding to this file's + * extension. + * + * @param filename String with filename containing file's extension + * @return FontAwesome character according to file's type + * @see FontAwesome + */ + @NonNull + public static String faIconFromFilename(Context context, String filename) { + filename = filename.toLowerCase(); + + if (filename.contains("jpg") || filename.contains("gif") || filename.contains("jpeg") + || filename.contains("png")) + return context.getResources().getString(R.string.fa_file_image_o); + else if (filename.contains("pdf")) + return context.getResources().getString(R.string.fa_file_pdf_o); + else if (filename.contains("zip") || filename.contains("rar") || filename.contains("tar.gz")) + return context.getResources().getString(R.string.fa_file_zip_o); + else if (filename.contains("txt")) + return context.getResources().getString(R.string.fa_file_text_o); + else if (filename.contains("doc") || filename.contains("docx")) + return context.getResources().getString(R.string.fa_file_word_o); + else if (filename.contains("xls") || filename.contains("xlsx")) + return context.getResources().getString(R.string.fa_file_excel_o); + else if (filename.contains("pps")) + return context.getResources().getString(R.string.fa_file_powerpoint_o); + else if (filename.contains("mpg")) + return context.getResources().getString(R.string.fa_file_video_o); + + return context.getResources().getString(R.string.fa_file); + } } diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/ScrollAwareFABBehavior.java b/app/src/main/java/gr/thmmy/mthmmy/utils/ScrollAwareFABBehavior.java index a45e1a3f..06a355f2 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/utils/ScrollAwareFABBehavior.java +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/ScrollAwareFABBehavior.java @@ -2,15 +2,16 @@ package gr.thmmy.mthmmy.utils; import android.content.Context; import android.util.AttributeSet; +import android.util.Log; import android.view.View; -import com.google.android.material.floatingactionbutton.FloatingActionButton; -import com.google.android.material.snackbar.Snackbar; - import androidx.annotation.NonNull; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.core.view.ViewCompat; +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.google.android.material.snackbar.Snackbar; + /** * Extends FloatingActionButton's behavior so the button will hide when scrolling down and show * otherwise. It also lifts the {@link FloatingActionButton} when a {@link Snackbar} is shown. @@ -37,7 +38,9 @@ public class ScrollAwareFABBehavior extends CoordinatorLayout.Behavior 0 || (!target.canScrollVertically(-1) && dyConsumed == 0 && dyUnconsumed > 50)) { + Log.d("THISSSS", "GOT_HIDE"); child.hide(new FloatingActionButton.OnVisibilityChangedListener() { @Override public void onHidden(FloatingActionButton fab) { @@ -47,7 +50,14 @@ public class ScrollAwareFABBehavior extends CoordinatorLayout.Behavior resInfoList = context.getPackageManager(). + queryIntentActivities(takePictureIntent, PackageManager.MATCH_DEFAULT_ONLY); + for (ResolveInfo resolveInfo : resInfoList) { + String packageName = resolveInfo.activityInfo.packageName; + context.grantUriPermission(packageName, photoURI, + Intent.FLAG_GRANT_WRITE_URI_PERMISSION | + Intent.FLAG_GRANT_READ_URI_PERMISSION); + } + return takePictureIntent; + } + return null; + } + + public static Uri processResult(Context context, File photoFile) { + Bitmap bitmap; + Uri fileUri = FileProvider.getUriForFile(context, context.getPackageName() + ".provider", photoFile); + + bitmap = getImageResized(context, fileUri); + int rotation = getRotation(context, fileUri); + bitmap = rotate(bitmap, rotation); + + try { + FileOutputStream out = new FileOutputStream(photoFile); + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out); + out.flush(); + out.close(); + } catch (Exception e) { + e.printStackTrace(); + } + + return fileUri; + } + + @Nullable + public static File createImageFile(Context context) { + // Create an image file name + String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.FRANCE).format(new Date()); + String imageFileName = "mThmmy_" + timeStamp + ".jpg"; + + File imageFolder = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM) + + File.separator + "mThmmy"); + + if (!imageFolder.exists()) { + if (!imageFolder.mkdirs()) { + Timber.w("Photos folder build returned false in %s", TakePhoto.class.getSimpleName()); + Toast.makeText(context, "Couldn't create photos directory", Toast.LENGTH_SHORT).show(); + return null; + } + } + + return new File(imageFolder, imageFileName); + } + + public static void galleryAddPic(Context context, File photoFile) { + ContentValues values = new ContentValues(); + values.put(MediaStore.Images.Media.TITLE, photoFile.getName()); + values.put(MediaStore.Images.Media.DESCRIPTION, IMAGE_CONTENT_DESCRIPTION); + values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis()); + values.put(MediaStore.Images.ImageColumns.BUCKET_ID, photoFile.toString().toLowerCase(Locale.US).hashCode()); + values.put(MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME, photoFile.getName().toLowerCase(Locale.US)); + values.put("_data", photoFile.getAbsolutePath()); + + ContentResolver cr = context.getContentResolver(); + cr.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); + } + + private static Bitmap getImageResized(Context context, Uri selectedImage) { + Bitmap bm; + int[] sampleSizes = new int[]{5, 3, 2, 1}; + int i = 0; + do { + bm = decodeBitmap(context, selectedImage, sampleSizes[i]); + i++; + } while (bm.getWidth() < DEFAULT_MIN_WIDTH_QUALITY && i < sampleSizes.length); + return bm; + } + + private static Bitmap decodeBitmap(Context context, Uri theUri, int sampleSize) { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inSampleSize = sampleSize; + + AssetFileDescriptor fileDescriptor = null; + try { + fileDescriptor = context.getContentResolver().openAssetFileDescriptor(theUri, "r"); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + + assert fileDescriptor != null; + return BitmapFactory.decodeFileDescriptor( + fileDescriptor.getFileDescriptor(), null, options); + } + + private static int getRotation(Context context, Uri imageUri) { + int rotation = 0; + try { + + context.getContentResolver().notifyChange(imageUri, null); + ExifInterface exif = new ExifInterface(imageUri.getPath()); + int orientation = exif.getAttributeInt( + ExifInterface.TAG_ORIENTATION, + ExifInterface.ORIENTATION_NORMAL); + + switch (orientation) { + case ExifInterface.ORIENTATION_ROTATE_270: + rotation = 270; + break; + case ExifInterface.ORIENTATION_ROTATE_180: + rotation = 180; + break; + case ExifInterface.ORIENTATION_ROTATE_90: + rotation = 90; + break; + } + } catch (Exception e) { + e.printStackTrace(); + } + return rotation; + } + + private static Bitmap rotate(Bitmap bm, int rotation) { + if (rotation != 0) { + Matrix matrix = new Matrix(); + matrix.postRotate(rotation); + return Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), matrix, true); + } + return bm; + } +} diff --git a/app/src/main/res/drawable/ic_attach_file_white_24dp.xml b/app/src/main/res/drawable/ic_attach_file_white_24dp.xml deleted file mode 100644 index 4834305b..00000000 --- a/app/src/main/res/drawable/ic_attach_file_white_24dp.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_cached_accent_24dp.xml b/app/src/main/res/drawable/ic_cached_accent_24dp.xml new file mode 100644 index 00000000..b4f3f7dd --- /dev/null +++ b/app/src/main/res/drawable/ic_cached_accent_24dp.xml @@ -0,0 +1,4 @@ + + + + diff --git a/app/src/main/res/drawable/ic_cancel_accent_24dp.xml b/app/src/main/res/drawable/ic_cancel_accent_24dp.xml new file mode 100644 index 00000000..4c1d6837 --- /dev/null +++ b/app/src/main/res/drawable/ic_cancel_accent_24dp.xml @@ -0,0 +1,4 @@ + + + + diff --git a/app/src/main/res/drawable/ic_info_outline_warning_24dp.xml b/app/src/main/res/drawable/ic_info_outline_warning_24dp.xml new file mode 100644 index 00000000..1c3b206c --- /dev/null +++ b/app/src/main/res/drawable/ic_info_outline_warning_24dp.xml @@ -0,0 +1,4 @@ + + + + diff --git a/app/src/main/res/layout/activity_board.xml b/app/src/main/res/layout/activity_board.xml index 9a51aa92..5ac81b3a 100644 --- a/app/src/main/res/layout/activity_board.xml +++ b/app/src/main/res/layout/activity_board.xml @@ -1,6 +1,5 @@ - + app:srcCompat="@drawable/ic_bookmark_false_accent_24dp" /> @@ -43,8 +42,8 @@ android:layout_marginTop="64dp" android:background="@color/background" android:scrollbars="none" - tools:context="gr.thmmy.mthmmy.activities.topic.TopicActivity"> - + app:layout_behavior="@string/appbar_scrolling_view_behavior" + tools:context="gr.thmmy.mthmmy.activities.topic.TopicActivity" /> + app:mpb_progressStyle="horizontal" /> + app:srcCompat="@drawable/ic_add_fab" /> - - diff --git a/app/src/main/res/layout/activity_downloads.xml b/app/src/main/res/layout/activity_downloads.xml index dc92a274..97e0afbc 100644 --- a/app/src/main/res/layout/activity_downloads.xml +++ b/app/src/main/res/layout/activity_downloads.xml @@ -48,7 +48,7 @@ app:mpb_indeterminateTint="@color/accent" app:mpb_progressStyle="horizontal"/> - + app:srcCompat="@drawable/ic_file_upload_white_24dp"/> diff --git a/app/src/main/res/layout/activity_topic.xml b/app/src/main/res/layout/activity_topic.xml index aaa13e4b..b61c41cf 100644 --- a/app/src/main/res/layout/activity_topic.xml +++ b/app/src/main/res/layout/activity_topic.xml @@ -45,8 +45,8 @@ android:layout_below="@id/appbar" android:layout_gravity="top|start" android:clipToPadding="false" - android:paddingBottom="4dp" android:paddingTop="4dp" + android:paddingBottom="4dp" android:scrollbars="none" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:context="gr.thmmy.mthmmy.activities.topic.TopicActivity" /> @@ -146,8 +146,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" - android:layout_marginBottom="50dp" android:layout_marginEnd="@dimen/fab_margins" + android:layout_marginBottom="50dp" app:layout_behavior="gr.thmmy.mthmmy.utils.ScrollAwareFABBehavior" app:srcCompat="@drawable/ic_reply" /> diff --git a/app/src/main/res/layout/activity_upload.xml b/app/src/main/res/layout/activity_upload.xml index 8639fd8c..fb985454 100644 --- a/app/src/main/res/layout/activity_upload.xml +++ b/app/src/main/res/layout/activity_upload.xml @@ -28,16 +28,16 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="top|start" - android:background="@color/background" - android:paddingEnd="@dimen/activity_horizontal_margin" + android:background="@color/primary_light" android:paddingStart="@dimen/activity_horizontal_margin" + android:paddingEnd="@dimen/activity_horizontal_margin" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:context="gr.thmmy.mthmmy.activities.upload.UploadActivity"> @@ -45,15 +45,16 @@ android:id="@+id/upload_spinners" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="@color/background" + android:layout_marginTop="8dp" + android:background="@color/primary_light" android:orientation="vertical"> @@ -61,8 +62,8 @@ + android:layout_marginTop="6dp" + android:layout_marginBottom="6dp"> + + + + + + + + + + + android:layout_marginTop="6dp" + android:layout_marginBottom="6dp"> - - - + + @@ -165,8 +193,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" - android:layout_marginBottom="@dimen/fab_margins" android:layout_marginEnd="@dimen/fab_margins" + android:layout_marginBottom="@dimen/fab_margins" app:layout_behavior="gr.thmmy.mthmmy.utils.ScrollAwareFABBehavior" app:srcCompat="@drawable/ic_file_upload_white_24dp" /> diff --git a/app/src/main/res/layout/activity_upload_fields_builder.xml b/app/src/main/res/layout/activity_upload_fields_builder.xml index 0583b601..601cdeeb 100644 --- a/app/src/main/res/layout/activity_upload_fields_builder.xml +++ b/app/src/main/res/layout/activity_upload_fields_builder.xml @@ -27,7 +27,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="top|start" - android:background="@color/background" + android:background="@color/primary_light" android:paddingEnd="@dimen/activity_horizontal_margin" android:paddingStart="@dimen/activity_horizontal_margin" app:layout_behavior="@string/appbar_scrolling_view_behavior" diff --git a/app/src/main/res/layout/activity_upload_file_list_row.xml b/app/src/main/res/layout/activity_upload_file_list_row.xml new file mode 100644 index 00000000..08b74a4a --- /dev/null +++ b/app/src/main/res/layout/activity_upload_file_list_row.xml @@ -0,0 +1,24 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_upload_filename_info_popup.xml b/app/src/main/res/layout/activity_upload_filename_info_popup.xml new file mode 100644 index 00000000..bb6a9704 --- /dev/null +++ b/app/src/main/res/layout/activity_upload_filename_info_popup.xml @@ -0,0 +1,10 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_upload_progress.xml b/app/src/main/res/layout/dialog_upload_progress.xml new file mode 100644 index 00000000..18444cb4 --- /dev/null +++ b/app/src/main/res/layout/dialog_upload_progress.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/editor_view_color_picker.xml b/app/src/main/res/layout/editor_view_color_picker.xml index b71214e1..be1ce295 100644 --- a/app/src/main/res/layout/editor_view_color_picker.xml +++ b/app/src/main/res/layout/editor_view_color_picker.xml @@ -1,11 +1,10 @@ - + android:layout_height="wrap_content" + android:background="@color/card_background"> - @@ -14,84 +13,84 @@ android:id="@+id/black" style="@style/PopupMenuItem.TopItem" android:text="@string/black" - android:textColor="@color/black"/> + android:textColor="@color/black" /> + android:textColor="@color/red" /> + android:textColor="@color/yellow" /> + android:textColor="@color/pink" /> + android:textColor="@color/green" /> + android:textColor="@color/orange" /> + android:textColor="@color/purple" /> + android:textColor="@color/blue" /> + android:textColor="@color/beige" /> + android:textColor="@color/brown" /> + android:textColor="@color/teal" /> + android:textColor="@color/navy" /> + android:textColor="@color/maroon" /> + android:textColor="@color/lime_green" /> \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index f2209027..6a76e274 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -18,6 +18,7 @@ #8B8B8B #FF9800 #FAA61A + #890d0d #FFFFFF #CCCCCC diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bf3d9e5f..5d856f19 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -132,12 +132,20 @@ Upload - Generate title and description + Generate fields Title + Upload as (filename) Description - Select file + Add files Take photo Select a category + Please follow the filename rules as\ndescribed + in this topic.\n + \nThis does not rename your local files. + Uploading + Uploading: %1$.2f Kbit/s, %2$d/%3$d KBytes + "Cancel" + "Retry" Select type of upload diff --git a/app/src/main/res/xml/app_preferences_user.xml b/app/src/main/res/xml/app_preferences_user.xml index 2599df44..0ea00d34 100644 --- a/app/src/main/res/xml/app_preferences_user.xml +++ b/app/src/main/res/xml/app_preferences_user.xml @@ -49,7 +49,7 @@ app:iconSpaceReserved="false" /> - +