diff --git a/.idea/.gitignore b/.idea/.gitignore index 26d3352..ca5bdd6 100644 --- a/.idea/.gitignore +++ b/.idea/.gitignore @@ -1,3 +1,4 @@ # Default ignored files /shelf/ /workspace.xml +/other.xml diff --git a/Gemfile b/Gemfile deleted file mode 100644 index adc90d9..0000000 --- a/Gemfile +++ /dev/null @@ -1,3 +0,0 @@ -source "https://rubygems.org" - -gem "fastlane" \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index c1b55f5..0000000 --- a/Gemfile.lock +++ /dev/null @@ -1,218 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - CFPropertyList (3.0.6) - rexml - addressable (2.8.5) - public_suffix (>= 2.0.2, < 6.0) - artifactory (3.0.15) - atomos (0.1.3) - aws-eventstream (1.2.0) - aws-partitions (1.804.0) - aws-sdk-core (3.180.3) - aws-eventstream (~> 1, >= 1.0.2) - aws-partitions (~> 1, >= 1.651.0) - aws-sigv4 (~> 1.5) - jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.71.0) - aws-sdk-core (~> 3, >= 3.177.0) - aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.132.1) - aws-sdk-core (~> 3, >= 3.179.0) - aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.6) - aws-sigv4 (1.6.0) - aws-eventstream (~> 1, >= 1.0.2) - babosa (1.0.4) - claide (1.1.0) - colored (1.2) - colored2 (3.1.2) - commander (4.6.0) - highline (~> 2.0.0) - declarative (0.0.20) - digest-crc (0.6.5) - rake (>= 12.0.0, < 14.0.0) - domain_name (0.5.20190701) - unf (>= 0.0.5, < 1.0.0) - dotenv (2.8.1) - emoji_regex (3.2.3) - excon (0.100.0) - faraday (1.10.3) - faraday-em_http (~> 1.0) - faraday-em_synchrony (~> 1.0) - faraday-excon (~> 1.1) - faraday-httpclient (~> 1.0) - faraday-multipart (~> 1.0) - faraday-net_http (~> 1.0) - faraday-net_http_persistent (~> 1.0) - faraday-patron (~> 1.0) - faraday-rack (~> 1.0) - faraday-retry (~> 1.0) - ruby2_keywords (>= 0.0.4) - faraday-cookie_jar (0.0.7) - faraday (>= 0.8.0) - http-cookie (~> 1.0.0) - faraday-em_http (1.0.0) - faraday-em_synchrony (1.0.0) - faraday-excon (1.1.0) - faraday-httpclient (1.0.1) - faraday-multipart (1.0.4) - multipart-post (~> 2) - faraday-net_http (1.0.1) - faraday-net_http_persistent (1.2.0) - faraday-patron (1.0.0) - faraday-rack (1.0.0) - faraday-retry (1.0.3) - faraday_middleware (1.2.0) - faraday (~> 1.0) - fastimage (2.2.7) - fastlane (2.214.0) - CFPropertyList (>= 2.3, < 4.0.0) - addressable (>= 2.8, < 3.0.0) - artifactory (~> 3.0) - aws-sdk-s3 (~> 1.0) - babosa (>= 1.0.3, < 2.0.0) - bundler (>= 1.12.0, < 3.0.0) - colored - commander (~> 4.6) - dotenv (>= 2.1.1, < 3.0.0) - emoji_regex (>= 0.1, < 4.0) - excon (>= 0.71.0, < 1.0.0) - faraday (~> 1.0) - faraday-cookie_jar (~> 0.0.6) - faraday_middleware (~> 1.0) - fastimage (>= 2.1.0, < 3.0.0) - gh_inspector (>= 1.1.2, < 2.0.0) - google-apis-androidpublisher_v3 (~> 0.3) - google-apis-playcustomapp_v1 (~> 0.1) - google-cloud-storage (~> 1.31) - highline (~> 2.0) - json (< 3.0.0) - jwt (>= 2.1.0, < 3) - mini_magick (>= 4.9.4, < 5.0.0) - multipart-post (>= 2.0.0, < 3.0.0) - naturally (~> 2.2) - optparse (~> 0.1.1) - plist (>= 3.1.0, < 4.0.0) - rubyzip (>= 2.0.0, < 3.0.0) - security (= 0.1.3) - simctl (~> 1.6.3) - terminal-notifier (>= 2.0.0, < 3.0.0) - terminal-table (>= 1.4.5, < 2.0.0) - tty-screen (>= 0.6.3, < 1.0.0) - tty-spinner (>= 0.8.0, < 1.0.0) - word_wrap (~> 1.0.0) - xcodeproj (>= 1.13.0, < 2.0.0) - xcpretty (~> 0.3.0) - xcpretty-travis-formatter (>= 0.0.3) - gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.48.0) - google-apis-core (>= 0.11.0, < 2.a) - google-apis-core (0.11.1) - addressable (~> 2.5, >= 2.5.1) - googleauth (>= 0.16.2, < 2.a) - httpclient (>= 2.8.1, < 3.a) - mini_mime (~> 1.0) - representable (~> 3.0) - retriable (>= 2.0, < 4.a) - rexml - webrick - google-apis-iamcredentials_v1 (0.17.0) - google-apis-core (>= 0.11.0, < 2.a) - google-apis-playcustomapp_v1 (0.13.0) - google-apis-core (>= 0.11.0, < 2.a) - google-apis-storage_v1 (0.19.0) - google-apis-core (>= 0.9.0, < 2.a) - google-cloud-core (1.6.0) - google-cloud-env (~> 1.0) - google-cloud-errors (~> 1.0) - google-cloud-env (1.6.0) - faraday (>= 0.17.3, < 3.0) - google-cloud-errors (1.3.1) - google-cloud-storage (1.44.0) - addressable (~> 2.8) - digest-crc (~> 0.4) - google-apis-iamcredentials_v1 (~> 0.1) - google-apis-storage_v1 (~> 0.19.0) - google-cloud-core (~> 1.6) - googleauth (>= 0.16.2, < 2.a) - mini_mime (~> 1.0) - googleauth (1.7.0) - faraday (>= 0.17.3, < 3.a) - jwt (>= 1.4, < 3.0) - memoist (~> 0.16) - multi_json (~> 1.11) - os (>= 0.9, < 2.0) - signet (>= 0.16, < 2.a) - highline (2.0.3) - http-cookie (1.0.5) - domain_name (~> 0.5) - httpclient (2.8.3) - jmespath (1.6.2) - json (2.6.3) - jwt (2.7.1) - memoist (0.16.2) - mini_magick (4.12.0) - mini_mime (1.1.5) - multi_json (1.15.0) - multipart-post (2.3.0) - nanaimo (0.3.0) - naturally (2.2.1) - optparse (0.1.1) - os (1.1.4) - plist (3.7.0) - public_suffix (5.0.3) - rake (13.0.6) - representable (3.2.0) - declarative (< 0.1.0) - trailblazer-option (>= 0.1.1, < 0.2.0) - uber (< 0.2.0) - retriable (3.1.2) - rexml (3.2.6) - rouge (2.0.7) - ruby2_keywords (0.0.5) - rubyzip (2.3.2) - security (0.1.3) - signet (0.17.0) - addressable (~> 2.8) - faraday (>= 0.17.5, < 3.a) - jwt (>= 1.5, < 3.0) - multi_json (~> 1.10) - simctl (1.6.10) - CFPropertyList - naturally - terminal-notifier (2.0.0) - terminal-table (1.8.0) - unicode-display_width (~> 1.1, >= 1.1.1) - trailblazer-option (0.1.2) - tty-cursor (0.7.1) - tty-screen (0.8.1) - tty-spinner (0.9.3) - tty-cursor (~> 0.7) - uber (0.1.0) - unf (0.1.4) - unf_ext - unf_ext (0.0.8.2) - unicode-display_width (1.8.0) - webrick (1.8.1) - word_wrap (1.0.0) - xcodeproj (1.22.0) - CFPropertyList (>= 2.3.3, < 4.0) - atomos (~> 0.1.3) - claide (>= 1.0.2, < 2.0) - colored2 (~> 3.1) - nanaimo (~> 0.3.0) - rexml (~> 3.2.4) - xcpretty (0.3.0) - rouge (~> 2.0.7) - xcpretty-travis-formatter (1.0.1) - xcpretty (~> 0.2, >= 0.0.7) - -PLATFORMS - x64-mingw-ucrt - -DEPENDENCIES - fastlane - -BUNDLED WITH - 2.4.18 diff --git a/app/build.gradle b/app/build.gradle index f3ae38a..2039edb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { applicationId "se.arctosoft.vault" minSdk 28 targetSdk 34 - versionCode 27 - versionName "1.9.0" + versionCode 28 + versionName "1.10.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -43,21 +43,21 @@ android { } dependencies { - implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.7.0' - implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0' + implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.8.4' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.4' testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.5' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + androidTestImplementation 'androidx.test.ext:junit:1.2.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' - implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'com.google.android.material:material:1.11.0' + implementation 'androidx.appcompat:appcompat:1.7.0' + implementation 'com.google.android.material:material:1.12.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.security:security-crypto:1.0.0' - implementation 'androidx.media3:media3-exoplayer:1.3.1' - implementation 'androidx.media3:media3-ui:1.3.1' + implementation 'androidx.media3:media3-exoplayer:1.4.0' + implementation 'androidx.media3:media3-ui:1.4.0' - implementation 'com.github.bumptech.glide:glide:4.15.1' - annotationProcessor 'com.github.bumptech.glide:compiler:4.15.1' + implementation 'com.github.bumptech.glide:glide:4.16.0' + annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0' implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0' implementation 'com.mikepenz:aboutlibraries-core-android:10.10.0' implementation('com.mikepenz:aboutlibraries:10.10.0') { diff --git a/app/src/main/java/se/arctosoft/vault/GalleryActivity.java b/app/src/main/java/se/arctosoft/vault/GalleryActivity.java index 3890676..63c6503 100644 --- a/app/src/main/java/se/arctosoft/vault/GalleryActivity.java +++ b/app/src/main/java/se/arctosoft/vault/GalleryActivity.java @@ -203,9 +203,38 @@ private void setClickListeners() { FileStuff.pickVideoFiles(activityLauncher, result -> onImportImagesOrVideos(result.getData())); showImportOverlay(false); }); + binding.btnImportTextWrite.setOnClickListener(v -> { + Dialogs.showImportTextDialog(this, null, false, text -> { + if (text != null && !text.isBlank()) { + importText(text); + } + }); + showImportOverlay(false); + }); binding.importChooseOverlay.setOnClickListener(v -> showImportOverlay(false)); } + private void importText(@NonNull String text) { + Dialogs.showImportTextChooseDestinationDialog(this, settings, new Dialogs.IOnDirectorySelected() { + @Override + public void onDirectorySelected(@NonNull DocumentFile directory, boolean deleteOriginal) { + DocumentFile createdFile = Encryption.importTextToDirectory(GalleryActivity.this, text, null, directory, settings); + if (createdFile != null) { + Toaster.getInstance(GalleryActivity.this).showLong(getString(R.string.gallery_importing_done, 1)); + } else { + Toaster.getInstance(GalleryActivity.this).showLong(getString(R.string.gallery_importing_error)); + } + } + + @Override + public void onOtherDirectory() { + viewModel.setTextToImport(text); + binding.btnAddFolder.performClick(); + } + }); + + } + private void onImportImagesOrVideos(@Nullable Intent data) { if (data != null) { List documentFiles = FileStuff.getDocumentsFromDirectoryResult(this, data); @@ -245,6 +274,9 @@ public void onAlreadyExists(boolean isRootDir) { if (viewModel.getFilesToAdd() != null) { importFiles(viewModel.getFilesToAdd()); } + if (viewModel.getTextToImport() != null) { + importText(viewModel.getTextToImport()); + } } } else if (result.getResultCode() == Activity.RESULT_CANCELED) { viewModel.setFilesToAdd(null); diff --git a/app/src/main/java/se/arctosoft/vault/GalleryDirectoryActivity.java b/app/src/main/java/se/arctosoft/vault/GalleryDirectoryActivity.java index fd69d19..4dc41bb 100644 --- a/app/src/main/java/se/arctosoft/vault/GalleryDirectoryActivity.java +++ b/app/src/main/java/se/arctosoft/vault/GalleryDirectoryActivity.java @@ -18,6 +18,7 @@ package se.arctosoft.vault; +import android.annotation.SuppressLint; import android.app.Activity; import android.content.Intent; import android.content.res.Configuration; @@ -42,6 +43,7 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; @@ -49,6 +51,7 @@ import se.arctosoft.vault.adapters.GalleryGridAdapter; import se.arctosoft.vault.adapters.GalleryPagerAdapter; +import se.arctosoft.vault.data.FileType; import se.arctosoft.vault.data.GalleryFile; import se.arctosoft.vault.databinding.ActivityGalleryDirectoryBinding; import se.arctosoft.vault.encryption.Encryption; @@ -68,6 +71,15 @@ public class GalleryDirectoryActivity extends BaseActivity { public static final String EXTRA_NESTED_PATH = "n"; public static final String EXTRA_IS_ALL = "a"; private static final int MIN_FILES_FOR_FAST_SCROLL = 60; + private static final int ORDER_BY_NEWEST = 0; + private static final int ORDER_BY_OLDEST = 1; + private static final int ORDER_BY_LARGEST = 2; + private static final int ORDER_BY_SMALLEST = 3; + private static final int FILTER_ALL = 0; + private static final int FILTER_IMAGES = FileType.IMAGE.i; + private static final int FILTER_GIFS = FileType.GIF.i; + private static final int FILTER_VIDEOS = FileType.VIDEO.i; + private static final int FILTER_TEXTS = FileType.TEXT.i; private ActivityGalleryDirectoryBinding binding; private GalleryDirectoryViewModel viewModel; @@ -84,6 +96,7 @@ public class GalleryDirectoryActivity extends BaseActivity { private boolean isAllFolder = false; private int foundFiles = 0, foundFolders = 0; + private int orderBy = ORDER_BY_NEWEST; @Override protected void onCreate(Bundle savedInstanceState) { @@ -194,6 +207,11 @@ public void onSelectionChanged(int selected) { binding.btnDeleteFiles.setText(getString(R.string.gallery_delete_selected_files, selected)); } + public void onItemChanged(int position) { + galleryGridAdapter.notifyItemChanged(position); + galleryPagerAdapter.notifyItemChanged(position); + } + private void deleteSelectedFiles() { setLoading(true); new Thread(() -> { @@ -565,6 +583,33 @@ public boolean onOptionsItemSelected(@NonNull MenuItem item) { } else if (id == R.id.move_selected) { moveSelected(); return true; + } else if (id == R.id.order_by_newest_first) { + orderBy(ORDER_BY_NEWEST); + return true; + } else if (id == R.id.order_by_oldest_first) { + orderBy(ORDER_BY_OLDEST); + return true; + } else if (id == R.id.order_by_largest_first) { + orderBy(ORDER_BY_LARGEST); + return true; + } else if (id == R.id.order_by_smallest_first) { + orderBy(ORDER_BY_SMALLEST); + return true; + } else if (id == R.id.filter_all) { + filterBy(FILTER_ALL); + return true; + } else if (id == R.id.filter_images) { + filterBy(FILTER_IMAGES); + return true; + } else if (id == R.id.filter_gifs) { + filterBy(FILTER_GIFS); + return true; + } else if (id == R.id.filter_videos) { + filterBy(FILTER_VIDEOS); + return true; + } else if (id == R.id.filter_text) { + filterBy(FILTER_TEXTS); + return true; } return super.onOptionsItemSelected(item); } @@ -575,6 +620,86 @@ private void lock() { startActivity(new Intent(this, LaunchActivity.class)); } + private void orderBy(int order) { + this.orderBy = order; + new Thread(() -> { + synchronized (LOCK) { + List galleryFiles = viewModel.getGalleryFiles(); + if (order == ORDER_BY_NEWEST) { + galleryFiles.sort((o1, o2) -> { + if (o1.getLastModified() > o2.getLastModified()) { + return -1; + } else if (o1.getLastModified() < o2.getLastModified()) { + return 1; + } + return 0; + }); + } else if (order == ORDER_BY_OLDEST) { + galleryFiles.sort((o1, o2) -> { + if (o1.getLastModified() > o2.getLastModified()) { + return 1; + } else if (o1.getLastModified() < o2.getLastModified()) { + return -1; + } + return 0; + }); + } else if (order == ORDER_BY_LARGEST) { + galleryFiles.sort((o1, o2) -> { + if (o1.getSize() > o2.getSize()) { + return -1; + } else if (o1.getSize() < o2.getSize()) { + return 1; + } + return 0; + }); + } else { + galleryFiles.sort((o1, o2) -> { + if (o1.getSize() > o2.getSize()) { + return 1; + } else if (o1.getSize() < o2.getSize()) { + return -1; + } + return 0; + }); + } + runOnUiThread(() -> { + int size = viewModel.getGalleryFiles().size(); + galleryGridAdapter.notifyItemRangeChanged(0, size); + galleryPagerAdapter.notifyItemRangeChanged(0, size); + }); + } + }).start(); + } + + @SuppressLint("NotifyDataSetChanged") + private void filterBy(int filter) { + new Thread(() -> { + synchronized (LOCK) { + List hiddenFiles = viewModel.getHiddenFiles(); + List galleryFiles = viewModel.getGalleryFiles(); + if (!hiddenFiles.isEmpty()) { + viewModel.getGalleryFiles().addAll(hiddenFiles); + hiddenFiles.clear(); + } + if (filter != FILTER_ALL) { + Iterator it = galleryFiles.iterator(); + while (it.hasNext()) { + GalleryFile f = it.next(); + if (!f.isDirectory() && f.getFileType().i != filter) { + it.remove(); + hiddenFiles.add(f); + } + } + runOnUiThread(() -> { + galleryGridAdapter.notifyDataSetChanged(); + galleryPagerAdapter.notifyDataSetChanged(); + }); + } + orderBy(this.orderBy); + } + }).start(); + } + private void exportSelected() { Dialogs.showConfirmationDialog(this, getString(R.string.dialog_export_selected_title), isAllFolder ? getString(R.string.dialog_export_selected_message_all_folder) : getString(R.string.dialog_export_selected_message, FileStuff.getFilenameWithPathFromUri(currentDirectory)), (dialog, which) -> { @@ -771,9 +896,13 @@ private void onCopyMoveDirectoryAdded(IOnDirectoryAdded iOnDirectoryAdded) { @Override public boolean onCreateOptionsMenu(@NonNull Menu menu) { getMenuInflater().inflate(R.menu.menu_gallery_directory, menu); + menu.findItem(R.id.order_by).setVisible(!inSelectionMode); + menu.findItem(R.id.filter).setVisible(!inSelectionMode); menu.findItem(R.id.toggle_filename).setVisible(!inSelectionMode); menu.findItem(R.id.select_all).setVisible(inSelectionMode); menu.findItem(R.id.export_selected).setVisible(inSelectionMode); + menu.findItem(R.id.copy_selected).setVisible(inSelectionMode); + menu.findItem(R.id.move_selected).setVisible(inSelectionMode); return super.onCreateOptionsMenu(menu); } diff --git a/app/src/main/java/se/arctosoft/vault/adapters/GalleryGridAdapter.java b/app/src/main/java/se/arctosoft/vault/adapters/GalleryGridAdapter.java index c0e01b3..99c750e 100644 --- a/app/src/main/java/se/arctosoft/vault/adapters/GalleryGridAdapter.java +++ b/app/src/main/java/se/arctosoft/vault/adapters/GalleryGridAdapter.java @@ -22,6 +22,7 @@ import android.content.Intent; import android.graphics.drawable.Drawable; import android.icu.text.SimpleDateFormat; +import android.net.Uri; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -41,6 +42,7 @@ import com.bumptech.glide.request.target.Target; import java.io.File; +import java.io.IOException; import java.lang.ref.WeakReference; import java.util.Date; import java.util.List; @@ -52,12 +54,15 @@ import se.arctosoft.vault.data.GalleryFile; import se.arctosoft.vault.data.UniqueLinkedList; import se.arctosoft.vault.databinding.AdapterGalleryGridItemBinding; +import se.arctosoft.vault.encryption.Encryption; import se.arctosoft.vault.exception.InvalidPasswordException; import se.arctosoft.vault.fastscroll.views.FastScrollRecyclerView; import se.arctosoft.vault.interfaces.IOnFileClicked; import se.arctosoft.vault.interfaces.IOnFileDeleted; import se.arctosoft.vault.interfaces.IOnSelectionModeChanged; +import se.arctosoft.vault.utils.FileStuff; import se.arctosoft.vault.utils.GlideStuff; +import se.arctosoft.vault.utils.Settings; import se.arctosoft.vault.utils.StringStuff; public class GalleryGridAdapter extends RecyclerView.Adapter implements IOnSelectionModeChanged, FastScrollRecyclerView.SectionedAdapter { @@ -68,6 +73,7 @@ public class GalleryGridAdapter extends RecyclerView.Adapter weakReference; private final List galleryFiles; private final UniqueLinkedList selectedFiles; + private final Settings settings; private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd MMM yyyy HH:mm:ss", Locale.ENGLISH); private boolean showFileNames; private IOnFileDeleted onFileDeleted; @@ -93,6 +99,7 @@ record Payload(int type) { public GalleryGridAdapter(FragmentActivity context, @NonNull List galleryFiles, boolean showFileNames, boolean isRootDir) { this.weakReference = new WeakReference<>(context); + this.settings = Settings.getInstance(context); this.galleryFiles = galleryFiles; this.showFileNames = showFileNames; this.selectedFiles = new UniqueLinkedList<>(); @@ -134,7 +141,7 @@ public void onBindViewHolder(@NonNull GalleryGridViewHolder holder, int position holder.binding.imgType.setVisibility(View.VISIBLE); holder.binding.imgType.setImageDrawable(ResourcesCompat.getDrawable(context.getResources(), galleryFile.isGif() ? R.drawable.ic_round_gif_24 : (galleryFile.isVideo() - ? R.drawable.ic_outline_video_file_24 : R.drawable.ic_round_folder_open_24), + ? R.drawable.ic_outline_video_file_24 : (galleryFile.isText() ? R.drawable.outline_text_snippet_24 : R.drawable.ic_round_folder_open_24)), context.getTheme())); } else { holder.binding.imgType.setVisibility(View.GONE); @@ -142,11 +149,15 @@ public void onBindViewHolder(@NonNull GalleryGridViewHolder holder, int position holder.binding.hasDescription.setVisibility(!isRootDir && galleryFile.hasNote() ? View.VISIBLE : View.GONE); holder.binding.imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); + holder.binding.textView.setVisibility(View.GONE); + holder.binding.textView.setText(null); if (galleryFile.isAllFolder()) { + holder.binding.imageView.setVisibility(View.VISIBLE); holder.binding.imageView.setImageDrawable(ResourcesCompat.getDrawable(context.getResources(), R.drawable.round_all_inclusive_24, context.getTheme())); holder.binding.imageView.setScaleType(ImageView.ScaleType.CENTER); holder.binding.txtName.setText(context.getString(R.string.gallery_all)); } else if (galleryFile.isDirectory()) { + holder.binding.imageView.setVisibility(View.VISIBLE); GalleryFile firstFile = galleryFile.getFirstFile(); if (firstFile != null) { Glide.with(context) @@ -155,7 +166,16 @@ public void onBindViewHolder(@NonNull GalleryGridViewHolder holder, int position .into(holder.binding.imageView); } holder.binding.txtName.setText(context.getString(R.string.gallery_adapter_folder_name, galleryFile.getNameWithPath(), galleryFile.getFileCount())); + } else if (galleryFile.isText()) { + holder.binding.imageView.setVisibility(View.GONE); + holder.binding.textView.setText(galleryFile.getText() == null ? context.getString(R.string.loading) : galleryFile.getText()); + holder.binding.textView.setVisibility(View.VISIBLE); + setItemFilename(holder, context, galleryFile); + if (galleryFile.getText() == null) { + readText(context, galleryFile, holder); + } } else { + holder.binding.imageView.setVisibility(View.VISIBLE); Glide.with(context) .load(galleryFile.getThumbUri()) .apply(GlideStuff.getRequestOptions()) @@ -184,6 +204,40 @@ public boolean onResourceReady(Drawable resource, Object model, Target setClickListener(holder, context, galleryFile); } + private void readText(FragmentActivity context, GalleryFile galleryFile, GalleryGridViewHolder holder) { + Encryption.decryptToCache(context, galleryFile.getUri(), FileStuff.getExtensionOrDefault(galleryFile), settings.getTempPassword(), new Encryption.IOnUriResult() { + @Override + public void onUriResult(Uri outputUri) { // decrypted, now read it + try { + galleryFile.setText(FileStuff.readTextFromUri(outputUri, context)); + } catch (IOException e) { + e.printStackTrace(); + galleryFile.setText(context.getString(R.string.gallery_file_read_failed, e.getMessage())); + } + if (context instanceof GalleryDirectoryActivity) { + context.runOnUiThread(() -> ((GalleryDirectoryActivity) context).onItemChanged(holder.getBindingAdapterPosition())); + } + } + + @Override + public void onError(Exception e) { + e.printStackTrace(); + galleryFile.setText(context.getString(R.string.gallery_file_decrypt_failed, e.getMessage())); + if (context instanceof GalleryDirectoryActivity) { + context.runOnUiThread(() -> ((GalleryDirectoryActivity) context).onItemChanged(holder.getBindingAdapterPosition())); + } + } + + @Override + public void onInvalidPassword(InvalidPasswordException e) { + galleryFile.setText(context.getString(R.string.gallery_file_decrypt_failed, e.getMessage())); + if (context instanceof GalleryDirectoryActivity) { + context.runOnUiThread(() -> ((GalleryDirectoryActivity) context).onItemChanged(holder.getBindingAdapterPosition())); + } + } + }); + } + private void setItemFilename(@NonNull GalleryGridViewHolder holder, Context context, @NonNull GalleryFile galleryFile) { if (galleryFile.getSize() > 0) { holder.binding.txtName.setText(context.getString(R.string.gallery_adapter_file_name, galleryFile.getName(), StringStuff.bytesToReadableString(galleryFile.getSize()))); @@ -193,7 +247,7 @@ private void setItemFilename(@NonNull GalleryGridViewHolder holder, Context cont } private void setClickListener(@NonNull GalleryGridViewHolder holder, FragmentActivity context, GalleryFile galleryFile) { - holder.binding.imageView.setOnClickListener(v -> { + holder.binding.layout.setOnClickListener(v -> { final int pos = holder.getBindingAdapterPosition(); if (galleryFile.isAllFolder()) { if (!selectMode) { @@ -234,12 +288,12 @@ private void setClickListener(@NonNull GalleryGridViewHolder holder, FragmentAct } } }); - holder.binding.imageView.setOnLongClickListener(v -> { + holder.binding.layout.setOnLongClickListener(v -> { if (!galleryFile.isAllFolder() && (isRootDir || !galleryFile.isDirectory())) { int pos = holder.getBindingAdapterPosition(); if (!selectMode) { setSelectMode(true); - holder.binding.imageView.performClick(); + holder.binding.layout.performClick(); } else { if (lastSelectedPos >= 0 && !selectedFiles.contains(galleryFile)) { int minPos = Math.min(pos, lastSelectedPos); @@ -257,7 +311,7 @@ private void setClickListener(@NonNull GalleryGridViewHolder holder, FragmentAct activity.onSelectionChanged(selectedFiles.size()); } } else { - holder.binding.imageView.performClick(); + holder.binding.layout.performClick(); } } lastSelectedPos = pos; diff --git a/app/src/main/java/se/arctosoft/vault/adapters/GalleryPagerAdapter.java b/app/src/main/java/se/arctosoft/vault/adapters/GalleryPagerAdapter.java index 29f3b8c..951d009 100644 --- a/app/src/main/java/se/arctosoft/vault/adapters/GalleryPagerAdapter.java +++ b/app/src/main/java/se/arctosoft/vault/adapters/GalleryPagerAdapter.java @@ -68,6 +68,7 @@ import se.arctosoft.vault.databinding.AdapterGalleryViewpagerItemDirectoryBinding; import se.arctosoft.vault.databinding.AdapterGalleryViewpagerItemGifBinding; import se.arctosoft.vault.databinding.AdapterGalleryViewpagerItemImageBinding; +import se.arctosoft.vault.databinding.AdapterGalleryViewpagerItemTextBinding; import se.arctosoft.vault.databinding.AdapterGalleryViewpagerItemVideoBinding; import se.arctosoft.vault.encryption.Encryption; import se.arctosoft.vault.encryption.MyDataSourceFactory; @@ -118,6 +119,9 @@ public GalleryPagerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int } else if (viewType == FileType.VIDEO.i) { AdapterGalleryViewpagerItemVideoBinding videoBinding = AdapterGalleryViewpagerItemVideoBinding.inflate(layoutInflater, parentBinding.content, true); return new GalleryPagerViewHolder.GalleryPagerVideoViewHolder(parentBinding, videoBinding); + } else if (viewType == FileType.TEXT.i) { + AdapterGalleryViewpagerItemTextBinding textBinding = AdapterGalleryViewpagerItemTextBinding.inflate(layoutInflater, parentBinding.content, true); + return new GalleryPagerViewHolder.GalleryPagerTextViewHolder(parentBinding, textBinding); } else { AdapterGalleryViewpagerItemDirectoryBinding videoBinding = AdapterGalleryViewpagerItemDirectoryBinding.inflate(layoutInflater, parentBinding.content, true); return new GalleryPagerViewHolder.GalleryPagerDirectoryViewHolder(parentBinding, videoBinding); @@ -138,6 +142,9 @@ public void onBindViewHolder(@NonNull GalleryPagerViewHolder holder, int positio if (holder instanceof GalleryPagerViewHolder.GalleryPagerVideoViewHolder) { holder.parentBinding.imgFullscreen.setVisibility(View.VISIBLE); setupVideoView((GalleryPagerViewHolder.GalleryPagerVideoViewHolder) holder, context, galleryFile); + } else if (holder instanceof GalleryPagerViewHolder.GalleryPagerTextViewHolder) { + holder.parentBinding.imgFullscreen.setVisibility(View.VISIBLE); + setupTextView((GalleryPagerViewHolder.GalleryPagerTextViewHolder) holder, context, galleryFile); } else { holder.parentBinding.imgFullscreen.setVisibility(View.GONE); setupImageView(holder, context, galleryFile); @@ -224,13 +231,17 @@ private void loadOriginalFilename(@NonNull GalleryFile galleryFile, FragmentActi } } + private void setupTextView(GalleryPagerViewHolder.GalleryPagerTextViewHolder holder, FragmentActivity context, GalleryFile galleryFile) { + holder.binding.text.setText(galleryFile.getText()); + holder.binding.text.setTextColor(context.getResources().getColor(this.isFullscreen ? R.color.text_color_light : R.color.text_color_dark, context.getTheme())); + } + private void setupVideoView(GalleryPagerViewHolder.GalleryPagerVideoViewHolder holder, FragmentActivity context, GalleryFile galleryFile) { holder.binding.rLPlay.setVisibility(View.VISIBLE); holder.binding.playerView.setVisibility(View.INVISIBLE); Glide.with(context) .load(galleryFile.getThumbUri()) .into(holder.binding.imgThumb); - holder.parentBinding.imgFullscreen.setOnClickListener(v -> toggleFullscreen(weakReference.get())); holder.parentBinding.imgFullscreen.setVisibility(isFullscreen ? View.GONE : View.VISIBLE); holder.binding.rLPlay.setOnClickListener(v -> { holder.binding.rLPlay.setVisibility(View.GONE); @@ -388,6 +399,12 @@ private void setupButtons(GalleryPagerViewHolder holder, FragmentActivity contex holder.parentBinding.btnDelete.setOnClickListener(v -> showDelete(context, galleryFile, holder)); holder.parentBinding.btnExport.setOnClickListener(v -> showExport(context, galleryFile)); holder.parentBinding.btnMenu.setOnClickListener(v -> showMenu(context, galleryFile, holder)); + holder.parentBinding.imgFullscreen.setOnClickListener(v -> { + toggleFullscreen(weakReference.get()); + if (holder instanceof GalleryPagerViewHolder.GalleryPagerTextViewHolder) { + setupTextView((GalleryPagerViewHolder.GalleryPagerTextViewHolder) holder, context, galleryFile); + } + }); } private void showMenu(FragmentActivity context, GalleryFile galleryFile, GalleryPagerViewHolder holder) { @@ -402,17 +419,21 @@ private void showMenu(FragmentActivity context, GalleryFile galleryFile, Gallery loadShareOrOpen(context, galleryFile, false); } else if (id == R.id.open_with) { loadShareOrOpen(context, galleryFile, true); + } else if (id == R.id.edit_text) { + showEditFile(context, galleryFile, holder); } return true; }); menu.getItem(2).setVisible(!isAllFolder); // hide edit note in All folder menu.getItem(2).setEnabled(!isAllFolder); + menu.getItem(3).setVisible(!isAllFolder); // hide edit file in All folder + menu.getItem(3).setEnabled(!isAllFolder); popup.show(); } private void showEditNote(FragmentActivity context, GalleryFile galleryFile, GalleryPagerViewHolder holder) { - Dialogs.showEditTextDialog(context, null, galleryFile.getNote(), text -> { + Dialogs.showEditNoteDialog(context, galleryFile.getNote(), text -> { if (text != null && text.isBlank()) { text = null; } @@ -434,6 +455,32 @@ private void showEditNote(FragmentActivity context, GalleryFile galleryFile, Gal }); } + private void showEditFile(FragmentActivity context, GalleryFile galleryFile, GalleryPagerViewHolder holder) { + Dialogs.showImportTextDialog(context, galleryFile.getText(), true, text -> { + if (text == null || text.isBlank()) { + return; + } + galleryFile.setText(text); + String name = FileStuff.getNameWithoutPrefix(galleryFile.getEncryptedName()); + int lio = name.lastIndexOf(".txt"); + if (lio > 0) { + name = name.substring(0, lio); + } + DocumentFile.fromSingleUri(context, galleryFile.getUri()).delete(); + if (galleryFile.getNoteUri() != null) { + DocumentFile.fromSingleUri(context, galleryFile.getNoteUri()).delete(); + } + + DocumentFile createdFile = Encryption.importTextToDirectory(context, text, name, currentDirectory, settings); + if (createdFile != null) { + galleryFile.setFileUri(createdFile.getUri()); + } + if (context instanceof GalleryDirectoryActivity) { + ((GalleryDirectoryActivity) context).onItemChanged(holder.getBindingAdapterPosition()); + } + }); + } + private void loadShareOrOpen(FragmentActivity context, GalleryFile galleryFile, boolean open) { if (galleryFile.getDecryptedCacheUri() != null) { shareOrOpenWith(context, galleryFile.getDecryptedCacheUri(), open); @@ -603,6 +650,8 @@ public int getItemViewType(int position) { return FileType.GIF.i; } else if (galleryFile.getFileType() == FileType.VIDEO) { return FileType.VIDEO.i; + } else if (galleryFile.getFileType() == FileType.TEXT) { + return FileType.TEXT.i; } else { return FileType.DIRECTORY.i; } diff --git a/app/src/main/java/se/arctosoft/vault/adapters/viewholders/GalleryPagerViewHolder.java b/app/src/main/java/se/arctosoft/vault/adapters/viewholders/GalleryPagerViewHolder.java index c3328e5..c7aceeb 100644 --- a/app/src/main/java/se/arctosoft/vault/adapters/viewholders/GalleryPagerViewHolder.java +++ b/app/src/main/java/se/arctosoft/vault/adapters/viewholders/GalleryPagerViewHolder.java @@ -25,6 +25,7 @@ import se.arctosoft.vault.databinding.AdapterGalleryViewpagerItemDirectoryBinding; import se.arctosoft.vault.databinding.AdapterGalleryViewpagerItemGifBinding; import se.arctosoft.vault.databinding.AdapterGalleryViewpagerItemImageBinding; +import se.arctosoft.vault.databinding.AdapterGalleryViewpagerItemTextBinding; import se.arctosoft.vault.databinding.AdapterGalleryViewpagerItemVideoBinding; public class GalleryPagerViewHolder extends RecyclerView.ViewHolder { @@ -63,6 +64,16 @@ public GalleryPagerVideoViewHolder(AdapterGalleryViewpagerItemBinding parentBind } + public static class GalleryPagerTextViewHolder extends GalleryPagerViewHolder { + public final AdapterGalleryViewpagerItemTextBinding binding; + + public GalleryPagerTextViewHolder(AdapterGalleryViewpagerItemBinding parentBinding, @NonNull AdapterGalleryViewpagerItemTextBinding binding) { + super(parentBinding); + this.binding = binding; + } + + } + public static class GalleryPagerDirectoryViewHolder extends GalleryPagerViewHolder { public final AdapterGalleryViewpagerItemDirectoryBinding binding; diff --git a/app/src/main/java/se/arctosoft/vault/data/FileType.java b/app/src/main/java/se/arctosoft/vault/data/FileType.java index 5f37648..dd213e8 100644 --- a/app/src/main/java/se/arctosoft/vault/data/FileType.java +++ b/app/src/main/java/se/arctosoft/vault/data/FileType.java @@ -29,7 +29,8 @@ public enum FileType { DIRECTORY(0, null, null), IMAGE(1, Encryption.PREFIX_IMAGE_FILE, ".jpg"), GIF(2, Encryption.PREFIX_GIF_FILE, ".gif"), - VIDEO(3, Encryption.PREFIX_VIDEO_FILE, ".mp4"); + VIDEO(3, Encryption.PREFIX_VIDEO_FILE, ".mp4"), + TEXT(4, Encryption.PREFIX_TEXT_FILE, ".txt"); public final int i; public final String encryptionPrefix, extension; @@ -47,6 +48,8 @@ public static FileType fromFilename(@NonNull String name) { return GIF; } else if (name.contains(Encryption.PREFIX_VIDEO_FILE)) { return VIDEO; + } else if (name.contains(Encryption.PREFIX_TEXT_FILE)) { + return TEXT; } else { return DIRECTORY; } @@ -61,6 +64,8 @@ public static FileType fromMimeType(@Nullable String mimeType) { return FileType.DIRECTORY; } else if (mimeType.startsWith("image/")) { return FileType.IMAGE; + } else if (mimeType.startsWith("text/")) { + return FileType.TEXT; } else { return FileType.VIDEO; } diff --git a/app/src/main/java/se/arctosoft/vault/data/GalleryFile.java b/app/src/main/java/se/arctosoft/vault/data/GalleryFile.java index c708779..28fabb3 100644 --- a/app/src/main/java/se/arctosoft/vault/data/GalleryFile.java +++ b/app/src/main/java/se/arctosoft/vault/data/GalleryFile.java @@ -34,11 +34,11 @@ public class GalleryFile implements Comparable { private final FileType fileType; private final String encryptedName, name; private final boolean isDirectory, isAllFolder; - private final Uri fileUri; + private Uri fileUri; private final long lastModified, size; private Uri thumbUri, noteUri, decryptedCacheUri; private List filesInDirectory; - private String originalName, nameWithPath, note; + private String originalName, nameWithPath, note, text; private GalleryFile(String name) { this.fileUri = null; @@ -135,6 +135,10 @@ public boolean isGif() { return FileType.GIF == fileType; } + public boolean isText() { + return FileType.TEXT == fileType; + } + public long getSize() { return size; } @@ -185,6 +189,10 @@ public void setNoteUri(Uri noteUri) { this.noteUri = noteUri; } + public void setFileUri(@NonNull Uri fileUri) { + this.fileUri = fileUri; + } + @Nullable public String getNote() { return note; @@ -194,6 +202,15 @@ public void setNote(@Nullable String note) { this.note = note; } + @Nullable + public String getText() { + return text; + } + + public void setText(@Nullable String text) { + this.text = text; + } + public boolean hasThumb() { return thumbUri != null; } diff --git a/app/src/main/java/se/arctosoft/vault/encryption/Encryption.java b/app/src/main/java/se/arctosoft/vault/encryption/Encryption.java index 1c66cfa..cbe6554 100644 --- a/app/src/main/java/se/arctosoft/vault/encryption/Encryption.java +++ b/app/src/main/java/se/arctosoft/vault/encryption/Encryption.java @@ -78,6 +78,7 @@ public class Encryption { public static final String PREFIX_IMAGE_FILE = ".valv.i.1-"; public static final String PREFIX_GIF_FILE = ".valv.g.1-"; public static final String PREFIX_VIDEO_FILE = ".valv.v.1-"; + public static final String PREFIX_TEXT_FILE = ".valv.x.1-"; public static final String PREFIX_NOTE_FILE = ".valv.n.1-"; public static final String PREFIX_THUMB = ".valv.t.1-"; @@ -123,7 +124,7 @@ public static DocumentFile importNoteToDirectory(FragmentActivity context, Strin DocumentFile file = directory.createFile("", Encryption.PREFIX_NOTE_FILE + fileNameWithoutPrefix); try { - createNoteFile(context, note, file, tempPassword, fileNameWithoutPrefix); + createTextFile(context, note, file, tempPassword, fileNameWithoutPrefix); } catch (GeneralSecurityException | IOException e) { Log.e(TAG, "importNoteToDirectory: failed " + e.getMessage()); e.printStackTrace(); @@ -134,6 +135,29 @@ public static DocumentFile importNoteToDirectory(FragmentActivity context, Strin return file; } + public static DocumentFile importTextToDirectory(FragmentActivity context, String text, @Nullable String fileNameWithoutPrefix, DocumentFile directory, Settings settings) { + char[] tempPassword = settings.getTempPassword(); + if (tempPassword == null || tempPassword.length == 0) { + throw new RuntimeException("No password"); + } + + if (fileNameWithoutPrefix == null) { + fileNameWithoutPrefix = StringStuff.getRandomFileName(); + } + DocumentFile file = directory.createFile("", Encryption.PREFIX_TEXT_FILE + fileNameWithoutPrefix); + + try { + createTextFile(context, text, file, tempPassword, fileNameWithoutPrefix + FileType.TEXT.extension); + } catch (GeneralSecurityException | IOException e) { + Log.e(TAG, "importTextToDirectory: failed " + e.getMessage()); + e.printStackTrace(); + file.delete(); + return null; + } + + return file; + } + public static class Streams { private final InputStream inputStream; private final CipherOutputStream outputStream; @@ -218,8 +242,8 @@ private static void createFile(FragmentActivity context, Uri input, DocumentFile streams.close(); } - private static void createNoteFile(FragmentActivity context, String input, DocumentFile outputFile, char[] password, String sourceFileName) throws GeneralSecurityException, IOException { - Streams streams = getNoteCipherOutputStream(context, input, outputFile, password, sourceFileName); + private static void createTextFile(FragmentActivity context, String input, DocumentFile outputFile, char[] password, String sourceFileName) throws GeneralSecurityException, IOException { + Streams streams = getTextCipherOutputStream(context, input, outputFile, password, sourceFileName); streams.outputStream.write(streams.inputString.getBytes(StandardCharsets.UTF_8)); streams.close(); } @@ -320,7 +344,7 @@ private static Streams getCipherOutputStream(FragmentActivity context, Uri input return new Streams(inputStream, cipherOutputStream, secretKey); } - private static Streams getNoteCipherOutputStream(FragmentActivity context, String input, DocumentFile outputFile, char[] password, String sourceFileName) throws GeneralSecurityException, IOException { + private static Streams getTextCipherOutputStream(FragmentActivity context, String input, DocumentFile outputFile, char[] password, String sourceFileName) throws GeneralSecurityException, IOException { SecureRandom sr = SecureRandom.getInstanceStrong(); byte[] salt = new byte[SALT_LENGTH]; byte[] ivBytes = new byte[IV_LENGTH]; diff --git a/app/src/main/java/se/arctosoft/vault/utils/Dialogs.java b/app/src/main/java/se/arctosoft/vault/utils/Dialogs.java index 2257345..7aebbfa 100644 --- a/app/src/main/java/se/arctosoft/vault/utils/Dialogs.java +++ b/app/src/main/java/se/arctosoft/vault/utils/Dialogs.java @@ -21,6 +21,7 @@ import android.content.Context; import android.content.DialogInterface; import android.net.Uri; +import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -39,8 +40,9 @@ import se.arctosoft.vault.BuildConfig; import se.arctosoft.vault.R; import se.arctosoft.vault.adapters.ImportListAdapter; -import se.arctosoft.vault.databinding.DialogEditTextBinding; +import se.arctosoft.vault.databinding.DialogEditNoteBinding; import se.arctosoft.vault.databinding.DialogImportBinding; +import se.arctosoft.vault.databinding.DialogImportTextBinding; import se.arctosoft.vault.interfaces.IOnEdited; public class Dialogs { @@ -79,6 +81,39 @@ public static void showImportGalleryChooseDestinationDialog(FragmentActivity con binding.recycler.setAdapter(adapter); } + public static void showImportTextChooseDestinationDialog(FragmentActivity context, Settings settings, IOnDirectorySelected onDirectorySelected) { + List directories = settings.getGalleryDirectoriesAsUri(false); + List names = new ArrayList<>(directories.size()); + for (int i = 0; i < directories.size(); i++) { + names.add(FileStuff.getFilenameWithPathFromUri(directories.get(i))); + } + DialogImportBinding binding = DialogImportBinding.inflate(context.getLayoutInflater()); + + AlertDialog alertDialog = new MaterialAlertDialogBuilder(context) + .setTitle(context.getString(R.string.dialog_import_to_title)) + .setView(binding.getRoot()) + .setNegativeButton(android.R.string.cancel, null) + .setNeutralButton(R.string.dialog_import_to_button_neutral, (dialog, which) -> onDirectorySelected.onOtherDirectory()) + .show(); + + ImportListAdapter adapter = new ImportListAdapter(names, pos -> { + alertDialog.dismiss(); + Uri uri = directories.get(pos); + + DocumentFile directory = DocumentFile.fromTreeUri(context, uri); + if (directory == null || !directory.isDirectory() || !directory.exists()) { + settings.removeGalleryDirectory(uri); + Toaster.getInstance(context).showLong(context.getString(R.string.directory_does_not_exist)); + showImportTextChooseDestinationDialog(context, settings, onDirectorySelected); + } else { + onDirectorySelected.onDirectorySelected(directory, false); + } + }); + binding.checkbox.setVisibility(View.GONE); + binding.recycler.setLayoutManager(new LinearLayoutManager(context)); + binding.recycler.setAdapter(adapter); + } + public static void showCopyMoveChooseDestinationDialog(FragmentActivity context, Settings settings, int fileCount, IOnDirectorySelected onDirectorySelected) { List directories = settings.getGalleryDirectoriesAsUri(false); String[] names = new String[directories.size()]; @@ -148,14 +183,13 @@ public static void showConfirmationDialog(Context context, String title, String .show(); } - public static void showEditTextDialog(FragmentActivity context, @Nullable String title, @Nullable String editTextBody, IOnEdited onEdited) { - DialogEditTextBinding binding = DialogEditTextBinding.inflate(context.getLayoutInflater(), null, false); + public static void showEditNoteDialog(FragmentActivity context, @Nullable String editTextBody, IOnEdited onEdited) { + DialogEditNoteBinding binding = DialogEditNoteBinding.inflate(context.getLayoutInflater(), null, false); if (editTextBody != null) { binding.text.setText(editTextBody); } new MaterialAlertDialogBuilder(context) - .setTitle(title) .setView(binding.getRoot()) .setPositiveButton(R.string.gallery_note_save, (dialog, which) -> onEdited.onEdited(binding.text.getText().toString())) .setNegativeButton(android.R.string.cancel, null) @@ -163,6 +197,20 @@ public static void showEditTextDialog(FragmentActivity context, @Nullable String .show(); } + public static void showImportTextDialog(FragmentActivity context, @Nullable String editTextBody, boolean isEdit, IOnEdited onEdited) { + DialogImportTextBinding binding = DialogImportTextBinding.inflate(context.getLayoutInflater(), null, false); + if (editTextBody != null) { + binding.text.setText(editTextBody); + } + + new MaterialAlertDialogBuilder(context) + .setTitle(context.getString(R.string.gallery_import_text_title)) + .setView(binding.getRoot()) + .setPositiveButton(isEdit ? R.string.gallery_import_text_overwrite : R.string.gallery_import_text_import, (dialog, which) -> onEdited.onEdited(binding.text.getText().toString())) + .setNegativeButton(android.R.string.cancel, null) + .show(); + } + public static void showEditIncludedFolders(Context context, @NonNull Settings settings, @NonNull IOnEditedIncludedFolders onEditedIncludedFolders) { List directories = settings.getGalleryDirectoriesAsUri(false); String[] names = new String[directories.size()]; diff --git a/app/src/main/java/se/arctosoft/vault/viewmodel/GalleryDirectoryViewModel.java b/app/src/main/java/se/arctosoft/vault/viewmodel/GalleryDirectoryViewModel.java index b607f05..4e4f802 100644 --- a/app/src/main/java/se/arctosoft/vault/viewmodel/GalleryDirectoryViewModel.java +++ b/app/src/main/java/se/arctosoft/vault/viewmodel/GalleryDirectoryViewModel.java @@ -30,6 +30,7 @@ public class GalleryDirectoryViewModel extends ViewModel { private static final String TAG = "GalleryDirectoryViewMod"; private final List galleryFiles = new LinkedList<>(); + private final List hiddenFiles = new LinkedList<>(); private int currentPosition = 0; private boolean viewPagerVisible = false; private boolean initialised = false; @@ -43,6 +44,11 @@ public List getGalleryFiles() { return galleryFiles; } + @NonNull + public List getHiddenFiles() { + return hiddenFiles; + } + public void setInitialised(List galleryFiles) { //Log.e(TAG, "setInitialised: " + galleryFiles.size()); if (initialised) { @@ -50,6 +56,7 @@ public void setInitialised(List galleryFiles) { } this.initialised = true; this.galleryFiles.addAll(galleryFiles); + this.hiddenFiles.clear(); } public int getCurrentPosition() { diff --git a/app/src/main/java/se/arctosoft/vault/viewmodel/GalleryViewModel.java b/app/src/main/java/se/arctosoft/vault/viewmodel/GalleryViewModel.java index 320473e..2b181e2 100644 --- a/app/src/main/java/se/arctosoft/vault/viewmodel/GalleryViewModel.java +++ b/app/src/main/java/se/arctosoft/vault/viewmodel/GalleryViewModel.java @@ -26,6 +26,7 @@ public class GalleryViewModel extends ViewModel { private List filesToAdd; + private String textToImport; public void setFilesToAdd(@Nullable List filesToAdd) { this.filesToAdd = filesToAdd; @@ -35,4 +36,13 @@ public void setFilesToAdd(@Nullable List filesToAdd) { public List getFilesToAdd() { return filesToAdd; } + + public void setTextToImport(String textToImport) { + this.textToImport = textToImport; + } + + @Nullable + public String getTextToImport() { + return textToImport; + } } diff --git a/app/src/main/java/se/arctosoft/vault/views/PressableGridImageView.java b/app/src/main/java/se/arctosoft/vault/views/GridImageView.java similarity index 79% rename from app/src/main/java/se/arctosoft/vault/views/PressableGridImageView.java rename to app/src/main/java/se/arctosoft/vault/views/GridImageView.java index d9b2c49..9c77113 100644 --- a/app/src/main/java/se/arctosoft/vault/views/PressableGridImageView.java +++ b/app/src/main/java/se/arctosoft/vault/views/GridImageView.java @@ -23,19 +23,17 @@ import androidx.appcompat.widget.AppCompatImageView; -import se.arctosoft.vault.utils.Constants; +public class GridImageView extends AppCompatImageView { -public class PressableGridImageView extends PressableImageView { - - public PressableGridImageView(Context context) { + public GridImageView(Context context) { super(context); } - public PressableGridImageView(Context context, AttributeSet attrs) { + public GridImageView(Context context, AttributeSet attrs) { super(context, attrs); } - public PressableGridImageView(Context context, AttributeSet attrs, int defStyleAttr) { + public GridImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } diff --git a/app/src/main/java/se/arctosoft/vault/views/PressableGridTextView.java b/app/src/main/java/se/arctosoft/vault/views/PressableGridTextView.java new file mode 100644 index 0000000..e68eaf5 --- /dev/null +++ b/app/src/main/java/se/arctosoft/vault/views/PressableGridTextView.java @@ -0,0 +1,45 @@ +/* + * Valv-Android + * Copyright (C) 2023 Arctosoft AB + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ + +package se.arctosoft.vault.views; + +import android.content.Context; +import android.util.AttributeSet; + +public class PressableGridTextView extends androidx.appcompat.widget.AppCompatTextView { + + public PressableGridTextView(Context context) { + super(context); + } + + public PressableGridTextView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public PressableGridTextView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + int width = getMeasuredWidth(); + setMeasuredDimension(width, (int) (width * 1.2)); + } +} diff --git a/app/src/main/res/drawable/round_edit_24.xml b/app/src/main/res/drawable/round_edit_24.xml new file mode 100644 index 0000000..720ee86 --- /dev/null +++ b/app/src/main/res/drawable/round_edit_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/round_filter_list_24.xml b/app/src/main/res/drawable/round_filter_list_24.xml new file mode 100644 index 0000000..0cba524 --- /dev/null +++ b/app/src/main/res/drawable/round_filter_list_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/round_sort_24.xml b/app/src/main/res/drawable/round_sort_24.xml new file mode 100644 index 0000000..9c97517 --- /dev/null +++ b/app/src/main/res/drawable/round_sort_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/layout/activity_gallery.xml b/app/src/main/res/layout/activity_gallery.xml index f3c65dc..b6808b3 100644 --- a/app/src/main/res/layout/activity_gallery.xml +++ b/app/src/main/res/layout/activity_gallery.xml @@ -106,7 +106,7 @@ android:layout_height="wrap_content" android:elevation="10dp" android:orientation="vertical" - android:weightSum="2"> + android:weightSum="3"> + + diff --git a/app/src/main/res/layout/adapter_gallery_grid_item.xml b/app/src/main/res/layout/adapter_gallery_grid_item.xml index 284a776..3613669 100644 --- a/app/src/main/res/layout/adapter_gallery_grid_item.xml +++ b/app/src/main/res/layout/adapter_gallery_grid_item.xml @@ -8,7 +8,8 @@ app:strokeColor="?attr/gallery_grid_stroke" app:strokeWidth="1dp"> - @@ -19,12 +20,21 @@ android:orientation="vertical" app:layout_constraintTop_toTopOf="parent"> - + + - + \ No newline at end of file diff --git a/app/src/main/res/layout/adapter_gallery_viewpager_item_text.xml b/app/src/main/res/layout/adapter_gallery_viewpager_item_text.xml new file mode 100644 index 0000000..66ef5f9 --- /dev/null +++ b/app/src/main/res/layout/adapter_gallery_viewpager_item_text.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_edit_text.xml b/app/src/main/res/layout/dialog_edit_note.xml similarity index 100% rename from app/src/main/res/layout/dialog_edit_text.xml rename to app/src/main/res/layout/dialog_edit_note.xml diff --git a/app/src/main/res/layout/dialog_import_text.xml b/app/src/main/res/layout/dialog_import_text.xml new file mode 100644 index 0000000..fec119e --- /dev/null +++ b/app/src/main/res/layout/dialog_import_text.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_gallery_directory.xml b/app/src/main/res/menu/menu_gallery_directory.xml index cb5c63e..79ef4c9 100644 --- a/app/src/main/res/menu/menu_gallery_directory.xml +++ b/app/src/main/res/menu/menu_gallery_directory.xml @@ -5,7 +5,50 @@ android:id="@+id/lock" android:icon="@drawable/ic_outline_lock_24" android:title="@string/menu_lock_vault" - app:showAsAction="ifRoom" /> + app:showAsAction="always" /> + + + + + + + + + + + + + + + + + + app:showAsAction="ifRoom" /> + app:showAsAction="ifRoom" /> + app:showAsAction="ifRoom" /> \ No newline at end of file diff --git a/app/src/main/res/menu/menu_gallery_viewpager.xml b/app/src/main/res/menu/menu_gallery_viewpager.xml index c74ab8c..0c6d920 100644 --- a/app/src/main/res/menu/menu_gallery_viewpager.xml +++ b/app/src/main/res/menu/menu_gallery_viewpager.xml @@ -9,4 +9,7 @@ + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cb30487..093a7f0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -11,6 +11,7 @@ Delete Export Add/edit note + Edit text Share Open with More @@ -27,7 +28,18 @@ Edit included folders Import files Lock vault + Order by + Newest first + Oldest first + Largest first + Smallest first Toggle filename visibility + Filter + Show all + Only images + Only GIFs + Only videos + Only text files Select all About @@ -41,6 +53,11 @@ Import files Images/GIFs Videos + Text (write) + Write text to import + Import + Overwrite + Text… Remove selected folders Delete %1$d selected files Export selected @@ -71,6 +88,8 @@ Decrypting note Failed to decrypt note: %1$s Failed to read note: %1$s + Failed to decrypt file: %1$s + Failed to read file: %1$s Note text Save note Delete note diff --git a/build.gradle b/build.gradle index 06ea01f..6e56362 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '8.4.0' apply false - id 'com.android.library' version '8.4.0' apply false + id 'com.android.application' version '8.5.0' apply false + id 'com.android.library' version '8.5.0' apply false id 'com.mikepenz.aboutlibraries.plugin' version '10.8.3' apply false } diff --git a/fastlane/metadata/android/en-US/changelogs/28.txt b/fastlane/metadata/android/en-US/changelogs/28.txt new file mode 100644 index 0000000..63a454e --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/28.txt @@ -0,0 +1,3 @@ +* Encrypt text files +* Order files by date and size +* Filter files by file type \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt index a861a48..39b0c58 100644 --- a/fastlane/metadata/android/en-US/full_description.txt +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -1,7 +1,7 @@ An encrypted gallery vault for Android devices. Features -* Supports images, GIFs and videos +* Supports images, GIFs, videos and text files * Organise using folders * The app requires no permissions * Encrypted files are stored on-disk allowing for easy backups and transfers between devices diff --git a/fastlane/metadata/android/sv-SE/changelogs/28.txt b/fastlane/metadata/android/sv-SE/changelogs/28.txt new file mode 100644 index 0000000..2ea63e8 --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/28.txt @@ -0,0 +1,3 @@ +* Kryptera textfiler +* Ordna filer efter datum och storlek +* Filtrera filer efter filtyp \ No newline at end of file diff --git a/fastlane/metadata/android/sv-SE/full_description.txt b/fastlane/metadata/android/sv-SE/full_description.txt index 26430a0..b98e054 100644 --- a/fastlane/metadata/android/sv-SE/full_description.txt +++ b/fastlane/metadata/android/sv-SE/full_description.txt @@ -1,7 +1,7 @@ En krypterad galleriapp för Android-enheter. Funktioner -* Stödjer bilder, GIF och videor +* Stödjer bilder, GIF, videor och textfiler * Organisera med hjälp av mappar * Appen kräver inga behörigheter * Dina krypterade filer lagras lokalt som vanliga filer vilket möjliggör enkel säkerhetskopiering och överföringar mellan enheter diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 639fcba..d240742 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Mon Aug 21 15:00:33 CEST 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists