diff --git a/app/build.gradle b/app/build.gradle
index 3c520f75..b85aecad 100755
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -20,7 +20,7 @@ android {
applicationId 'me.austinhuang.instagrabber'
minSdkVersion 21
- targetSdkVersion 29
+ targetSdkVersion 30
versionCode 63
versionName '19.2.2'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index bc617ff1..4e6d6d3f 100755
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -4,7 +4,7 @@
package="awais.instagrabber">
-
+
@@ -20,7 +20,6 @@
android:fullBackupContent="@xml/backup_descriptor"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
- android:requestLegacyExternalStorage="true"
android:supportsRtl="true"
android:theme="@style/AppTheme.Launcher"
tools:ignore="UnusedAttribute">
@@ -98,6 +97,7 @@
+
- Log.d(TAG, "onImageSaved: scan complete")
- val intent = Intent()
- intent.data = uri1
- setResult(RESULT_OK, intent)
- finish()
- }
- Log.d(TAG, "onImageSaved: $uri")
+ try { outputStream.close() } catch (ignored: IOException) {}
+ val intent = Intent()
+ intent.data = photoFile.uri
+ setResult(RESULT_OK, intent)
+ finish()
+ Log.d(TAG, "onImageSaved: " + photoFile.uri)
}
override fun onError(exception: ImageCaptureException) {
Log.e(TAG, "onError: ", exception)
+ try { outputStream.close() } catch (ignored: IOException) {}
}
}
)
diff --git a/app/src/main/java/awais/instagrabber/activities/DirectorySelectActivity.java b/app/src/main/java/awais/instagrabber/activities/DirectorySelectActivity.java
new file mode 100644
index 00000000..7fffbfec
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/activities/DirectorySelectActivity.java
@@ -0,0 +1,115 @@
+package awais.instagrabber.activities;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.provider.DocumentsContract;
+import android.util.Log;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.ViewModelProvider;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import awais.instagrabber.R;
+import awais.instagrabber.databinding.ActivityDirectorySelectBinding;
+import awais.instagrabber.dialogs.ConfirmDialogFragment;
+import awais.instagrabber.utils.AppExecutors;
+import awais.instagrabber.viewmodels.DirectorySelectActivityViewModel;
+
+public class DirectorySelectActivity extends BaseLanguageActivity {
+ private static final String TAG = DirectorySelectActivity.class.getSimpleName();
+ public static final int SELECT_DIR_REQUEST_CODE = 0x01;
+ private static final int ERROR_REQUEST_CODE = 0x02;
+
+ private Uri initialUri;
+ private ActivityDirectorySelectBinding binding;
+ private DirectorySelectActivityViewModel viewModel;
+
+ @Override
+ protected void onCreate(@Nullable final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ binding = ActivityDirectorySelectBinding.inflate(getLayoutInflater());
+ setContentView(binding.getRoot());
+ viewModel = new ViewModelProvider(this).get(DirectorySelectActivityViewModel.class);
+ setupObservers();
+ binding.selectDir.setOnClickListener(v -> openDirectoryChooser());
+ AppExecutors.INSTANCE.getMainThread().execute(() -> viewModel.setInitialUri(getIntent()));
+ }
+
+ private void setupObservers() {
+ viewModel.getMessage().observe(this, message -> binding.message.setText(message));
+ viewModel.getPrevUri().observe(this, prevUri -> {
+ if (prevUri == null) {
+ binding.prevUri.setVisibility(View.GONE);
+ binding.message2.setVisibility(View.GONE);
+ return;
+ }
+ binding.prevUri.setText(prevUri);
+ binding.prevUri.setVisibility(View.VISIBLE);
+ binding.message2.setVisibility(View.VISIBLE);
+ });
+ viewModel.getDirSuccess().observe(this, success -> binding.selectDir.setVisibility(success ? View.GONE : View.VISIBLE));
+ viewModel.isLoading().observe(this, loading -> {
+ binding.message.setVisibility(loading ? View.GONE : View.VISIBLE);
+ binding.loadingIndicator.setVisibility(loading ? View.VISIBLE : View.GONE);
+ });
+ }
+
+ private void openDirectoryChooser() {
+ final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && initialUri != null) {
+ intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, initialUri);
+ }
+ startActivityForResult(intent, SELECT_DIR_REQUEST_CODE);
+ }
+
+ @Override
+ protected void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (requestCode != SELECT_DIR_REQUEST_CODE) return;
+ if (resultCode != RESULT_OK) {
+ showErrorDialog(getString(R.string.select_a_folder));
+ return;
+ }
+ if (data == null || data.getData() == null) {
+ showErrorDialog(getString(R.string.select_a_folder));
+ return;
+ }
+ AppExecutors.INSTANCE.getMainThread().execute(() -> {
+ try {
+ viewModel.setupSelectedDir(data);
+ final Intent intent = new Intent(this, MainActivity.class);
+ startActivity(intent);
+ finish();
+ } catch (Exception e) {
+ // Should not come to this point.
+ // If it does, we have to show this error to the user so that they can report it.
+ try (final StringWriter sw = new StringWriter();
+ final PrintWriter pw = new PrintWriter(sw)) {
+ e.printStackTrace(pw);
+ showErrorDialog("Please report this error to the developers:\n\n" + sw.toString());
+ } catch (IOException ioException) {
+ Log.e(TAG, "onActivityResult: ", ioException);
+ }
+ }
+ }, 500);
+ }
+
+ private void showErrorDialog(@NonNull final String message) {
+ final ConfirmDialogFragment dialogFragment = ConfirmDialogFragment.newInstance(
+ ERROR_REQUEST_CODE,
+ R.string.error,
+ message,
+ R.string.ok,
+ 0,
+ 0
+ );
+ dialogFragment.show(getSupportFragmentManager(), ConfirmDialogFragment.class.getSimpleName());
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/activities/MainActivity.kt b/app/src/main/java/awais/instagrabber/activities/MainActivity.kt
index 90be552a..adb1e728 100644
--- a/app/src/main/java/awais/instagrabber/activities/MainActivity.kt
+++ b/app/src/main/java/awais/instagrabber/activities/MainActivity.kt
@@ -8,6 +8,7 @@ import android.content.ComponentName
import android.content.Intent
import android.content.ServiceConnection
import android.os.*
+import android.provider.DocumentsContract.EXTRA_INITIAL_URI
import android.text.Editable
import android.util.Log
import android.view.Menu
@@ -53,6 +54,7 @@ import awais.instagrabber.services.ActivityCheckerService
import awais.instagrabber.services.DMSyncAlarmReceiver
import awais.instagrabber.utils.*
import awais.instagrabber.utils.AppExecutors.tasksThread
+import awais.instagrabber.utils.DownloadUtils.ReselectDocumentTreeException
import awais.instagrabber.utils.TextUtils.isEmpty
import awais.instagrabber.utils.TextUtils.shortcodeToId
import awais.instagrabber.utils.emoji.EmojiParser
@@ -73,6 +75,7 @@ import kotlinx.coroutines.withContext
import java.util.*
import java.util.stream.Collectors
+
class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedListener {
private lateinit var binding: ActivityMainBinding
@@ -107,6 +110,16 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL
private val graphQLRepository: GraphQLRepository by lazy { GraphQLRepository.getInstance() }
override fun onCreate(savedInstanceState: Bundle?) {
+ try {
+ DownloadUtils.init(this)
+ } catch (e: ReselectDocumentTreeException) {
+ super.onCreate(savedInstanceState)
+ val intent = Intent(this, DirectorySelectActivity::class.java)
+ intent.putExtra(EXTRA_INITIAL_URI, e.initialUri)
+ startActivity(intent)
+ finish()
+ return
+ }
super.onCreate(savedInstanceState)
instance = this
binding = ActivityMainBinding.inflate(layoutInflater)
diff --git a/app/src/main/java/awais/instagrabber/dialogs/ConfirmDialogFragment.java b/app/src/main/java/awais/instagrabber/dialogs/ConfirmDialogFragment.java
index f5b5bc81..70d4c9f3 100644
--- a/app/src/main/java/awais/instagrabber/dialogs/ConfirmDialogFragment.java
+++ b/app/src/main/java/awais/instagrabber/dialogs/ConfirmDialogFragment.java
@@ -11,6 +11,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
@@ -73,6 +74,7 @@ public class ConfirmDialogFragment extends DialogFragment {
ConfirmDialogFragment fragment = new ConfirmDialogFragment();
fragment.setArguments(args);
return fragment;
+
}
public ConfirmDialogFragment() {}
@@ -80,11 +82,16 @@ public class ConfirmDialogFragment extends DialogFragment {
@Override
public void onAttach(@NonNull final Context context) {
super.onAttach(context);
+ this.context = context;
final Fragment parentFragment = getParentFragment();
if (parentFragment instanceof ConfirmDialogFragmentCallback) {
callback = (ConfirmDialogFragmentCallback) parentFragment;
+ return;
+ }
+ final FragmentActivity fragmentActivity = getActivity();
+ if (fragmentActivity instanceof ConfirmDialogFragmentCallback) {
+ callback = (ConfirmDialogFragmentCallback) fragmentActivity;
}
- this.context = context;
}
@NonNull
diff --git a/app/src/main/java/awais/instagrabber/dialogs/CreateBackupDialogFragment.java b/app/src/main/java/awais/instagrabber/dialogs/CreateBackupDialogFragment.java
index 478837ad..bbf2e35e 100644
--- a/app/src/main/java/awais/instagrabber/dialogs/CreateBackupDialogFragment.java
+++ b/app/src/main/java/awais/instagrabber/dialogs/CreateBackupDialogFragment.java
@@ -2,8 +2,10 @@ package awais.instagrabber.dialogs;
import android.app.Dialog;
import android.content.Context;
-import android.content.pm.PackageManager;
+import android.content.Intent;
+import android.os.Build;
import android.os.Bundle;
+import android.provider.DocumentsContract;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
@@ -14,27 +16,26 @@ import android.view.inputmethod.InputMethodManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment;
-import androidx.fragment.app.FragmentTransaction;
-import java.io.File;
import java.time.format.DateTimeFormatter;
import java.time.LocalDateTime;
import java.util.Locale;
import awais.instagrabber.databinding.DialogCreateBackupBinding;
-import awais.instagrabber.utils.DirectoryChooser;
+import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.ExportImportUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
-import static awais.instagrabber.fragments.settings.PreferenceKeys.FOLDER_PATH;
-import static awais.instagrabber.utils.DownloadUtils.PERMS;
+import static android.app.Activity.RESULT_OK;
public class CreateBackupDialogFragment extends DialogFragment {
+ private static final String TAG = CreateBackupDialogFragment.class.getSimpleName();
private static final int STORAGE_PERM_REQUEST_CODE = 8020;
private static final DateTimeFormatter BACKUP_FILE_DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("yyyyMMddHHmmss", Locale.US);
+ private static final int CREATE_FILE_REQUEST_CODE = 1;
+
private final OnResultListener onResultListener;
private DialogCreateBackupBinding binding;
@@ -113,59 +114,112 @@ public class CreateBackupDialogFragment extends DialogFragment {
imm.hideSoftInputFromWindow(binding.etPassword.getWindowToken(), InputMethodManager.RESULT_UNCHANGED_SHOWN);
});
binding.btnSaveTo.setOnClickListener(v -> {
- if (ContextCompat.checkSelfPermission(context, PERMS[0]) == PackageManager.PERMISSION_GRANTED) {
- showChooser(context);
- } else {
- requestPermissions(PERMS, STORAGE_PERM_REQUEST_CODE);
- }
+ createFile();
+ // if (ContextCompat.checkSelfPermission(context, PERMS[0]) == PackageManager.PERMISSION_GRANTED) {
+ // showChooser(context);
+ // } else {
+ // requestPermissions(PERMS, STORAGE_PERM_REQUEST_CODE);
+ // }
});
}
@Override
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
- if (requestCode == STORAGE_PERM_REQUEST_CODE && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- final Context context = getContext();
- if (context == null) return;
- showChooser(context);
- }
+ // if (requestCode == STORAGE_PERM_REQUEST_CODE && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ // final Context context = getContext();
+ // if (context == null) return;
+ // showChooser(context);
+ // }
}
- private void showChooser(@NonNull final Context context) {
- final String folderPath = Utils.settingsHelper.getString(FOLDER_PATH);
+ @Override
+ public void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) {
+ if (data == null || data.getData() == null) return;
+ if (resultCode != RESULT_OK || requestCode != CREATE_FILE_REQUEST_CODE) return;
+ final Context context = getContext();
+ if (context == null) return;
final Editable passwordText = binding.etPassword.getText();
final String password = binding.cbPassword.isChecked()
&& passwordText != null
&& !TextUtils.isEmpty(passwordText.toString())
? passwordText.toString().trim()
: null;
- final DirectoryChooser directoryChooser = new DirectoryChooser()
- .setInitialDirectory(folderPath)
- .setInteractionListener(path -> {
- final File file = new File(path, String.format("barinsta_%s.backup", LocalDateTime.now().format(BACKUP_FILE_DATE_TIME_FORMAT)));
- int flags = 0;
- if (binding.cbExportFavorites.isChecked()) {
- flags |= ExportImportUtils.FLAG_FAVORITES;
- }
- if (binding.cbExportSettings.isChecked()) {
- flags |= ExportImportUtils.FLAG_SETTINGS;
- }
- if (binding.cbExportLogins.isChecked()) {
- flags |= ExportImportUtils.FLAG_COOKIES;
- }
- ExportImportUtils.exportData(context, flags, file, password, result -> {
- if (onResultListener != null) {
- onResultListener.onResult(result);
- }
- dismiss();
- });
-
- });
- directoryChooser.setEnterTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
- directoryChooser.setExitTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
- directoryChooser.show(getChildFragmentManager(), "directory_chooser");
+ int flags = 0;
+ if (binding.cbExportFavorites.isChecked()) {
+ flags |= ExportImportUtils.FLAG_FAVORITES;
+ }
+ if (binding.cbExportSettings.isChecked()) {
+ flags |= ExportImportUtils.FLAG_SETTINGS;
+ }
+ if (binding.cbExportLogins.isChecked()) {
+ flags |= ExportImportUtils.FLAG_COOKIES;
+ }
+ ExportImportUtils.exportData(context, flags, data.getData(), password, result -> {
+ if (onResultListener != null) {
+ onResultListener.onResult(result);
+ }
+ dismiss();
+ });
+ // try (final OutputStream stream = context.getContentResolver().openOutputStream(data.getData())) {
+ // } catch (Exception e) {
+ // Log.e(TAG, "onActivityResult: ", e);
+ // }
}
+ // private void showChooser(@NonNull final Context context) {
+ // final String folderPath = Utils.settingsHelper.getString(FOLDER_PATH);
+ // final Editable passwordText = binding.etPassword.getText();
+ // final String password = binding.cbPassword.isChecked()
+ // && passwordText != null
+ // && !TextUtils.isEmpty(passwordText.toString())
+ // ? passwordText.toString().trim()
+ // : null;
+ // final DirectoryChooser directoryChooser = new DirectoryChooser()
+ // .setInitialDirectory(folderPath)
+ // .setInteractionListener(path -> {
+ // final Date now = new Date();
+ // final File file = new File(path, String.format("barinsta_%s.backup", BACKUP_FILE_DATE_TIME_FORMAT.format(now)));
+ // int flags = 0;
+ // if (binding.cbExportFavorites.isChecked()) {
+ // flags |= ExportImportUtils.FLAG_FAVORITES;
+ // }
+ // if (binding.cbExportSettings.isChecked()) {
+ // flags |= ExportImportUtils.FLAG_SETTINGS;
+ // }
+ // if (binding.cbExportLogins.isChecked()) {
+ // flags |= ExportImportUtils.FLAG_COOKIES;
+ // }
+ // ExportImportUtils.exportData(context, flags, file, password, result -> {
+ // if (onResultListener != null) {
+ // onResultListener.onResult(result);
+ // }
+ // dismiss();
+ // });
+ //
+ // });
+ // directoryChooser.setEnterTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
+ // directoryChooser.setExitTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
+ // directoryChooser.show(getChildFragmentManager(), "directory_chooser");
+ // }
+
+ private void createFile() {
+ Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+ intent.setType("application/octet-stream");
+ final String fileName = String.format("barinsta_%s.backup", LocalDateTime.now().format(BACKUP_FILE_DATE_TIME_FORMAT));
+ intent.putExtra(Intent.EXTRA_TITLE, fileName);
+
+ // Optionally, specify a URI for the directory that should be opened in
+ // the system file picker when your app creates the document.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, DownloadUtils.getBackupsDir().getUri());
+ }
+
+ startActivityForResult(intent, CREATE_FILE_REQUEST_CODE);
+ }
+
+
public interface OnResultListener {
void onResult(boolean result);
}
diff --git a/app/src/main/java/awais/instagrabber/dialogs/ProfilePicDialogFragment.java b/app/src/main/java/awais/instagrabber/dialogs/ProfilePicDialogFragment.java
index 94a67ec7..6db8a875 100644
--- a/app/src/main/java/awais/instagrabber/dialogs/ProfilePicDialogFragment.java
+++ b/app/src/main/java/awais/instagrabber/dialogs/ProfilePicDialogFragment.java
@@ -7,7 +7,6 @@ import android.graphics.Color;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
-import android.os.Environment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -16,7 +15,7 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.core.content.ContextCompat;
+import androidx.documentfile.provider.DocumentFile;
import androidx.fragment.app.DialogFragment;
import com.facebook.drawee.backends.pipeline.Fresco;
@@ -24,7 +23,7 @@ import com.facebook.drawee.controller.BaseControllerListener;
import com.facebook.drawee.interfaces.DraweeController;
import com.facebook.imagepipeline.image.ImageInfo;
-import java.io.File;
+// import java.io.File;
import awais.instagrabber.R;
import awais.instagrabber.customviews.drawee.AnimatedZoomableController;
@@ -36,6 +35,7 @@ import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.CoroutineUtilsKt;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils;
+import awais.instagrabber.utils.Utils;
import awais.instagrabber.webservices.UserRepository;
import kotlinx.coroutines.Dispatchers;
@@ -114,11 +114,11 @@ public class ProfilePicDialogFragment extends DialogFragment {
binding.download.setOnClickListener(v -> {
final Context context = getContext();
if (context == null) return;
- if (ContextCompat.checkSelfPermission(context, DownloadUtils.PERMS[0]) == PackageManager.PERMISSION_GRANTED) {
- downloadProfilePicture();
- return;
- }
- requestPermissions(DownloadUtils.PERMS, 8020);
+ // if (ContextCompat.checkSelfPermission(context, DownloadUtils.PERMS[0]) == PackageManager.PERMISSION_GRANTED) {
+ downloadProfilePicture();
+ // return;
+ // }
+ // requestPermissions(DownloadUtils.PERMS, 8020);
});
}
@@ -195,14 +195,18 @@ public class ProfilePicDialogFragment extends DialogFragment {
private void downloadProfilePicture() {
if (url == null) return;
- final File dir = new File(Environment.getExternalStorageDirectory(), "Download");
+ // final File dir = new File(Environment.getExternalStorageDirectory(), "Download");
final Context context = getContext();
if (context == null) return;
- if (dir.exists() || dir.mkdirs()) {
- final File saveFile = new File(dir, name + '_' + System.currentTimeMillis() + ".jpg");
- DownloadUtils.download(context, url, saveFile.getAbsolutePath());
- return;
- }
- Toast.makeText(context, R.string.downloader_error_creating_folder, Toast.LENGTH_SHORT).show();
+ // if (dir.exists() || dir.mkdirs()) {
+ //
+ // }
+ final String fileName = name + '_' + System.currentTimeMillis() + ".jpg";
+ // final File saveFile = new File(dir, fileName);
+ final DocumentFile downloadDir = DownloadUtils.getDownloadDir();
+ final DocumentFile saveFile = downloadDir.createFile(Utils.mimeTypeMap.getMimeTypeFromExtension("jpg"), fileName);
+ DownloadUtils.download(context, url, saveFile);
+ // return;
+ // Toast.makeText(context, R.string.downloader_error_creating_folder, Toast.LENGTH_SHORT).show();
}
}
diff --git a/app/src/main/java/awais/instagrabber/dialogs/RestoreBackupDialogFragment.java b/app/src/main/java/awais/instagrabber/dialogs/RestoreBackupDialogFragment.java
index 7ab5f0fd..c4d8ea87 100644
--- a/app/src/main/java/awais/instagrabber/dialogs/RestoreBackupDialogFragment.java
+++ b/app/src/main/java/awais/instagrabber/dialogs/RestoreBackupDialogFragment.java
@@ -1,12 +1,18 @@
package awais.instagrabber.dialogs;
import android.app.Dialog;
+import android.content.ContentResolver;
import android.content.Context;
-import android.content.pm.PackageManager;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
+import android.os.Looper;
+import android.provider.MediaStore;
import android.text.Editable;
import android.text.TextWatcher;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -15,30 +21,28 @@ import android.view.inputmethod.InputMethodManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment;
-import androidx.fragment.app.FragmentTransaction;
-
-import java.io.File;
import awais.instagrabber.databinding.DialogRestoreBackupBinding;
-import awais.instagrabber.utils.DirectoryChooser;
+import awais.instagrabber.utils.AppExecutors;
import awais.instagrabber.utils.ExportImportUtils;
import awais.instagrabber.utils.PasswordUtils.IncorrectPasswordException;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
-import static awais.instagrabber.fragments.settings.PreferenceKeys.FOLDER_PATH;
-import static awais.instagrabber.utils.DownloadUtils.PERMS;
+import static android.app.Activity.RESULT_OK;
public class RestoreBackupDialogFragment extends DialogFragment {
+ private static final String TAG = RestoreBackupDialogFragment.class.getSimpleName();
private static final int STORAGE_PERM_REQUEST_CODE = 8020;
+ private static final int OPEN_FILE_REQUEST_CODE = 1;
private OnResultListener onResultListener;
private DialogRestoreBackupBinding binding;
- private File file;
+ // private File file;
private boolean isEncrypted;
+ private Uri uri;
public RestoreBackupDialogFragment() {}
@@ -83,18 +87,62 @@ public class RestoreBackupDialogFragment extends DialogFragment {
@Override
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
- if (requestCode == STORAGE_PERM_REQUEST_CODE && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- showChooser();
+ // if (requestCode == STORAGE_PERM_REQUEST_CODE && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ // showChooser();
+ // }
+ }
+
+ @Override
+ public void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) {
+ if (data == null || data.getData() == null) return;
+ if (resultCode != RESULT_OK || requestCode != OPEN_FILE_REQUEST_CODE) return;
+ final Context context = getContext();
+ if (context == null) return;
+ isEncrypted = ExportImportUtils.isEncrypted(context, data.getData());
+ if (isEncrypted) {
+ binding.passwordGroup.setVisibility(View.VISIBLE);
+ binding.passwordGroup.post(() -> {
+ binding.etPassword.requestFocus();
+ binding.etPassword.post(() -> {
+ final InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
+ if (imm == null) return;
+ imm.showSoftInput(binding.etPassword, InputMethodManager.SHOW_IMPLICIT);
+ });
+ binding.btnRestore.setEnabled(!TextUtils.isEmpty(binding.etPassword.getText()));
+ });
+ } else {
+ binding.passwordGroup.setVisibility(View.GONE);
+ binding.btnRestore.setEnabled(true);
}
+ uri = data.getData();
+ AppExecutors.INSTANCE.getMainThread().execute(() -> {
+ Cursor c = null;
+ try {
+ String[] projection = {MediaStore.Files.FileColumns.DISPLAY_NAME};
+ final ContentResolver contentResolver = context.getContentResolver();
+ c = contentResolver.query(uri, projection, null, null, null);
+ if (c != null) {
+ while (c.moveToNext()) {
+ final String displayName = c.getString(0);
+ binding.filePath.setText(displayName);
+ }
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "onActivityResult: ", e);
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+ });
}
private void init() {
final Context context = getContext();
- if (context == null) {
- return;
- }
+ if (context == null) return;
binding.btnRestore.setEnabled(false);
- binding.btnRestore.setOnClickListener(v -> new Handler().post(() -> {
+ binding.btnRestore.setOnClickListener(v -> new Handler(Looper.getMainLooper()).post(() -> {
+ if (uri == null) return;
int flags = 0;
if (binding.cbFavorites.isChecked()) {
flags |= ExportImportUtils.FLAG_FAVORITES;
@@ -111,7 +159,7 @@ public class RestoreBackupDialogFragment extends DialogFragment {
ExportImportUtils.importData(
context,
flags,
- file,
+ uri,
!isEncrypted ? null : text.toString(),
result -> {
if (onResultListener != null) {
@@ -137,46 +185,56 @@ public class RestoreBackupDialogFragment extends DialogFragment {
@Override
public void afterTextChanged(final Editable s) {}
});
- if (ContextCompat.checkSelfPermission(context, PERMS[0]) == PackageManager.PERMISSION_GRANTED) {
- showChooser();
- return;
- }
- requestPermissions(PERMS, STORAGE_PERM_REQUEST_CODE);
- }
+ // if (ContextCompat.checkSelfPermission(context, PERMS[0]) == PackageManager.PERMISSION_GRANTED) {
+ // showChooser();
+ // return;
+ // }
+ // requestPermissions(PERMS, STORAGE_PERM_REQUEST_CODE);
+ final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+ // intent.addCategory(Intent.CATEGORY_OPENABLE);
+ intent.setType("*/*");
+ // intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{
+ // "application/pdf", // .pdf
+ // "application/vnd.oasis.opendocument.text", // .odt
+ // "text/plain" // .txt
+ // });
+ startActivityForResult(intent, OPEN_FILE_REQUEST_CODE);
- private void showChooser() {
- final String folderPath = Utils.settingsHelper.getString(FOLDER_PATH);
- final Context context = getContext();
- if (context == null) return;
- final DirectoryChooser directoryChooser = new DirectoryChooser()
- .setInitialDirectory(folderPath)
- .setShowBackupFiles(true)
- .setInteractionListener(file -> {
- isEncrypted = ExportImportUtils.isEncrypted(file);
- if (isEncrypted) {
- binding.passwordGroup.setVisibility(View.VISIBLE);
- binding.passwordGroup.post(() -> {
- binding.etPassword.requestFocus();
- binding.etPassword.post(() -> {
- final InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
- if (imm == null) return;
- imm.showSoftInput(binding.etPassword, InputMethodManager.SHOW_IMPLICIT);
- });
- binding.btnRestore.setEnabled(!TextUtils.isEmpty(binding.etPassword.getText()));
- });
- } else {
- binding.passwordGroup.setVisibility(View.GONE);
- binding.btnRestore.setEnabled(true);
- }
- this.file = file;
- binding.filePath.setText(file.getAbsolutePath());
- });
- directoryChooser.setEnterTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
- directoryChooser.setExitTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
- directoryChooser.setOnCancelListener(this::dismiss);
- directoryChooser.show(getChildFragmentManager(), "directory_chooser");
}
+ // private void showChooser() {
+ // final String folderPath = Utils.settingsHelper.getString(FOLDER_PATH);
+ // final Context context = getContext();
+ // if (context == null) return;
+ // final DirectoryChooser directoryChooser = new DirectoryChooser()
+ // .setInitialDirectory(folderPath)
+ // .setShowBackupFiles(true)
+ // .setInteractionListener(file -> {
+ // isEncrypted = ExportImportUtils.isEncrypted(file);
+ // if (isEncrypted) {
+ // binding.passwordGroup.setVisibility(View.VISIBLE);
+ // binding.passwordGroup.post(() -> {
+ // binding.etPassword.requestFocus();
+ // binding.etPassword.post(() -> {
+ // final InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
+ // if (imm == null) return;
+ // imm.showSoftInput(binding.etPassword, InputMethodManager.SHOW_IMPLICIT);
+ // });
+ // binding.btnRestore.setEnabled(!TextUtils.isEmpty(binding.etPassword.getText()));
+ // });
+ // } else {
+ // binding.passwordGroup.setVisibility(View.GONE);
+ // binding.btnRestore.setEnabled(true);
+ // }
+ // this.file = file;
+ // binding.filePath.setText(file.getAbsolutePath());
+ // });
+ // directoryChooser.setEnterTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
+ // directoryChooser.setExitTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
+ // directoryChooser.setOnCancelListener(this::dismiss);
+ // directoryChooser.show(getChildFragmentManager(), "directory_chooser");
+ // }
+
public interface OnResultListener {
void onResult(boolean result);
}
diff --git a/app/src/main/java/awais/instagrabber/fragments/CollectionPostsFragment.java b/app/src/main/java/awais/instagrabber/fragments/CollectionPostsFragment.java
index 1480f6e3..05212d19 100644
--- a/app/src/main/java/awais/instagrabber/fragments/CollectionPostsFragment.java
+++ b/app/src/main/java/awais/instagrabber/fragments/CollectionPostsFragment.java
@@ -27,7 +27,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
-import androidx.core.content.PermissionChecker;
import androidx.core.graphics.ColorUtils;
import androidx.fragment.app.Fragment;
import androidx.navigation.NavController;
@@ -65,9 +64,6 @@ import awais.instagrabber.utils.Utils;
import awais.instagrabber.webservices.CollectionService;
import awais.instagrabber.webservices.ServiceCallback;
-import static androidx.core.content.PermissionChecker.checkSelfPermission;
-import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION;
-
public class CollectionPostsFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "CollectionPostsFragment";
private static final int STORAGE_PERM_REQUEST_CODE = 8020;
@@ -106,12 +102,12 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay
if (CollectionPostsFragment.this.selectedFeedModels == null) return false;
final Context context = getContext();
if (context == null) return false;
- if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
- DownloadUtils.download(context, ImmutableList.copyOf(CollectionPostsFragment.this.selectedFeedModels));
- binding.posts.endSelection();
- return true;
- }
- requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION);
+ // if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
+ DownloadUtils.download(context, ImmutableList.copyOf(CollectionPostsFragment.this.selectedFeedModels));
+ binding.posts.endSelection();
+ // return true;
+ // }
+ // requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION);
}
return false;
}
@@ -141,13 +137,13 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay
public void onDownloadClick(final Media feedModel, final int childPosition) {
final Context context = getContext();
if (context == null) return;
- if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
- DownloadUtils.showDownloadDialog(context, feedModel, childPosition);
- return;
- }
- downloadFeedModel = feedModel;
- downloadChildPosition = -1;
- requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE);
+ // if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
+ DownloadUtils.showDownloadDialog(context, feedModel, childPosition);
+ // return;
+ // }
+ // downloadFeedModel = feedModel;
+ // downloadChildPosition = -1;
+ // requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE);
}
@Override
diff --git a/app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java b/app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java
index 6efd3711..873448da 100644
--- a/app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java
+++ b/app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java
@@ -105,7 +105,7 @@ import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.PostViewV2ViewModel;
-import static androidx.core.content.PermissionChecker.checkSelfPermission;
+//import static androidx.core.content.PermissionChecker.checkSelfPermission;
import static awais.instagrabber.fragments.HashTagFragment.ARG_HASHTAG;
import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_SHOWN_COUNT_TOOLTIP;
import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION;
@@ -119,6 +119,7 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme
private static final int STORAGE_PERM_REQUEST_CODE = 8020;
private DialogPostViewBinding binding;
+ private Context context;
private boolean detailsVisible = true;
private boolean video;
private VideoPlayerViewHelper videoPlayerViewHelper;
@@ -211,6 +212,12 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme
init();
}
+ @Override
+ public void onAttach(@NonNull final Context context) {
+ super.onAttach(context);
+ this.context = context;
+ }
+
@Override
public void onPause() {
super.onPause();
@@ -454,13 +461,7 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme
private void setupDownload() {
bottom.download.setOnClickListener(v -> {
- final Context context = getContext();
- if (context == null) return;
- if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
- DownloadUtils.showDownloadDialog(context, viewModel.getMedia(), sliderPosition);
- return;
- }
- requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE);
+ DownloadUtils.showDownloadDialog(context, viewModel.getMedia(), sliderPosition);
});
TooltipCompat.setTooltipText(bottom.download, getString(R.string.action_download));
}
diff --git a/app/src/main/java/awais/instagrabber/fragments/SavedViewerFragment.java b/app/src/main/java/awais/instagrabber/fragments/SavedViewerFragment.java
index 8ae5a963..d366a476 100644
--- a/app/src/main/java/awais/instagrabber/fragments/SavedViewerFragment.java
+++ b/app/src/main/java/awais/instagrabber/fragments/SavedViewerFragment.java
@@ -19,7 +19,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
-import androidx.core.content.PermissionChecker;
import androidx.fragment.app.Fragment;
import androidx.navigation.NavController;
import androidx.navigation.NavDirections;
@@ -46,8 +45,6 @@ import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
-import static androidx.core.content.PermissionChecker.checkSelfPermission;
-import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION;
import static awais.instagrabber.utils.Utils.settingsHelper;
public final class SavedViewerFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
@@ -88,12 +85,12 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL
if (SavedViewerFragment.this.selectedFeedModels == null) return false;
final Context context = getContext();
if (context == null) return false;
- if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
- DownloadUtils.download(context, ImmutableList.copyOf(SavedViewerFragment.this.selectedFeedModels));
- binding.posts.endSelection();
- return true;
- }
- requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION);
+ // if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
+ DownloadUtils.download(context, ImmutableList.copyOf(SavedViewerFragment.this.selectedFeedModels));
+ binding.posts.endSelection();
+ // return true;
+ // }
+ // requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION);
}
return false;
}
@@ -123,13 +120,13 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL
public void onDownloadClick(final Media feedModel, final int childPosition) {
final Context context = getContext();
if (context == null) return;
- if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
- DownloadUtils.showDownloadDialog(context, feedModel, childPosition);
- return;
- }
- downloadFeedModel = feedModel;
- downloadChildPosition = childPosition;
- requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE);
+ // if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
+ DownloadUtils.showDownloadDialog(context, feedModel, childPosition);
+ // return;
+ // }
+ // downloadFeedModel = feedModel;
+ // downloadChildPosition = childPosition;
+ // requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE);
}
@Override
diff --git a/app/src/main/java/awais/instagrabber/fragments/TopicPostsFragment.java b/app/src/main/java/awais/instagrabber/fragments/TopicPostsFragment.java
index 76374891..9e4474a1 100644
--- a/app/src/main/java/awais/instagrabber/fragments/TopicPostsFragment.java
+++ b/app/src/main/java/awais/instagrabber/fragments/TopicPostsFragment.java
@@ -24,7 +24,6 @@ import androidx.activity.OnBackPressedDispatcher;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
-import androidx.core.content.PermissionChecker;
import androidx.core.graphics.ColorUtils;
import androidx.fragment.app.Fragment;
import androidx.navigation.NavController;
@@ -60,9 +59,6 @@ import awais.instagrabber.utils.ResponseBodyUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.webservices.DiscoverService;
-import static androidx.core.content.PermissionChecker.checkSelfPermission;
-import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION;
-
public class TopicPostsFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = TopicPostsFragment.class.getSimpleName();
private static final int STORAGE_PERM_REQUEST_CODE = 8020;
@@ -99,12 +95,12 @@ public class TopicPostsFragment extends Fragment implements SwipeRefreshLayout.O
if (TopicPostsFragment.this.selectedFeedModels == null) return false;
final Context context = getContext();
if (context == null) return false;
- if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
- DownloadUtils.download(context, ImmutableList.copyOf(TopicPostsFragment.this.selectedFeedModels));
- binding.posts.endSelection();
- return true;
- }
- requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION);
+ // if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
+ DownloadUtils.download(context, ImmutableList.copyOf(TopicPostsFragment.this.selectedFeedModels));
+ binding.posts.endSelection();
+ return true;
+ // }
+ // requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION);
}
return false;
}
@@ -134,13 +130,13 @@ public class TopicPostsFragment extends Fragment implements SwipeRefreshLayout.O
public void onDownloadClick(final Media feedModel, final int childPosition) {
final Context context = getContext();
if (context == null) return;
- if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
- DownloadUtils.showDownloadDialog(context, feedModel, childPosition);
- return;
- }
- downloadFeedModel = feedModel;
- downloadChildPosition = -1;
- requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE);
+ // if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
+ DownloadUtils.showDownloadDialog(context, feedModel, childPosition);
+ // return;
+ // }
+ // downloadFeedModel = feedModel;
+ // downloadChildPosition = -1;
+ // requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE);
}
@Override
diff --git a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java
index 450b21e7..7c705042 100644
--- a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java
+++ b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java
@@ -1408,13 +1408,13 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
return;
}
- if (ContextCompat.checkSelfPermission(context, DownloadUtils.PERMS[0]) == PackageManager.PERMISSION_GRANTED) {
- DownloadUtils.download(context, media);
- Toast.makeText(context, R.string.downloader_downloading_media, Toast.LENGTH_SHORT).show();
- return;
- }
- tempMedia = media;
- requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE);
+ // if (ContextCompat.checkSelfPermission(context, DownloadUtils.PERMS[0]) == PackageManager.PERMISSION_GRANTED) {
+ DownloadUtils.download(context, media);
+ Toast.makeText(context, R.string.downloader_downloading_media, Toast.LENGTH_SHORT).show();
+ // return;
+ // }
+ // tempMedia = media;
+ // requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE);
}
@Nullable
diff --git a/app/src/main/java/awais/instagrabber/fragments/imageedit/ImageEditFragment.java b/app/src/main/java/awais/instagrabber/fragments/imageedit/ImageEditFragment.java
index 7586bdce..034b6706 100644
--- a/app/src/main/java/awais/instagrabber/fragments/imageedit/ImageEditFragment.java
+++ b/app/src/main/java/awais/instagrabber/fragments/imageedit/ImageEditFragment.java
@@ -183,11 +183,12 @@ public class ImageEditFragment extends Fragment {
if (context == null) return;
final Uri resultUri = viewModel.getResultUri().getValue();
if (resultUri == null) return;
- Utils.mediaScanFile(context, new File(resultUri.toString()), (path, uri) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
+ AppExecutors.INSTANCE.getMainThread().execute(() -> {
final NavController navController = NavHostFragment.findNavController(this);
setNavControllerResult(navController, resultUri);
navController.navigateUp();
- }));
+ });
+ // Utils.mediaScanFile(context, new File(resultUri.toString()), (path, uri) -> );
});
}
diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java
index 6a18c69a..3f76ffcc 100644
--- a/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java
+++ b/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java
@@ -1,27 +1,41 @@
package awais.instagrabber.fragments.settings;
import android.content.Context;
-import android.view.View;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.provider.DocumentsContract;
+import android.util.Log;
import androidx.annotation.NonNull;
-import androidx.appcompat.widget.AppCompatButton;
-import androidx.appcompat.widget.AppCompatTextView;
+import androidx.annotation.Nullable;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
-import androidx.preference.PreferenceViewHolder;
import androidx.preference.SwitchPreferenceCompat;
-import com.google.android.material.switchmaterial.SwitchMaterial;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
import awais.instagrabber.R;
-import awais.instagrabber.utils.DirectoryChooser;
+import awais.instagrabber.dialogs.ConfirmDialogFragment;
+import awais.instagrabber.utils.AppExecutors;
+import awais.instagrabber.utils.Constants;
+import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils;
+import awais.instagrabber.utils.Utils;
-import static awais.instagrabber.fragments.settings.PreferenceKeys.FOLDER_PATH;
-import static awais.instagrabber.fragments.settings.PreferenceKeys.FOLDER_SAVE_TO;
+import static android.app.Activity.RESULT_OK;
+import static awais.instagrabber.activities.DirectorySelectActivity.SELECT_DIR_REQUEST_CODE;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class DownloadsPreferencesFragment extends BasePreferencesFragment {
+ private static final String TAG = DownloadsPreferencesFragment.class.getSimpleName();
+ // private SaveToCustomFolderPreference.ResultCallback resultCallback;
+
@Override
void setupPreferenceScreen(final PreferenceScreen screen) {
final Context context = getContext();
@@ -40,13 +54,88 @@ public class DownloadsPreferencesFragment extends BasePreferencesFragment {
}
private Preference getSaveToCustomFolderPreference(@NonNull final Context context) {
- return new SaveToCustomFolderPreference(context, (resultCallback) -> new DirectoryChooser()
- .setInitialDirectory(settingsHelper.getString(FOLDER_PATH))
- .setInteractionListener(file -> {
- settingsHelper.putString(FOLDER_PATH, file.getAbsolutePath());
- resultCallback.onResult(file.getAbsolutePath());
- })
- .show(getParentFragmentManager(), null));
+ final Preference preference = new Preference(context);
+ preference.setKey(PreferenceKeys.PREF_BARINSTA_DIR_URI);
+ preference.setIconSpaceReserved(false);
+ preference.setTitle(R.string.barinsta_folder);
+ preference.setSummaryProvider(p -> {
+ final String currentValue = settingsHelper.getString(PreferenceKeys.PREF_BARINSTA_DIR_URI);
+ if (TextUtils.isEmpty(currentValue)) return "";
+ String path;
+ try {
+ path = URLDecoder.decode(currentValue, StandardCharsets.UTF_8.toString());
+ } catch (UnsupportedEncodingException e) {
+ path = currentValue;
+ }
+ return path;
+ });
+ preference.setOnPreferenceClickListener(p -> {
+ openDirectoryChooser(DownloadUtils.getRootDirUri());
+ return true;
+ });
+ return preference;
+ // return new SaveToCustomFolderPreference(context, checked -> {
+ // try {
+ // DownloadUtils.init(context);
+ // } catch (DownloadUtils.ReselectDocumentTreeException e) {
+ // if (!checked) return;
+ // startDocumentSelector(e.getInitialUri());
+ // } catch (Exception e) {
+ // Log.e(TAG, "getSaveToCustomFolderPreference: ", e);
+ // }
+ // }, (resultCallback) -> {
+ // // Choose a directory using the system's file picker.
+ // startDocumentSelector(null);
+ // this.resultCallback = resultCallback;
+ //
+ // // new DirectoryChooser()
+ // // .setInitialDirectory(settingsHelper.getString(FOLDER_PATH))
+ // // .setInteractionListener(file -> {
+ // // settingsHelper.putString(FOLDER_PATH, file.getAbsolutePath());
+ // // resultCallback.onResult(file.getAbsolutePath());
+ // // })
+ // // .show(getParentFragmentManager(), null);
+ // });
+ }
+
+ private void openDirectoryChooser(final Uri initialUri) {
+ final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && initialUri != null) {
+ intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, initialUri);
+ }
+ startActivityForResult(intent, SELECT_DIR_REQUEST_CODE);
+ }
+
+ @Override
+ public void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) {
+ if (requestCode != SELECT_DIR_REQUEST_CODE) return;
+ if (resultCode != RESULT_OK) return;
+ if (data == null || data.getData() == null) return;
+ final Context context = getContext();
+ if (context == null) return;
+ AppExecutors.INSTANCE.getMainThread().execute(() -> {
+ try {
+ Utils.setupSelectedDir(context, data);
+ } catch (Exception e) {
+ // Should not come to this point.
+ // If it does, we have to show this error to the user so that they can report it.
+ try (final StringWriter sw = new StringWriter();
+ final PrintWriter pw = new PrintWriter(sw)) {
+ e.printStackTrace(pw);
+ final ConfirmDialogFragment dialogFragment = ConfirmDialogFragment.newInstance(
+ 123,
+ R.string.error,
+ "Please report this error to the developers:\n\n" + sw.toString(),
+ R.string.ok,
+ 0,
+ 0
+ );
+ dialogFragment.show(getChildFragmentManager(), ConfirmDialogFragment.class.getSimpleName());
+ } catch (IOException ioException) {
+ Log.e(TAG, "onActivityResult: ", ioException);
+ }
+ }
+ }, 500);
}
private Preference getPrependUsernameToFilenamePreference(@NonNull final Context context) {
@@ -57,53 +146,74 @@ public class DownloadsPreferencesFragment extends BasePreferencesFragment {
return preference;
}
- public static class SaveToCustomFolderPreference extends Preference {
- private AppCompatTextView customPathTextView;
- private final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener;
- private final String key;
-
- public SaveToCustomFolderPreference(final Context context, final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener) {
- super(context);
- this.onSelectFolderButtonClickListener = onSelectFolderButtonClickListener;
- key = PreferenceKeys.FOLDER_SAVE_TO;
- setLayoutResource(R.layout.pref_custom_folder);
- setKey(key);
- setTitle(R.string.save_to_folder);
- setIconSpaceReserved(false);
- }
-
- @Override
- public void onBindViewHolder(final PreferenceViewHolder holder) {
- super.onBindViewHolder(holder);
- final SwitchMaterial cbSaveTo = (SwitchMaterial) holder.findViewById(R.id.cbSaveTo);
- final View buttonContainer = holder.findViewById(R.id.button_container);
- customPathTextView = (AppCompatTextView) holder.findViewById(R.id.custom_path);
- cbSaveTo.setOnCheckedChangeListener((buttonView, isChecked) -> {
- settingsHelper.putBoolean(FOLDER_SAVE_TO, isChecked);
- buttonContainer.setVisibility(isChecked ? View.VISIBLE : View.GONE);
- final String customPath = settingsHelper.getString(FOLDER_PATH);
- customPathTextView.setText(customPath);
- });
- final boolean savedToEnabled = settingsHelper.getBoolean(key);
- holder.itemView.setOnClickListener(v -> cbSaveTo.toggle());
- cbSaveTo.setChecked(savedToEnabled);
- buttonContainer.setVisibility(savedToEnabled ? View.VISIBLE : View.GONE);
- final AppCompatButton btnSaveTo = (AppCompatButton) holder.findViewById(R.id.btnSaveTo);
- btnSaveTo.setOnClickListener(v -> {
- if (onSelectFolderButtonClickListener == null) return;
- onSelectFolderButtonClickListener.onClick(result -> {
- if (TextUtils.isEmpty(result)) return;
- customPathTextView.setText(result);
- });
- });
- }
-
- public interface ResultCallback {
- void onResult(String result);
- }
-
- public interface OnSelectFolderButtonClickListener {
- void onClick(ResultCallback resultCallback);
- }
- }
+ // public static class SaveToCustomFolderPreference extends Preference {
+ // private AppCompatTextView customPathTextView;
+ // private final OnSaveToChangeListener onSaveToChangeListener;
+ // private final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener;
+ // private final String key;
+ //
+ // public SaveToCustomFolderPreference(final Context context,
+ // final OnSaveToChangeListener onSaveToChangeListener,
+ // final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener) {
+ // super(context);
+ // this.onSaveToChangeListener = onSaveToChangeListener;
+ // this.onSelectFolderButtonClickListener = onSelectFolderButtonClickListener;
+ // key = FOLDER_SAVE_TO;
+ // setLayoutResource(R.layout.pref_custom_folder);
+ // setKey(key);
+ // setTitle(R.string.save_to_folder);
+ // setIconSpaceReserved(false);
+ // }
+ //
+ // @Override
+ // public void onBindViewHolder(final PreferenceViewHolder holder) {
+ // super.onBindViewHolder(holder);
+ // final SwitchMaterial cbSaveTo = (SwitchMaterial) holder.findViewById(R.id.cbSaveTo);
+ // final View buttonContainer = holder.findViewById(R.id.button_container);
+ // customPathTextView = (AppCompatTextView) holder.findViewById(R.id.custom_path);
+ // cbSaveTo.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ // settingsHelper.putBoolean(FOLDER_SAVE_TO, isChecked);
+ // buttonContainer.setVisibility(isChecked ? View.VISIBLE : View.GONE);
+ // final Context context = getContext();
+ // String customPath = settingsHelper.getString(FOLDER_PATH);
+ // if (!TextUtils.isEmpty(customPath) && customPath.startsWith("content") && context != null) {
+ // final Uri uri = Uri.parse(customPath);
+ // final DocumentFile documentFile = DocumentFile.fromSingleUri(context, uri);
+ // try {
+ // customPath = Utils.getDocumentFileRealPath(context, documentFile).getAbsolutePath();
+ // } catch (Exception e) {
+ // Log.e(TAG, "onBindViewHolder: ", e);
+ // }
+ // }
+ // customPathTextView.setText(customPath);
+ // if (onSaveToChangeListener != null) {
+ // onSaveToChangeListener.onChange(isChecked);
+ // }
+ // });
+ // final boolean savedToEnabled = settingsHelper.getBoolean(key);
+ // holder.itemView.setOnClickListener(v -> cbSaveTo.toggle());
+ // cbSaveTo.setChecked(savedToEnabled);
+ // buttonContainer.setVisibility(savedToEnabled ? View.VISIBLE : View.GONE);
+ // final AppCompatButton btnSaveTo = (AppCompatButton) holder.findViewById(R.id.btnSaveTo);
+ // btnSaveTo.setOnClickListener(v -> {
+ // if (onSelectFolderButtonClickListener == null) return;
+ // onSelectFolderButtonClickListener.onClick(result -> {
+ // if (TextUtils.isEmpty(result)) return;
+ // customPathTextView.setText(result);
+ // });
+ // });
+ // }
+ //
+ // public interface ResultCallback {
+ // void onResult(String result);
+ // }
+ //
+ // public interface OnSelectFolderButtonClickListener {
+ // void onClick(ResultCallback resultCallback);
+ // }
+ //
+ // public interface OnSaveToChangeListener {
+ // void onChange(boolean checked);
+ // }
+ // }
}
diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.kt b/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.kt
index bdc8a77c..bf1b7edf 100644
--- a/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.kt
+++ b/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.kt
@@ -19,6 +19,7 @@ object PreferenceKeys {
const val APP_THEME = "app_theme_v19"
const val APP_LANGUAGE = "app_language_v19"
const val STORY_SORT = "story_sort"
+ const val PREF_BARINSTA_DIR_URI = "barinsta_dir_uri"
// set string prefs
const val KEYWORD_FILTERS = "keyword_filters"
diff --git a/app/src/main/java/awais/instagrabber/services/DeleteImageIntentService.java b/app/src/main/java/awais/instagrabber/services/DeleteImageIntentService.java
index b9442527..ceeb2c7c 100644
--- a/app/src/main/java/awais/instagrabber/services/DeleteImageIntentService.java
+++ b/app/src/main/java/awais/instagrabber/services/DeleteImageIntentService.java
@@ -4,13 +4,14 @@ import android.app.IntentService;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
+import android.net.Uri;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationManagerCompat;
+import androidx.documentfile.provider.DocumentFile;
-import java.io.File;
import java.util.Random;
import awais.instagrabber.utils.TextUtils;
@@ -39,7 +40,10 @@ public class DeleteImageIntentService extends IntentService {
if (intent != null && Intent.ACTION_DELETE.equals(intent.getAction()) && intent.hasExtra(EXTRA_IMAGE_PATH)) {
final String path = intent.getStringExtra(EXTRA_IMAGE_PATH);
if (TextUtils.isEmpty(path)) return;
- final File file = new File(path);
+ // final File file = new File(path);
+ final Uri parse = Uri.parse(path);
+ if (parse == null) return;
+ final DocumentFile file = DocumentFile.fromSingleUri(getApplicationContext(), parse);
boolean deleted;
if (file.exists()) {
deleted = file.delete();
@@ -58,11 +62,11 @@ public class DeleteImageIntentService extends IntentService {
@NonNull
public static PendingIntent pendingIntent(@NonNull final Context context,
- @NonNull final String imagePath,
+ @NonNull final DocumentFile imagePath,
final int notificationId) {
final Intent intent = new Intent(context, DeleteImageIntentService.class);
intent.setAction(Intent.ACTION_DELETE);
- intent.putExtra(EXTRA_IMAGE_PATH, imagePath);
+ intent.putExtra(EXTRA_IMAGE_PATH, imagePath.getUri().toString());
intent.putExtra(EXTRA_NOTIFICATION_ID, notificationId);
return PendingIntent.getService(context, random.nextInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
diff --git a/app/src/main/java/awais/instagrabber/utils/BitmapUtils.kt b/app/src/main/java/awais/instagrabber/utils/BitmapUtils.kt
index 379ea8db..4d6abced 100644
--- a/app/src/main/java/awais/instagrabber/utils/BitmapUtils.kt
+++ b/app/src/main/java/awais/instagrabber/utils/BitmapUtils.kt
@@ -8,6 +8,7 @@ import android.net.Uri
import android.util.Log
import android.util.LruCache
import androidx.core.util.Pair
+import androidx.documentfile.provider.DocumentFile
import awais.instagrabber.utils.extensions.TAG
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@@ -192,9 +193,9 @@ object BitmapUtils {
}
@Throws(IOException::class)
- fun convertToJpegAndSaveToFile(bitmap: Bitmap, file: File?): File {
- val tempFile = file ?: DownloadUtils.getTempFile()
- FileOutputStream(tempFile).use { output ->
+ fun convertToJpegAndSaveToFile(contentResolver: ContentResolver, bitmap: Bitmap, file: DocumentFile?): DocumentFile {
+ val tempFile = file ?: DownloadUtils.getTempFile(null, "jpg")
+ contentResolver.openOutputStream(tempFile.uri).use { output ->
val compressResult = bitmap.compress(Bitmap.CompressFormat.JPEG, 100, output)
if (!compressResult) {
throw RuntimeException("Compression failed!")
diff --git a/app/src/main/java/awais/instagrabber/utils/Constants.kt b/app/src/main/java/awais/instagrabber/utils/Constants.kt
index 8cf96e03..82b1f87e 100644
--- a/app/src/main/java/awais/instagrabber/utils/Constants.kt
+++ b/app/src/main/java/awais/instagrabber/utils/Constants.kt
@@ -89,4 +89,5 @@ object Constants {
const val DM_THREAD_ACTION_EXTRA_THREAD_ID = "thread_id"
const val DM_THREAD_ACTION_EXTRA_THREAD_TITLE = "thread_title"
const val X_IG_APP_ID = "936619743392459"
+ const val EXTRA_INITIAL_URI = "initial_uri"
}
\ No newline at end of file
diff --git a/app/src/main/java/awais/instagrabber/utils/DirectoryChooser.java b/app/src/main/java/awais/instagrabber/utils/DirectoryChooser.java
index d349e490..1ea880a5 100755
--- a/app/src/main/java/awais/instagrabber/utils/DirectoryChooser.java
+++ b/app/src/main/java/awais/instagrabber/utils/DirectoryChooser.java
@@ -3,7 +3,6 @@ package awais.instagrabber.utils;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Environment;
@@ -11,19 +10,14 @@ import android.os.FileObserver;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
-import com.google.android.material.snackbar.BaseTransientBottomBar;
-import com.google.android.material.snackbar.Snackbar;
-
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
@@ -95,15 +89,15 @@ public final class DirectoryChooser extends DialogFragment {
if (context == null) context = getContext();
if (context == null) context = getActivity();
if (context == null) return;
- if (ContextCompat.checkSelfPermission(context, DownloadUtils.PERMS[0]) != PackageManager.PERMISSION_GRANTED) {
- final String text = "Storage permissions denied!";
- if (container == null) {
- Toast.makeText(context, text, Toast.LENGTH_LONG).show();
- } else {
- Snackbar.make(container, text, BaseTransientBottomBar.LENGTH_LONG).show();
- }
- dismiss();
- }
+ // if (ContextCompat.checkSelfPermission(context, DownloadUtils.PERMS[0]) != PackageManager.PERMISSION_GRANTED) {
+ // final String text = "Storage permissions denied!";
+ // if (container == null) {
+ // Toast.makeText(context, text, Toast.LENGTH_LONG).show();
+ // } else {
+ // Snackbar.make(container, text, BaseTransientBottomBar.LENGTH_LONG).show();
+ // }
+ // dismiss();
+ // }
final View.OnClickListener clickListener = v -> {
if (v == binding.btnConfirm) {
if (interactionListener != null && isValidFile(selectedDir))
diff --git a/app/src/main/java/awais/instagrabber/utils/DirectoryUtils.java b/app/src/main/java/awais/instagrabber/utils/DirectoryUtils.java
index 1ad6a531..205239c1 100644
--- a/app/src/main/java/awais/instagrabber/utils/DirectoryUtils.java
+++ b/app/src/main/java/awais/instagrabber/utils/DirectoryUtils.java
@@ -1,7 +1,5 @@
package awais.instagrabber.utils;
-import android.content.Context;
-import android.os.Build;
import android.os.Environment;
import java.io.File;
@@ -10,8 +8,6 @@ import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;
-import awais.instagrabber.R;
-
public class DirectoryUtils {
private static final Pattern DIR_SEPORATOR = Pattern.compile("/");
@@ -73,22 +69,22 @@ public class DirectoryUtils {
return rv;
}
- public static File getOutputMediaDirectory(final Context context, final String... dirs) {
- if (context == null) return null;
- final File[] externalMediaDirs = context.getExternalMediaDirs();
- if (externalMediaDirs == null || externalMediaDirs.length == 0) return context.getFilesDir();
- final File externalMediaDir = externalMediaDirs[0];
- File subDir = new File(externalMediaDir, context.getString(R.string.app_name));
- if (dirs != null) {
- for (final String dir : dirs) {
- subDir = new File(subDir, dir);
- //noinspection ResultOfMethodCallIgnored
- subDir.mkdirs();
- }
- }
- if (!subDir.exists()) {
- return context.getFilesDir();
- }
- return subDir;
- }
+ // public static File getOutputMediaDirectory(final Context context, final String... dirs) {
+ // if (context == null) return null;
+ // final File[] externalMediaDirs = context.getExternalMediaDirs();
+ // if (externalMediaDirs == null || externalMediaDirs.length == 0) return context.getFilesDir();
+ // final File externalMediaDir = externalMediaDirs[0];
+ // File subDir = new File(externalMediaDir, context.getString(R.string.app_name));
+ // if (dirs != null) {
+ // for (final String dir : dirs) {
+ // subDir = new File(subDir, dir);
+ // //noinspection ResultOfMethodCallIgnored
+ // subDir.mkdirs();
+ // }
+ // }
+ // if (!subDir.exists()) {
+ // return context.getFilesDir();
+ // }
+ // return subDir;
+ // }
}
diff --git a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java
index be24dd48..755b6198 100644
--- a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java
+++ b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java
@@ -1,9 +1,11 @@
package awais.instagrabber.utils;
-import android.Manifest;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
-import android.os.Environment;
+import android.content.UriPermission;
+import android.Manifest;
+import android.net.Uri;
import android.util.Log;
import android.webkit.MimeTypeMap;
import android.widget.Toast;
@@ -11,6 +13,8 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
+import androidx.core.util.Pair;
+import androidx.documentfile.provider.DocumentFile;
import androidx.work.Constraints;
import androidx.work.Data;
import androidx.work.NetworkType;
@@ -21,9 +25,9 @@ import androidx.work.WorkRequest;
import com.google.gson.Gson;
import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileWriter;
import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
@@ -42,51 +46,159 @@ import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.VideoVersion;
import awais.instagrabber.workers.DownloadWorker;
-import static awais.instagrabber.fragments.settings.PreferenceKeys.FOLDER_PATH;
-import static awais.instagrabber.fragments.settings.PreferenceKeys.FOLDER_SAVE_TO;
+import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_BARINSTA_DIR_URI;
public final class DownloadUtils {
- private static final String TAG = "DownloadUtils";
+ private static final String TAG = DownloadUtils.class.getSimpleName();
+ // private static final String DIR_BARINSTA = "Barinsta";
+ private static final String DIR_DOWNLOADS = "Downloads";
+ private static final String DIR_CAMERA = "Camera";
+ private static final String DIR_EDIT = "Edit";
+ private static final String DIR_RECORDINGS = "Sent Recordings";
+ private static final String DIR_TEMP = "Temp";
+ private static final String DIR_BACKUPS = "Backups";
+
+ private static DocumentFile root;
public static final String WRITE_PERMISSION = Manifest.permission.WRITE_EXTERNAL_STORAGE;
public static final String[] PERMS = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE};
- @NonNull
- private static File getDownloadDir() {
- File dir = new File(Environment.getExternalStorageDirectory(), "Download");
+ public static void init(@NonNull final Context context) throws ReselectDocumentTreeException {
+ final String barinstaDirUri = Utils.settingsHelper.getString(PREF_BARINSTA_DIR_URI);
+ if (TextUtils.isEmpty(barinstaDirUri)) {
+ throw new ReselectDocumentTreeException("folder path is null or empty");
+ }
+ if (!barinstaDirUri.startsWith("content")) {
+ // reselect the folder in selector view
+ throw new ReselectDocumentTreeException(Uri.parse(barinstaDirUri));
+ }
+ final Uri uri = Uri.parse(barinstaDirUri);
+ final List existingPermissions = context.getContentResolver().getPersistedUriPermissions();
+ if (existingPermissions.isEmpty()) {
+ // reselect the folder in selector view
+ throw new ReselectDocumentTreeException(uri);
+ }
+ final boolean anyMatch = existingPermissions.stream().anyMatch(uriPermission -> uriPermission.getUri().equals(uri));
+ if (!anyMatch) {
+ // reselect the folder in selector view
+ throw new ReselectDocumentTreeException(uri);
+ }
+ root = DocumentFile.fromTreeUri(context, uri);
+ if (root == null || !root.exists() || root.lastModified() == 0) {
+ root = null;
+ throw new ReselectDocumentTreeException(uri);
+ }
+ }
- if (Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) {
- final String customPath = Utils.settingsHelper.getString(FOLDER_PATH);
- if (!TextUtils.isEmpty(customPath)) {
- dir = new File(customPath);
+ public static void destroy() {
+ root = null;
+ }
+
+ @Nullable
+ public static DocumentFile getDownloadDir(final String... dirs) {
+ if (root == null) {
+ return null;
+ }
+ DocumentFile subDir = root;
+ if (dirs != null) {
+ for (final String dir : dirs) {
+ if (subDir == null || TextUtils.isEmpty(dir)) continue;
+ final DocumentFile subDirFile = subDir.findFile(dir);
+ final boolean exists = subDirFile != null && subDirFile.exists();
+ subDir = exists ? subDirFile : subDir.createDirectory(dir);
}
}
- return dir;
+ return subDir;
}
@Nullable
- private static File getDownloadDir(@NonNull final Context context, @Nullable final String username) {
- return getDownloadDir(context, username, false);
+ public static DocumentFile getDownloadDir() {
+ // final File parent = new File(Environment.getExternalStorageDirectory(), Environment.DIRECTORY_DOWNLOADS);
+ // final File dir = new File(new File(parent, "barinsta"), "downloads");
+ // if (!dir.exists()) {
+ // final boolean mkdirs = dir.mkdirs();
+ // if (!mkdirs) {
+ // Log.e(TAG, "getDownloadDir: failed to create dir");
+ // }
+ // }
+ // if (Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) {
+ // final String customPath = Utils.settingsHelper.getString(FOLDER_PATH);
+ // if (!TextUtils.isEmpty(customPath)) {
+ // dir = new File(customPath);
+ // }
+ // }
+ return getDownloadDir(DIR_DOWNLOADS);
}
@Nullable
- private static File getDownloadDir(final Context context,
- @Nullable final String username,
- final boolean skipCreateDir) {
- File dir = getDownloadDir();
-
- if (Utils.settingsHelper.getBoolean(PreferenceKeys.DOWNLOAD_USER_FOLDER) && !TextUtils.isEmpty(username)) {
- final String finaleUsername = username.startsWith("@") ? username.substring(1) : username;
- dir = new File(dir, finaleUsername);
- }
+ public static DocumentFile getCameraDir() {
+ return getDownloadDir(DIR_CAMERA);
+ }
- if (context != null && !skipCreateDir && !dir.exists() && !dir.mkdirs()) {
+ @Nullable
+ public static DocumentFile getImageEditDir(final String sessionId) {
+ return getDownloadDir(DIR_EDIT, sessionId);
+ }
+
+ @Nullable
+ public static DocumentFile getRecordingsDir() {
+ return getDownloadDir(DIR_RECORDINGS);
+ }
+
+ @Nullable
+ public static DocumentFile getBackupsDir() {
+ return getDownloadDir(DIR_BACKUPS);
+ }
+
+ // @Nullable
+ // private static DocumentFile getDownloadDir(@NonNull final Context context, @Nullable final String username) {
+ // return getDownloadDir(context, username, false);
+ // }
+
+ @Nullable
+ private static DocumentFile getDownloadDir(final Context context,
+ @Nullable final String username) {
+ final List userFolderPaths = getSubPathForUserFolder(username);
+ DocumentFile dir = root;
+ for (final String dirName : userFolderPaths) {
+ final DocumentFile file = dir.findFile(dirName);
+ if (file != null) {
+ dir = file;
+ continue;
+ }
+ dir = dir.createDirectory(dirName);
+ if (dir == null) break;
+ }
+ // final String joined = android.text.TextUtils.join("/", userFolderPaths);
+ // final Uri userFolderUri = DocumentsContract.buildDocumentUriUsingTree(root.getUri(), joined);
+ // final DocumentFile userFolder = DocumentFile.fromSingleUri(context, userFolderUri);
+ if (context != null && (dir == null || !dir.exists())) {
Toast.makeText(context, R.string.error_creating_folders, Toast.LENGTH_SHORT).show();
return null;
}
return dir;
}
+ private static List getSubPathForUserFolder(final String username) {
+ final List list = new ArrayList<>();
+ if (!Utils.settingsHelper.getBoolean(PreferenceKeys.DOWNLOAD_USER_FOLDER) || TextUtils.isEmpty(username)) {
+ list.add(DIR_DOWNLOADS);
+ return list;
+ }
+ final String finalUsername = username.startsWith("@") ? username.substring(1) : username;
+ list.add(DIR_DOWNLOADS);
+ list.add(finalUsername);
+ return list;
+ }
+
+ private static DocumentFile getTempDir() {
+ DocumentFile file = root.findFile(DIR_TEMP);
+ if (file == null) {
+ file = root.createDirectory(DIR_TEMP);
+ }
+ return file;
+ }
+
// public static void dmDownload(@NonNull final Context context,
// @Nullable final String username,
// final String modelId,
@@ -99,70 +211,82 @@ public final class DownloadUtils {
// }
// }
-// private static void dmDownloadImpl(@NonNull final Context context,
-// @Nullable final String username,
-// final String modelId,
-// final String url) {
-// final File dir = getDownloadDir(context, username);
-// if (dir.exists() || dir.mkdirs()) {
-// download(context,
-// url,
-// getDownloadSaveFile(dir, modelId, url).getAbsolutePath());
-// return;
-// }
-// Toast.makeText(context, R.string.error_creating_folders, Toast.LENGTH_SHORT).show();
-// }
-
- @NonNull
- private static File getDownloadSaveFile(final File finalDir,
- final String postId,
- final String displayUrl) {
- return getDownloadSaveFile(finalDir, postId, "", displayUrl, "");
+ // private static void dmDownloadImpl(@NonNull final Context context,
+ // @Nullable final String username,
+ // final String modelId,
+ // final String url) {
+ // final DocumentFile dir = getDownloadDir(context, username);
+ // if (dir != null && dir.exists()) {
+ // download(context, url, getDownloadSavePaths(dir, modelId, url));
+ // return;
+ // }
+ // Toast.makeText(context, R.string.error_creating_folders, Toast.LENGTH_SHORT).show();
+ // }
+
+ private static Pair, String> getDownloadSavePaths(final List paths,
+ final String postId,
+ final String displayUrl) {
+ return getDownloadSavePaths(paths, postId, "", displayUrl, "");
}
- @NonNull
- private static File getDownloadSaveFile(final File finalDir,
- final String postId,
- final String displayUrl,
- final String username) {
- return getDownloadSaveFile(finalDir, postId, "", displayUrl, username);
+ private static Pair, String> getDownloadSavePaths(final List paths,
+ final String postId,
+ final String displayUrl,
+ final String username) {
+ return getDownloadSavePaths(paths, postId, "", displayUrl, username);
}
- private static File getDownloadChildSaveFile(final File downloadDir,
- final String postId,
- final int childPosition,
- final String url,
- final String username) {
+ private static Pair, String> getDownloadChildSavePaths(final List paths,
+ final String postId,
+ final int childPosition,
+ final String url,
+ final String username) {
final String sliderPostfix = "_slide_" + childPosition;
- return getDownloadSaveFile(downloadDir, postId, sliderPostfix, url, username);
+ return getDownloadSavePaths(paths, postId, sliderPostfix, url, username);
}
- @NonNull
- private static File getDownloadSaveFile(final File finalDir,
- final String postId,
- final String sliderPostfix,
- final String displayUrl,
- final String username) {
+ private static Pair, String> getDownloadSavePaths(final List paths,
+ final String postId,
+ final String sliderPostfix,
+ final String displayUrl,
+ final String username) {
+ if (paths == null) return null;
+ final String extension = getFileExtensionFromUrl(displayUrl);
final String usernamePrepend = TextUtils.isEmpty(username) ? "" : (username + "_");
- final String fileName = usernamePrepend + postId + sliderPostfix + getFileExtensionFromUrl(displayUrl);
- return new File(finalDir, fileName);
+ final String fileName = usernamePrepend + postId + sliderPostfix + extension;
+ // return new File(finalDir, fileName);
+ // DocumentFile file = finalDir.findFile(fileName);
+ // if (file == null) {
+ final String mimeType = Utils.mimeTypeMap.getMimeTypeFromExtension(extension.startsWith(".") ? extension.substring(1) : extension);
+ // file = finalDir.createFile(mimeType, fileName);
+ // }
+ paths.add(fileName);
+ return new Pair<>(paths, mimeType);
}
- @NonNull
- public static File getTempFile() {
- return getTempFile(null, null);
- }
+ // public static DocumentFile getTempFile() {
+ // return getTempFile(null, null);
+ // }
- public static File getTempFile(final String fileName, final String extension) {
- final File dir = getDownloadDir();
+ public static DocumentFile getTempFile(final String fileName, final String extension) {
+ final DocumentFile dir = getTempDir();
String name = fileName;
if (TextUtils.isEmpty(name)) {
name = UUID.randomUUID().toString();
}
+ String mimeType = "application/octet-stream";
if (!TextUtils.isEmpty(extension)) {
name += "." + extension;
+ final String mimeType1 = Utils.mimeTypeMap.getMimeTypeFromExtension(extension);
+ if (mimeType1 != null) {
+ mimeType = mimeType1;
+ }
+ }
+ DocumentFile file = dir.findFile(name);
+ if (file == null) {
+ file = dir.createFile(mimeType, name);
}
- return new File(dir, name);
+ return file;
}
/**
@@ -212,14 +336,20 @@ public final class DownloadUtils {
if (user != null) {
username = user.getUsername();
}
- final File downloadDir = getDownloadDir(null, "@" + username, true);
+ final List userFolderPaths = getSubPathForUserFolder(username);
switch (media.getMediaType()) {
case MEDIA_TYPE_IMAGE:
case MEDIA_TYPE_VIDEO: {
final String url = ResponseBodyUtils.getImageUrl(media);
- final File file = getDownloadSaveFile(downloadDir, media.getCode(), url, "");
- final File usernamePrependedFile = getDownloadSaveFile(downloadDir, media.getCode(), url, username);
- checkList.add(file.exists() || usernamePrependedFile.exists());
+ final Pair, String> file = getDownloadSavePaths(new ArrayList<>(userFolderPaths), media.getCode(), url, "");
+ final boolean fileExists = file.first != null && checkPathExists(file.first);
+ boolean usernameFileExists = false;
+ if (!fileExists) {
+ final Pair, String> usernameFile = getDownloadSavePaths(
+ new ArrayList<>(userFolderPaths), media.getCode(), url, username);
+ usernameFileExists = usernameFile.first != null && checkPathExists(usernameFile.first);
+ }
+ checkList.add(fileExists || usernameFileExists);
break;
}
case MEDIA_TYPE_SLIDER:
@@ -228,9 +358,16 @@ public final class DownloadUtils {
final Media child = sliderItems.get(i);
if (child == null) continue;
final String url = ResponseBodyUtils.getImageUrl(child);
- final File file = getDownloadChildSaveFile(downloadDir, media.getCode(), i + 1, url, "");
- final File usernamePrependedFile = getDownloadChildSaveFile(downloadDir, media.getCode(), i + 1, url, username);
- checkList.add(file.exists() || usernamePrependedFile.exists());
+ final Pair, String> file = getDownloadChildSavePaths(
+ new ArrayList<>(userFolderPaths), media.getCode(), i + 1, url, "");
+ final boolean fileExists = file.first != null && checkPathExists(file.first);
+ boolean usernameFileExists = false;
+ if (!fileExists) {
+ final Pair, String> usernameFile = getDownloadChildSavePaths(
+ new ArrayList<>(userFolderPaths), media.getCode(), i + 1, url, username);
+ usernameFileExists = usernameFile.first != null && checkPathExists(usernameFile.first);
+ }
+ checkList.add(fileExists || usernameFileExists);
}
break;
default:
@@ -238,6 +375,18 @@ public final class DownloadUtils {
return checkList;
}
+ private static boolean checkPathExists(@NonNull final List paths) {
+ if (root == null) return false;
+ DocumentFile dir = root;
+ for (final String path : paths) {
+ dir = dir.findFile(path);
+ if (dir == null || !dir.exists()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
public static void showDownloadDialog(@NonNull Context context,
@NonNull final Media feedModel,
final int childPosition) {
@@ -272,17 +421,25 @@ public final class DownloadUtils {
public static void download(@NonNull final Context context,
@NonNull final StoryModel storyModel) {
- final File downloadDir = getDownloadDir(context, "@" + storyModel.getUsername());
+ final DocumentFile downloadDir = getDownloadDir(context, storyModel.getUsername());
+ if (downloadDir == null) return;
final String url = storyModel.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO
? storyModel.getVideoUrl()
: storyModel.getStoryUrl();
+ final String extension = DownloadUtils.getFileExtensionFromUrl(url);
final String baseFileName = storyModel.getStoryMediaId() + "_"
- + storyModel.getTimestamp() + DownloadUtils.getFileExtensionFromUrl(url);
+ + storyModel.getTimestamp() + extension;
final String usernamePrepend = Utils.settingsHelper.getBoolean(PreferenceKeys.DOWNLOAD_PREPEND_USER_NAME)
- && storyModel.getUsername() != null ? storyModel.getUsername() + "_" : "";
- final File saveFile = new File(downloadDir,
- usernamePrepend + baseFileName);
- download(context, url, saveFile.getAbsolutePath());
+ && storyModel.getUsername() != null ? storyModel.getUsername() + "_" : "";
+ final String fileName = usernamePrepend + baseFileName;
+ DocumentFile saveFile = downloadDir.findFile(fileName);
+ if (saveFile == null) {
+ final String mimeType = Utils.mimeTypeMap.getMimeTypeFromExtension(extension.startsWith(".") ? extension.substring(1) : extension);
+ if (mimeType == null) return;
+ saveFile = downloadDir.createFile(mimeType, fileName);
+ }
+ // final File saveFile = new File(downloadDir, fileName);
+ download(context, url, saveFile);
}
public static void download(@NonNull final Context context,
@@ -304,11 +461,12 @@ public final class DownloadUtils {
private static void download(@NonNull final Context context,
@NonNull final List feedModels,
final int childPositionIfSingle) {
- final Map map = new HashMap<>();
+ final Map map = new HashMap<>();
for (final Media media : feedModels) {
final User mediaUser = media.getUser();
- final File downloadDir = getDownloadDir(context, mediaUser == null ? "" : mediaUser.getUsername());
- if (downloadDir == null) return;
+ final String username = mediaUser == null ? "" : mediaUser.getUsername();
+ final List userFolderPaths = getSubPathForUserFolder(username);
+ // final DocumentFile downloadDir = getDownloadDir(context, mediaUser == null ? "" : mediaUser.getUsername());
switch (media.getMediaType()) {
case MEDIA_TYPE_IMAGE:
case MEDIA_TYPE_VIDEO: {
@@ -323,8 +481,10 @@ public final class DownloadUtils {
fileName = mediaUser.getUsername() + "_" + fileName;
}
}
- final File file = getDownloadSaveFile(downloadDir, fileName, url);
- map.put(url, file.getAbsolutePath());
+ final Pair, String> pair = getDownloadSavePaths(userFolderPaths, fileName, url);
+ final DocumentFile file = createFile(pair);
+ if (file == null) continue;
+ map.put(url, file);
break;
}
case MEDIA_TYPE_VOICE: {
@@ -333,29 +493,54 @@ public final class DownloadUtils {
if (mediaUser != null) {
fileName = mediaUser.getUsername() + "_" + fileName;
}
- final File file = getDownloadSaveFile(downloadDir, fileName, url);
- map.put(url, file.getAbsolutePath());
+ final Pair, String> pair = getDownloadSavePaths(userFolderPaths, fileName, url);
+ final DocumentFile file = createFile(pair);
+ if (file == null) continue;
+ map.put(url, file);
break;
}
case MEDIA_TYPE_SLIDER:
final List sliderItems = media.getCarouselMedia();
for (int i = 0; i < sliderItems.size(); i++) {
- if (childPositionIfSingle >= 0 && feedModels.size() == 1 && i != childPositionIfSingle) {
- continue;
- }
+ if (childPositionIfSingle >= 0 && feedModels.size() == 1 && i != childPositionIfSingle) continue;
final Media child = sliderItems.get(i);
final String url = getUrlOfType(child);
- final String usernamePrepend = Utils.settingsHelper.getBoolean(PreferenceKeys.DOWNLOAD_PREPEND_USER_NAME) && mediaUser != null ? mediaUser.getUsername() : "";
- final File file = getDownloadChildSaveFile(downloadDir, media.getCode(), i + 1, url, usernamePrepend);
- map.put(url, file.getAbsolutePath());
+ final String usernamePrepend = Utils.settingsHelper.getBoolean(PreferenceKeys.DOWNLOAD_PREPEND_USER_NAME) && mediaUser != null
+ ? mediaUser.getUsername()
+ : "";
+ final Pair, String> pair = getDownloadChildSavePaths(
+ new ArrayList<>(userFolderPaths), media.getCode(), i + 1, url, usernamePrepend);
+ final DocumentFile file = createFile(pair);
+ if (file == null) continue;
+ map.put(url, file);
}
break;
default:
}
}
+ if (map.isEmpty()) return;
download(context, map);
}
+ @Nullable
+ private static DocumentFile createFile(@NonNull final Pair, String> pair) {
+ if (root == null) return null;
+ if (pair.first == null || pair.second == null) return null;
+ DocumentFile dir = root;
+ final List first = pair.first;
+ for (int i = 0; i < first.size(); i++) {
+ final String name = first.get(i);
+ final DocumentFile file = dir.findFile(name);
+ if (file != null) {
+ dir = file;
+ continue;
+ }
+ dir = i == first.size() - 1 ? dir.createFile(pair.second, name) : dir.createDirectory(name);
+ if (dir == null) break;
+ }
+ return dir;
+ }
+
@Nullable
private static String getUrlOfType(@NonNull final Media media) {
switch (media.getMediaType()) {
@@ -387,12 +572,13 @@ public final class DownloadUtils {
public static void download(final Context context,
final String url,
- final String filePath) {
+ final DocumentFile filePath) {
if (context == null || url == null || filePath == null) return;
download(context, Collections.singletonMap(url, filePath));
}
- private static void download(final Context context, final Map urlFilePathMap) {
+ private static void download(final Context context, final Map urlFilePathMap) {
+ if (context == null) return;
final Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build();
@@ -400,19 +586,25 @@ public final class DownloadUtils {
.setUrlToFilePathMap(urlFilePathMap)
.build();
final String requestJson = new Gson().toJson(request);
- final File tempFile = getTempFile();
- try (BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile))) {
+ final DocumentFile tempFile = getTempFile(null, "json");
+ if (tempFile == null) {
+ Log.e(TAG, "download: temp file is null");
+ return;
+ }
+ final Uri uri = tempFile.getUri();
+ final ContentResolver contentResolver = context.getContentResolver();
+ if (contentResolver == null) return;
+ try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(contentResolver.openOutputStream(uri)))) {
writer.write(requestJson);
} catch (IOException e) {
Log.e(TAG, "download: Error writing request to file", e);
- //noinspection ResultOfMethodCallIgnored
tempFile.delete();
return;
}
final WorkRequest downloadWorkRequest = new OneTimeWorkRequest.Builder(DownloadWorker.class)
.setInputData(
new Data.Builder()
- .putString(DownloadWorker.KEY_DOWNLOAD_REQUEST_JSON, tempFile.getAbsolutePath())
+ .putString(DownloadWorker.KEY_DOWNLOAD_REQUEST_JSON, tempFile.getUri().toString())
.build()
)
.setConstraints(constraints)
@@ -421,4 +613,30 @@ public final class DownloadUtils {
WorkManager.getInstance(context)
.enqueue(downloadWorkRequest);
}
+
+ @Nullable
+ public static Uri getRootDirUri() {
+ return root != null ? root.getUri() : null;
+ }
+
+ public static class ReselectDocumentTreeException extends Exception {
+ private final Uri initialUri;
+
+ public ReselectDocumentTreeException() {
+ initialUri = null;
+ }
+
+ public ReselectDocumentTreeException(final String message) {
+ super(message);
+ initialUri = null;
+ }
+
+ public ReselectDocumentTreeException(final Uri initialUri) {
+ this.initialUri = initialUri;
+ }
+
+ public Uri getInitialUri() {
+ return initialUri;
+ }
+ }
}
diff --git a/app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java b/app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java
index cc03534d..64d91cdd 100755
--- a/app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java
+++ b/app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java
@@ -2,6 +2,7 @@ package awais.instagrabber.utils;
import android.content.Context;
import android.content.SharedPreferences;
+import android.net.Uri;
import android.util.Base64;
import android.util.Log;
import android.util.Pair;
@@ -20,9 +21,8 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
@@ -55,14 +55,15 @@ public final class ExportImportUtils {
public static void importData(@NonNull final Context context,
@ExportImportFlags final int flags,
- @NonNull final File file,
+ @NonNull final Uri uri,
final String password,
final FetchListener fetchListener) throws IncorrectPasswordException {
- try (final FileInputStream fis = new FileInputStream(file)) {
- final int configType = fis.read();
+ try (final InputStream stream = context.getContentResolver().openInputStream(uri)) {
+ if (stream == null) return;
+ final int configType = stream.read();
final StringBuilder builder = new StringBuilder();
int c;
- while ((c = fis.read()) != -1) {
+ while ((c = stream.read()) != -1) {
builder.append((char) c);
}
if (configType == 'A') {
@@ -223,9 +224,11 @@ public final class ExportImportUtils {
}
}
- public static boolean isEncrypted(final File file) {
- try (final FileInputStream fis = new FileInputStream(file)) {
- final int configType = fis.read();
+ public static boolean isEncrypted(@NonNull final Context context,
+ @NonNull final Uri uri) {
+ try (final InputStream stream = context.getContentResolver().openInputStream(uri)) {
+ if (stream == null) return false;
+ final int configType = stream.read();
if (configType == 'A') {
return true;
}
@@ -237,7 +240,7 @@ public final class ExportImportUtils {
public static void exportData(@NonNull final Context context,
@ExportImportFlags final int flags,
- @NonNull final File filePath,
+ @NonNull final Uri uri,
final String password,
final FetchListener fetchListener) {
getExportString(flags, context, exportString -> {
@@ -258,15 +261,20 @@ public final class ExportImportUtils {
exportBytes = Base64.encode(exportString.getBytes(), Base64.DEFAULT | Base64.NO_WRAP | Base64.NO_PADDING);
}
if (exportBytes != null && exportBytes.length > 1) {
- try (final FileOutputStream fos = new FileOutputStream(filePath)) {
- fos.write(isPass ? 'A' : 'Z');
- fos.write(exportBytes);
+ try (final OutputStream stream = context.getContentResolver().openOutputStream(uri)) {
+ if (stream == null) return;
+ stream.write(isPass ? 'A' : 'Z');
+ stream.write(exportBytes);
if (fetchListener != null) fetchListener.onResult(true);
- } catch (final Exception e) {
+ } catch (Exception e) {
if (fetchListener != null) fetchListener.onResult(false);
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
}
- } else if (fetchListener != null) fetchListener.onResult(false);
+ return;
+ }
+ if (fetchListener != null) {
+ fetchListener.onResult(false);
+ }
});
}
diff --git a/app/src/main/java/awais/instagrabber/utils/MediaUploader.kt b/app/src/main/java/awais/instagrabber/utils/MediaUploader.kt
index 911b30c8..65fff446 100644
--- a/app/src/main/java/awais/instagrabber/utils/MediaUploader.kt
+++ b/app/src/main/java/awais/instagrabber/utils/MediaUploader.kt
@@ -3,6 +3,7 @@ package awais.instagrabber.utils
import android.content.ContentResolver
import android.graphics.Bitmap
import android.net.Uri
+import androidx.documentfile.provider.DocumentFile
import awais.instagrabber.models.UploadVideoOptions
import awais.instagrabber.webservices.interceptors.AddCookiesInterceptor
import kotlinx.coroutines.Dispatchers
@@ -12,8 +13,6 @@ import okio.BufferedSink
import okio.Okio
import org.json.JSONObject
import ru.gildor.coroutines.okhttp.await
-import java.io.File
-import java.io.FileInputStream
import java.io.IOException
import java.io.InputStream
@@ -29,20 +28,23 @@ object MediaUploader {
): MediaUploadResponse = withContext(Dispatchers.IO) {
val bitmapResult = BitmapUtils.loadBitmap(contentResolver, uri, 1000f, false)
val bitmap = bitmapResult?.bitmap ?: throw IOException("bitmap is null")
- uploadPhoto(bitmap)
+ uploadPhoto(contentResolver, bitmap)
}
@Suppress("BlockingMethodInNonBlockingContext")
private suspend fun uploadPhoto(
+ contentResolver: ContentResolver,
bitmap: Bitmap,
): MediaUploadResponse = withContext(Dispatchers.IO) {
- val file: File = BitmapUtils.convertToJpegAndSaveToFile(bitmap, null)
+ val file: DocumentFile = BitmapUtils.convertToJpegAndSaveToFile(contentResolver, bitmap, null)
val byteLength: Long = file.length()
val options = createUploadPhotoOptions(byteLength)
val headers = getUploadPhotoHeaders(options)
val url = HOST + "/rupload_igphoto/" + options.name + "/"
try {
- FileInputStream(file).use { input -> upload(input, url, headers) }
+ contentResolver.openInputStream(file.uri).use { input ->
+ upload(input!!, url, headers)
+ }
} finally {
file.delete()
}
diff --git a/app/src/main/java/awais/instagrabber/utils/MediaUtils.java b/app/src/main/java/awais/instagrabber/utils/MediaUtils.java
index e7ca2707..0d58a76f 100644
--- a/app/src/main/java/awais/instagrabber/utils/MediaUtils.java
+++ b/app/src/main/java/awais/instagrabber/utils/MediaUtils.java
@@ -2,13 +2,17 @@ package awais.instagrabber.utils;
import android.content.ContentResolver;
import android.database.Cursor;
+import android.media.MediaMetadataRetriever;
import android.net.Uri;
+import android.os.ParcelFileDescriptor;
import android.provider.MediaStore;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import java.io.FileDescriptor;
+
public final class MediaUtils {
private static final String TAG = MediaUtils.class.getSimpleName();
private static final String[] PROJECTION_VIDEO = {
@@ -28,9 +32,7 @@ public final class MediaUtils {
AppExecutors.INSTANCE.getTasksThread().submit(() -> {
try (Cursor cursor = MediaStore.Video.query(contentResolver, uri, PROJECTION_VIDEO)) {
if (cursor == null) {
- if (listener != null) {
- listener.onLoad(null);
- }
+ listener.onLoad(null);
return;
}
int durationColumn = cursor.getColumnIndex(MediaStore.Video.Media.DURATION);
@@ -38,24 +40,16 @@ public final class MediaUtils {
int heightColumn = cursor.getColumnIndex(MediaStore.Video.Media.HEIGHT);
int sizeColumn = cursor.getColumnIndex(MediaStore.Video.Media.SIZE);
if (cursor.moveToNext()) {
- if (listener != null) {
- listener.onLoad(new VideoInfo(
- cursor.getLong(durationColumn),
- cursor.getInt(widthColumn),
- cursor.getInt(heightColumn),
- cursor.getLong(sizeColumn)
- ));
- }
+ listener.onLoad(new VideoInfo(
+ cursor.getLong(durationColumn),
+ cursor.getInt(widthColumn),
+ cursor.getInt(heightColumn),
+ cursor.getLong(sizeColumn)
+ ));
}
} catch (Exception e) {
Log.e(TAG, "getVideoInfo: ", e);
- if (listener != null) {
- listener.onFailure(e);
- }
- return;
- }
- if (listener != null) {
- listener.onLoad(null);
+ listener.onFailure(e);
}
});
}
@@ -64,34 +58,25 @@ public final class MediaUtils {
@NonNull final Uri uri,
@NonNull final OnInfoLoadListener listener) {
AppExecutors.INSTANCE.getTasksThread().submit(() -> {
- try (Cursor cursor = MediaStore.Video.query(contentResolver, uri, PROJECTION_AUDIO)) {
- if (cursor == null) {
- if (listener != null) {
- listener.onLoad(null);
- }
+ try (ParcelFileDescriptor parcelFileDescriptor = contentResolver.openFileDescriptor(uri, "r")) {
+ if (parcelFileDescriptor == null) {
+ listener.onLoad(null);
return;
}
- int durationColumn = cursor.getColumnIndex(MediaStore.Audio.Media.DURATION);
- int sizeColumn = cursor.getColumnIndex(MediaStore.Audio.Media.SIZE);
- if (cursor.moveToNext()) {
- if (listener != null) {
- listener.onLoad(new VideoInfo(
- cursor.getLong(durationColumn),
- 0,
- 0,
- cursor.getLong(sizeColumn)
- ));
- }
- }
+ final FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
+ final MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
+ mediaMetadataRetriever.setDataSource(fileDescriptor);
+ String duration = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
+ if (TextUtils.isEmpty(duration)) duration = "0";
+ listener.onLoad(new VideoInfo(
+ Long.parseLong(duration),
+ 0,
+ 0,
+ 0
+ ));
} catch (Exception e) {
Log.e(TAG, "getVoiceInfo: ", e);
- if (listener != null) {
- listener.onFailure(e);
- }
- return;
- }
- if (listener != null) {
- listener.onLoad(null);
+ listener.onFailure(e);
}
});
}
diff --git a/app/src/main/java/awais/instagrabber/utils/PermissionUtils.java b/app/src/main/java/awais/instagrabber/utils/PermissionUtils.java
index b0ec5f61..8b92fe1d 100644
--- a/app/src/main/java/awais/instagrabber/utils/PermissionUtils.java
+++ b/app/src/main/java/awais/instagrabber/utils/PermissionUtils.java
@@ -13,17 +13,15 @@ import androidx.fragment.app.Fragment;
import static android.Manifest.permission.CAMERA;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.Manifest.permission.RECORD_AUDIO;
-import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
import static androidx.core.content.PermissionChecker.checkSelfPermission;
public class PermissionUtils {
- public static final String[] AUDIO_RECORD_PERMS = new String[]{WRITE_EXTERNAL_STORAGE, RECORD_AUDIO};
+ public static final String[] AUDIO_RECORD_PERMS = new String[]{RECORD_AUDIO};
public static final String[] ATTACH_MEDIA_PERMS = new String[]{READ_EXTERNAL_STORAGE};
public static final String[] CAMERA_PERMS = new String[]{CAMERA};
public static boolean hasAudioRecordPerms(@NonNull final Context context) {
- return checkSelfPermission(context, WRITE_EXTERNAL_STORAGE) == PermissionChecker.PERMISSION_GRANTED
- && checkSelfPermission(context, RECORD_AUDIO) == PermissionChecker.PERMISSION_GRANTED;
+ return checkSelfPermission(context, RECORD_AUDIO) == PermissionChecker.PERMISSION_GRANTED;
}
public static void requestAudioRecordPerms(final Fragment fragment, final int requestCode) {
diff --git a/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java b/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java
index 74c69b9c..6bd688d2 100755
--- a/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java
+++ b/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java
@@ -11,6 +11,7 @@ import androidx.appcompat.app.AppCompatDelegate;
import java.util.HashSet;
import java.util.Set;
+import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_BARINSTA_DIR_URI;
import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH;
import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH_FREQ_NUMBER;
import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH_FREQ_UNIT;
@@ -159,7 +160,7 @@ public final class SettingsHelper {
CUSTOM_DATE_TIME_FORMAT, DEVICE_UUID, SKIPPED_VERSION, DEFAULT_TAB, PREF_DARK_THEME, PREF_LIGHT_THEME,
PREF_POSTS_LAYOUT, PREF_PROFILE_POSTS_LAYOUT, PREF_TOPIC_POSTS_LAYOUT, PREF_HASHTAG_POSTS_LAYOUT,
PREF_LOCATION_POSTS_LAYOUT, PREF_LIKED_POSTS_LAYOUT, PREF_TAGGED_POSTS_LAYOUT, PREF_SAVED_POSTS_LAYOUT,
- STORY_SORT, PREF_EMOJI_VARIANTS, PREF_REACTIONS, PREF_ENABLE_DM_AUTO_REFRESH_FREQ_UNIT, PREF_TAB_ORDER})
+ STORY_SORT, PREF_EMOJI_VARIANTS, PREF_REACTIONS, PREF_ENABLE_DM_AUTO_REFRESH_FREQ_UNIT, PREF_TAB_ORDER, PREF_BARINSTA_DIR_URI})
public @interface StringSettings {}
@StringDef({DOWNLOAD_USER_FOLDER, DOWNLOAD_PREPEND_USER_NAME, FOLDER_SAVE_TO, AUTOPLAY_VIDEOS, MUTED_VIDEOS,
diff --git a/app/src/main/java/awais/instagrabber/utils/Utils.java b/app/src/main/java/awais/instagrabber/utils/Utils.java
index b64cc0d6..b6fd06b2 100644
--- a/app/src/main/java/awais/instagrabber/utils/Utils.java
+++ b/app/src/main/java/awais/instagrabber/utils/Utils.java
@@ -13,11 +13,12 @@ import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.media.MediaScannerConnection;
-import android.media.MediaScannerConnection.OnScanCompletedListener;
import android.net.Uri;
import android.os.Build;
+import android.os.Environment;
+import android.os.storage.StorageManager;
import android.provider.Browser;
+import android.provider.DocumentsContract;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Pair;
@@ -37,6 +38,7 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
+import androidx.documentfile.provider.DocumentFile;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MediatorLiveData;
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat;
@@ -46,12 +48,13 @@ import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvicto
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Ordering;
-import com.google.common.io.Files;
import org.json.JSONObject;
import java.io.File;
import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -66,6 +69,8 @@ import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.models.Tab;
import awais.instagrabber.models.enums.FavoriteType;
+import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_BARINSTA_DIR_URI;
+
public final class Utils {
private static final String TAG = "Utils";
private static final int VIDEO_CACHE_MAX_BYTES = 10 * 1024 * 1024;
@@ -82,6 +87,7 @@ public final class Utils {
public static String cacheDir;
public static String tabOrderString;
private static int defaultStatusBarColor;
+ private static Object[] volumes;
public static int convertDpToPx(final float dp) {
return Math.round((dp * displayMetrics.densityDpi) / DisplayMetrics.DENSITY_DEFAULT);
@@ -346,18 +352,18 @@ public final class Utils {
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
- public static void mediaScanFile(@NonNull final Context context,
- @NonNull File file,
- @NonNull final OnScanCompletedListener callback) {
- //noinspection UnstableApiUsage
- final String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(Files.getFileExtension(file.getName()));
- MediaScannerConnection.scanFile(
- context,
- new String[]{file.getAbsolutePath()},
- new String[]{mimeType},
- callback
- );
- }
+ // public static void mediaScanFile(@NonNull final Context context,
+ // @NonNull File file,
+ // @NonNull final OnScanCompletedListener callback) {
+ // //noinspection UnstableApiUsage
+ // final String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(Files.getFileExtension(file.getName()));
+ // MediaScannerConnection.scanFile(
+ // context,
+ // new String[]{file.getAbsolutePath()},
+ // new String[]{mimeType},
+ // callback
+ // );
+ // }
public static void showKeyboard(@NonNull final View view) {
final Context context = view.getContext();
@@ -524,6 +530,73 @@ public final class Utils {
return tabOrderString.contains(navRootString);
}
+ // public static void scanDocumentFile(@NonNull final Context context,
+ // @NonNull final DocumentFile documentFile,
+ // @NonNull final OnScanCompletedListener callback) {
+ // if (!documentFile.isFile() || !documentFile.exists()) {
+ // Log.d(TAG, "scanDocumentFile: " + documentFile);
+ // callback.onScanCompleted(null, null);
+ // return;
+ // }
+ // File file = null;
+ // try {
+ // file = getDocumentFileRealPath(context, documentFile);
+ // } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
+ // Log.e(TAG, "scanDocumentFile: ", e);
+ // }
+ // if (file == null) return;
+ // MediaScannerConnection.scanFile(context,
+ // new String[]{file.getAbsolutePath()},
+ // new String[]{documentFile.getType()},
+ // callback);
+ // }
+
+ public static File getDocumentFileRealPath(@NonNull final Context context,
+ @NonNull final DocumentFile documentFile)
+ throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+ final String docId = DocumentsContract.getDocumentId(documentFile.getUri());
+ final String[] split = docId.split(":");
+ final String type = split[0];
+
+ if (type.equalsIgnoreCase("primary")) {
+ return new File(Environment.getExternalStorageDirectory(), split[1]);
+ } else if (type.equalsIgnoreCase("raw")) {
+ return new File(split[1]);
+ } else {
+ if (volumes == null) {
+ final StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
+ if (sm == null) return null;
+ final Method getVolumeListMethod = sm.getClass().getMethod("getVolumeList");
+ volumes = (Object[]) getVolumeListMethod.invoke(sm);
+ }
+ if (volumes == null) return null;
+ for (Object volume : volumes) {
+ final Method getUuidMethod = volume.getClass().getMethod("getUuid");
+ final String uuid = (String) getUuidMethod.invoke(volume);
+
+ if (uuid != null && uuid.equalsIgnoreCase(type)) {
+ final Method getPathMethod = volume.getClass().getMethod("getPath");
+ final String path = (String) getPathMethod.invoke(volume);
+ return new File(path, split[1]);
+ }
+ }
+ }
+
+ return null;
+ }
+
+ public static void setupSelectedDir(@NonNull final Context context,
+ @NonNull final Intent intent) throws DownloadUtils.ReselectDocumentTreeException {
+ final Uri dirUri = intent.getData();
+ Log.d(TAG, "onActivityResult: " + dirUri);
+ if (dirUri == null) return;
+ final int takeFlags = intent.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ context.getContentResolver().takePersistableUriPermission(dirUri, takeFlags);
+ settingsHelper.putString(PREF_BARINSTA_DIR_URI, dirUri.toString());
+ // re-init DownloadUtils
+ DownloadUtils.init(context);
+ }
+
@NonNull
public static Point getNavigationBarSize(@NonNull Context context) {
Point appUsableSize = getAppUsableScreenSize(context);
diff --git a/app/src/main/java/awais/instagrabber/utils/VoiceRecorder.java b/app/src/main/java/awais/instagrabber/utils/VoiceRecorder.java
index 83f79c7f..266d814c 100644
--- a/app/src/main/java/awais/instagrabber/utils/VoiceRecorder.java
+++ b/app/src/main/java/awais/instagrabber/utils/VoiceRecorder.java
@@ -1,13 +1,18 @@
package awais.instagrabber.utils;
+import android.app.Application;
+import android.content.ContentResolver;
import android.media.MediaRecorder;
import android.os.Handler;
import android.os.Message;
+import android.os.ParcelFileDescriptor;
import android.util.Log;
import android.webkit.MimeTypeMap;
import androidx.annotation.NonNull;
+import androidx.documentfile.provider.DocumentFile;
+import java.io.IOException;
import java.io.File;
import java.time.format.DateTimeFormatter;
import java.time.LocalDateTime;
@@ -27,28 +32,30 @@ public class VoiceRecorder {
private static final DateTimeFormatter SIMPLE_DATE_FORMAT = DateTimeFormatter.ofPattern(FILE_FORMAT, Locale.US);
private final List waveform = new ArrayList<>();
- private final File recordingsDir;
+ private final DocumentFile recordingsDir;
private final VoiceRecorderCallback callback;
private MediaRecorder recorder;
- private File audioTempFile;
+ private DocumentFile audioTempFile;
private MaxAmpHandler maxAmpHandler;
private boolean stopped;
- public VoiceRecorder(@NonNull final File recordingsDir, final VoiceRecorderCallback callback) {
+ public VoiceRecorder(@NonNull final DocumentFile recordingsDir, final VoiceRecorderCallback callback) {
this.recordingsDir = recordingsDir;
this.callback = callback;
}
- public void startRecording() {
+ public void startRecording(final ContentResolver contentResolver) {
stopped = false;
+ ParcelFileDescriptor parcelFileDescriptor = null;
try {
recorder = new MediaRecorder();
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
deleteTempAudioFile();
audioTempFile = getAudioRecordFile();
- recorder.setOutputFile(audioTempFile.getAbsolutePath());
+ parcelFileDescriptor = contentResolver.openFileDescriptor(audioTempFile.getUri(), "rwt");
+ recorder.setOutputFile(parcelFileDescriptor.getFileDescriptor());
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.HE_AAC);
recorder.setAudioEncodingBitRate(AUDIO_BIT_RATE);
recorder.setAudioSamplingRate(AUDIO_SAMPLE_RATE);
@@ -63,6 +70,12 @@ public class VoiceRecorder {
} catch (Exception e) {
Log.e(TAG, "Audio recording failed", e);
deleteTempAudioFile();
+ } finally {
+ if (parcelFileDescriptor != null) {
+ try {
+ parcelFileDescriptor.close();
+ } catch (IOException ignored) {}
+ }
}
}
@@ -140,9 +153,13 @@ public class VoiceRecorder {
// }
@NonNull
- private File getAudioRecordFile() {
+ private DocumentFile getAudioRecordFile() {
final String name = String.format("%s-%s.%s", FILE_PREFIX, LocalDateTime.now().format(SIMPLE_DATE_FORMAT), EXTENSION);
- return new File(recordingsDir, name);
+ DocumentFile file = recordingsDir.findFile(name);
+ if (file == null || !file.exists()) {
+ file = recordingsDir.createFile(MIME_TYPE, name);
+ }
+ return file;
}
private void deleteTempAudioFile() {
@@ -160,11 +177,11 @@ public class VoiceRecorder {
public static class VoiceRecordingResult {
private final String mimeType;
- private final File file;
+ private final DocumentFile file;
private final List waveform;
private final int samplingFreq = 10;
- public VoiceRecordingResult(final String mimeType, final File file, final List waveform) {
+ public VoiceRecordingResult(final String mimeType, final DocumentFile file, final List waveform) {
this.mimeType = mimeType;
this.file = file;
this.waveform = waveform;
@@ -174,7 +191,7 @@ public class VoiceRecorder {
return mimeType;
}
- public File getFile() {
+ public DocumentFile getFile() {
return file;
}
diff --git a/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.kt b/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.kt
index f7626af3..70407c06 100644
--- a/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.kt
+++ b/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.kt
@@ -1,10 +1,10 @@
package awais.instagrabber.viewmodels
+import android.R.attr
import android.app.Application
import android.content.ContentResolver
-import android.media.MediaScannerConnection
import android.net.Uri
-import android.util.Log
+import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.*
import awais.instagrabber.customviews.emoji.Emoji
import awais.instagrabber.managers.DirectMessagesManager
@@ -23,10 +23,9 @@ import awais.instagrabber.utils.MediaUtils.OnInfoLoadListener
import awais.instagrabber.utils.MediaUtils.VideoInfo
import awais.instagrabber.utils.VoiceRecorder.VoiceRecorderCallback
import awais.instagrabber.utils.VoiceRecorder.VoiceRecordingResult
-import awais.instagrabber.utils.extensions.TAG
-import java.io.File
import java.util.*
+
class DirectThreadViewModel(
application: Application,
val threadId: String,
@@ -37,7 +36,7 @@ class DirectThreadViewModel(
// private static final String ERROR_INVALID_THREAD = "Invalid thread";
private val contentResolver: ContentResolver = application.contentResolver
- private val recordingsDir: File = DirectoryUtils.getOutputMediaDirectory(application, "Recordings")
+ private val recordingsDir: DocumentFile? = DownloadUtils.getRecordingsDir()
private var voiceRecorder: VoiceRecorder? = null
private lateinit var threadManager: ThreadManager
@@ -87,33 +86,24 @@ class DirectThreadViewModel(
fun startRecording(): LiveData> {
val data = MutableLiveData>()
- voiceRecorder = VoiceRecorder(recordingsDir, object : VoiceRecorderCallback {
+ voiceRecorder = VoiceRecorder(recordingsDir!!, object : VoiceRecorderCallback {
override fun onStart() {}
override fun onComplete(result: VoiceRecordingResult) {
- Log.d(TAG, "onComplete: recording complete. Scanning file...")
- MediaScannerConnection.scanFile(
- getApplication(),
- arrayOf(result.file.absolutePath),
- arrayOf(result.mimeType)
- ) { _: String?, uri: Uri? ->
- if (uri == null) {
- val msg = "Scan failed!"
- Log.e(TAG, msg)
- data.postValue(error(msg, null))
- return@scanFile
- }
- Log.d(TAG, "onComplete: scan complete")
- MediaUtils.getVoiceInfo(contentResolver, uri, object : OnInfoLoadListener {
+ // Log.d(TAG, "onComplete: recording complete. Scanning file...");
+ MediaUtils.getVoiceInfo(
+ contentResolver,
+ result.file.uri,
+ object : OnInfoLoadListener {
override fun onLoad(videoInfo: VideoInfo?) {
if (videoInfo == null) return
threadManager.sendVoice(
data,
- uri,
+ result.file.uri,
result.waveform,
result.samplingFreq,
videoInfo.duration,
- videoInfo.size,
- viewModelScope,
+ result.file.length(),
+ viewModelScope
)
}
@@ -121,12 +111,11 @@ class DirectThreadViewModel(
data.postValue(error(t.message, null))
}
})
- }
}
override fun onCancel() {}
})
- voiceRecorder?.startRecording()
+ voiceRecorder?.startRecording(contentResolver)
return data
}
diff --git a/app/src/main/java/awais/instagrabber/viewmodels/DirectorySelectActivityViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/DirectorySelectActivityViewModel.java
new file mode 100644
index 00000000..03e0420f
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/viewmodels/DirectorySelectActivityViewModel.java
@@ -0,0 +1,113 @@
+package awais.instagrabber.viewmodels;
+
+import android.app.Application;
+import android.content.Intent;
+import android.content.UriPermission;
+import android.net.Uri;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.documentfile.provider.DocumentFile;
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+import awais.instagrabber.R;
+import awais.instagrabber.utils.Constants;
+import awais.instagrabber.utils.DownloadUtils;
+import awais.instagrabber.utils.TextUtils;
+import awais.instagrabber.utils.Utils;
+
+import static awais.instagrabber.fragments.settings.PreferenceKeys.FOLDER_PATH;
+
+public class DirectorySelectActivityViewModel extends AndroidViewModel {
+ private static final String TAG = DirectorySelectActivityViewModel.class.getSimpleName();
+
+ private final MutableLiveData message = new MutableLiveData<>();
+ private final MutableLiveData prevUri = new MutableLiveData<>();
+ private final MutableLiveData loading = new MutableLiveData<>(false);
+ private final MutableLiveData dirSuccess = new MutableLiveData<>(false);
+
+ public DirectorySelectActivityViewModel(final Application application) {
+ super(application);
+ }
+
+ public LiveData getMessage() {
+ return message;
+ }
+
+ public LiveData getPrevUri() {
+ return prevUri;
+ }
+
+ public LiveData isLoading() {
+ return loading;
+ }
+
+ public LiveData getDirSuccess() {
+ return dirSuccess;
+ }
+
+ public void setInitialUri(final Intent intent) {
+ if (intent == null) {
+ setMessage(null);
+ return;
+ }
+ final Parcelable initialUriParcelable = intent.getParcelableExtra(Constants.EXTRA_INITIAL_URI);
+ if (!(initialUriParcelable instanceof Uri)) {
+ setMessage(null);
+ return;
+ }
+ setMessage((Uri) initialUriParcelable);
+ }
+
+ private void setMessage(@Nullable final Uri initialUri) {
+ if (initialUri == null) {
+ final String prevVersionFolderPath = Utils.settingsHelper.getString(FOLDER_PATH);
+ if (TextUtils.isEmpty(prevVersionFolderPath)) {
+ // default message
+ message.postValue(getApplication().getString(R.string.dir_select_default_message));
+ prevUri.postValue(null);
+ return;
+ }
+ message.postValue(getApplication().getString(R.string.dir_select_reselect_message));
+ prevUri.postValue(prevVersionFolderPath);
+ return;
+ }
+ final List existingPermissions = getApplication().getContentResolver().getPersistedUriPermissions();
+ final boolean anyMatch = existingPermissions.stream().anyMatch(uriPermission -> uriPermission.getUri().equals(initialUri));
+ final DocumentFile documentFile = DocumentFile.fromSingleUri(getApplication(), initialUri);
+ String path;
+ try {
+ path = URLDecoder.decode(initialUri.toString(), StandardCharsets.UTF_8.toString());
+ } catch (UnsupportedEncodingException e) {
+ path = initialUri.toString();
+ }
+ if (!anyMatch) {
+ message.postValue(getApplication().getString(R.string.dir_select_permission_revoked_message));
+ prevUri.postValue(path);
+ return;
+ }
+ if (documentFile == null || !documentFile.exists() || documentFile.lastModified() == 0) {
+ message.postValue(getApplication().getString(R.string.dir_select_folder_not_exist));
+ prevUri.postValue(path);
+ }
+ }
+
+ public void setupSelectedDir(@NonNull final Intent data) throws DownloadUtils.ReselectDocumentTreeException {
+ loading.postValue(true);
+ try {
+ Utils.setupSelectedDir(getApplication(), data);
+ message.postValue(getApplication().getString(R.string.dir_select_success_message));
+ dirSuccess.postValue(true);
+ } finally {
+ loading.postValue(false);
+ }
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/viewmodels/ImageEditViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/ImageEditViewModel.java
index 561d4fb3..ebbfc2f6 100644
--- a/app/src/main/java/awais/instagrabber/viewmodels/ImageEditViewModel.java
+++ b/app/src/main/java/awais/instagrabber/viewmodels/ImageEditViewModel.java
@@ -5,6 +5,7 @@ import android.graphics.RectF;
import android.net.Uri;
import androidx.annotation.NonNull;
+import androidx.documentfile.provider.DocumentFile;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
@@ -24,8 +25,9 @@ import awais.instagrabber.fragments.imageedit.filters.filters.Filter;
import awais.instagrabber.fragments.imageedit.filters.properties.Property;
import awais.instagrabber.models.SavedImageEditState;
import awais.instagrabber.utils.AppExecutors;
-import awais.instagrabber.utils.DirectoryUtils;
+import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.SerializablePair;
+import awais.instagrabber.utils.Utils;
import jp.co.cyberagent.android.gpuimage.GPUImage;
import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter;
import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilterGroup;
@@ -34,6 +36,7 @@ public class ImageEditViewModel extends AndroidViewModel {
private static final String CROP = "crop";
private static final String RESULT = "result";
private static final String FILE_FORMAT = "yyyyMMddHHmmssSSS";
+ private static final String MIME_TYPE = Utils.mimeTypeMap.getMimeTypeFromExtension("jpg");
private static final DateTimeFormatter SIMPLE_DATE_FORMAT = DateTimeFormatter.ofPattern(FILE_FORMAT, Locale.US);
private Uri originalUri;
@@ -48,18 +51,18 @@ public class ImageEditViewModel extends AndroidViewModel {
private final MutableLiveData isCropped = new MutableLiveData<>(false);
private final MutableLiveData isTuned = new MutableLiveData<>(false);
private final MutableLiveData isFiltered = new MutableLiveData<>(false);
- private final File outputDir;
+ private final DocumentFile outputDir;
private List> tuningFilters;
private Filter extends GPUImageFilter> appliedFilter;
- private final File destinationFile;
+ private final DocumentFile destinationFile;
public ImageEditViewModel(final Application application) {
super(application);
sessionId = LocalDateTime.now().format(SIMPLE_DATE_FORMAT);
- outputDir = DirectoryUtils.getOutputMediaDirectory(application, "Edit", sessionId);
- destinationFile = new File(outputDir, RESULT + ".jpg");
- destinationUri = Uri.fromFile(destinationFile);
- cropDestinationUri = Uri.fromFile(new File(outputDir, CROP + ".jpg"));
+ outputDir = DownloadUtils.getImageEditDir(sessionId);
+ destinationFile = outputDir.createFile(MIME_TYPE, RESULT + ".jpg");
+ destinationUri = destinationFile.getUri();
+ cropDestinationUri = outputDir.createFile(MIME_TYPE, CROP + ".jpg").getUri();
}
public String getSessionId() {
@@ -159,16 +162,15 @@ public class ImageEditViewModel extends AndroidViewModel {
delete(outputDir);
}
- private void delete(@NonNull final File file) {
+ private void delete(@NonNull final DocumentFile file) {
if (file.isDirectory()) {
- final File[] files = file.listFiles();
+ final DocumentFile[] files = file.listFiles();
if (files != null) {
- for (File f : files) {
+ for (DocumentFile f : files) {
delete(f);
}
}
}
- //noinspection ResultOfMethodCallIgnored
file.delete();
}
@@ -206,9 +208,9 @@ public class ImageEditViewModel extends AndroidViewModel {
return new SerializablePair<>(type, propertyValueMap);
}
- public File getDestinationFile() {
- return destinationFile;
- }
+ // public File getDestinationFile() {
+ // return destinationFile;
+ // }
public enum Tab {
RESULT,
diff --git a/app/src/main/java/awais/instagrabber/workers/DownloadWorker.kt b/app/src/main/java/awais/instagrabber/workers/DownloadWorker.kt
index a749cc07..27c47c3a 100644
--- a/app/src/main/java/awais/instagrabber/workers/DownloadWorker.kt
+++ b/app/src/main/java/awais/instagrabber/workers/DownloadWorker.kt
@@ -6,8 +6,8 @@ import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
+import android.graphics.BitmapFactory
import android.media.MediaMetadataRetriever
-import android.media.MediaScannerConnection
import android.net.Uri
import android.os.Build
import android.os.Handler
@@ -15,7 +15,7 @@ import android.os.Looper
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
-import androidx.core.content.FileProvider
+import androidx.documentfile.provider.DocumentFile
import androidx.work.CoroutineWorker
import androidx.work.Data
import androidx.work.ForegroundInfo
@@ -37,11 +37,11 @@ import kotlinx.coroutines.withContext
import org.apache.commons.imaging.formats.jpeg.iptc.JpegIptcRewriter
import java.io.BufferedInputStream
import java.io.File
-import java.io.FileInputStream
-import java.io.FileOutputStream
import java.net.URL
import java.util.*
import java.util.concurrent.ExecutionException
+import java.util.stream.Collectors
+import kotlin.collections.Map
import kotlin.math.abs
class DownloadWorker(context: Context, workerParams: WorkerParameters) : CoroutineWorker(context, workerParams) {
@@ -55,9 +55,14 @@ class DownloadWorker(context: Context, workerParams: WorkerParameters) : Corouti
.build())
}
val downloadRequestString: String
- val requestFile = File(downloadRequestFilePath)
+ val requestFile = Uri.parse(downloadRequestFilePath)
+ val context = applicationContext
+ val contentResolver = context.contentResolver ?: return Result.failure(Data.Builder()
+ .putString("error", "contentResolver is null")
+ .build())
try {
- downloadRequestString = requestFile.bufferedReader().use { it.readText() }
+ val scanner = Scanner(contentResolver.openInputStream(requestFile))
+ downloadRequestString = scanner.useDelimiter("\\A").next()
} catch (e: Exception) {
Log.e(TAG, "doWork: ", e)
return Result.failure(Data.Builder()
@@ -82,7 +87,7 @@ class DownloadWorker(context: Context, workerParams: WorkerParameters) : Corouti
val urlToFilePathMap = downloadRequest.urlToFilePathMap
download(urlToFilePathMap)
Handler(Looper.getMainLooper()).postDelayed({ showSummary(urlToFilePathMap) }, 500)
- val deleted = requestFile.delete()
+ val deleted = DocumentFile.fromSingleUri(context, requestFile)!!.delete()
if (!deleted) {
Log.w(TAG, "doWork: requestFile not deleted!")
}
@@ -94,10 +99,11 @@ class DownloadWorker(context: Context, workerParams: WorkerParameters) : Corouti
val entries = urlToFilePathMap.entries
var count = 1
val total = urlToFilePathMap.size
- for ((url, value) in entries) {
+ for ((url, uriString) in entries) {
updateDownloadProgress(notificationId, count, total, 0f)
withContext(Dispatchers.IO) {
- download(notificationId, count, total, url, value)
+ val file = DocumentFile.fromSingleUri(applicationContext, Uri.parse(uriString))
+ download(notificationId, count, total, url, file!!)
}
count++
}
@@ -111,47 +117,49 @@ class DownloadWorker(context: Context, workerParams: WorkerParameters) : Corouti
position: Int,
total: Int,
url: String,
- filePath: String,
+ filePath: DocumentFile,
) {
- val isJpg = filePath.endsWith("jpg")
+ val context = applicationContext.let { it }
+ val contentResolver = context.contentResolver?.let { it } ?: return
+ val filePathType = filePath.type?.let { it } ?: return
+ val isJpg = filePathType.startsWith("image")
// using temp file approach to remove IPTC so that download progress can be reported
- val outFile = if (isJpg) DownloadUtils.getTempFile() else File(filePath)
+ val outFile = if (isJpg) DownloadUtils.getTempFile(null, "jpg") else filePath
try {
val urlConnection = URL(url).openConnection()
val fileSize = if (Build.VERSION.SDK_INT >= 24) urlConnection.contentLengthLong else urlConnection.contentLength.toLong()
var totalRead = 0f
try {
BufferedInputStream(urlConnection.getInputStream()).use { bis ->
- FileOutputStream(outFile).use { fos ->
+ contentResolver.openOutputStream(outFile.uri).use { fos ->
val buffer = ByteArray(0x2000)
var count: Int
while (bis.read(buffer, 0, 0x2000).also { count = it } != -1) {
totalRead += count
- fos.write(buffer, 0, count)
+ fos!!.write(buffer, 0, count)
setProgressAsync(Data.Builder().putString(URL, url)
.putFloat(PROGRESS, totalRead * 100f / fileSize)
.build())
updateDownloadProgress(notificationId, position, total, totalRead * 100f / fileSize)
}
- fos.flush()
+ fos!!.flush()
}
}
} catch (e: Exception) {
- Log.e(TAG, "Error while writing data from url: " + url + " to file: " + outFile.absolutePath, e)
+ Log.e(TAG, "Error while writing data from url: " + url + " to file: " + outFile.name, e)
}
if (isJpg) {
- val finalFile = File(filePath)
try {
- FileInputStream(outFile).use { fis ->
- FileOutputStream(finalFile).use { fos ->
+ contentResolver.openInputStream(outFile.uri).use { fis ->
+ contentResolver.openOutputStream(filePath.uri).use { fos ->
val jpegIptcRewriter = JpegIptcRewriter()
jpegIptcRewriter.removeIPTC(fis, fos)
}
}
} catch (e: Exception) {
Log.e(TAG, "Error while removing iptc: url: " + url
- + ", tempFile: " + outFile.absolutePath
- + ", finalFile: " + finalFile.absolutePath, e)
+ + ", tempFile: " + outFile.name
+ + ", finalFile: " + filePath.name, e)
}
val deleted = outFile.delete()
if (!deleted) {
@@ -218,53 +226,90 @@ class DownloadWorker(context: Context, workerParams: WorkerParameters) : Corouti
return builder.build()
}
- private fun showSummary(urlToFilePathMap: Map?) {
+ private fun showSummary(urlToFilePathMap: Map) {
val context = applicationContext
- val filePaths = urlToFilePathMap!!.values
+ val filePaths = urlToFilePathMap.mapNotNull { DocumentFile.fromSingleUri(context, Uri.parse(it.value)) }
val notifications: MutableList = LinkedList()
val notificationIds: MutableList = LinkedList()
var count = 1
- for (filePath in filePaths) {
- val file = File(filePath)
- context.sendBroadcast(Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file)))
- MediaScannerConnection.scanFile(context, arrayOf(file.absolutePath), null, null)
- val uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file)
+ for (filePath: DocumentFile in filePaths) {
+ // final File file = new File(filePath);
+ // context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, filePath.getUri()));
+ // Utils.scanDocumentFile(context, filePath, (path, uri) -> {});
+ // final Uri uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file);
val contentResolver = context.contentResolver
- val bitmap = getThumbnail(context, file, uri, contentResolver)
+ var bitmap: Bitmap? = null
+ val mimeType = filePath.type // Utils.getMimeType(uri, contentResolver);
+ if (!isEmpty(mimeType)) {
+ if (mimeType!!.startsWith("image")) {
+ try {
+ contentResolver.openInputStream(filePath.uri).use { inputStream ->
+ bitmap = BitmapFactory.decodeStream(inputStream)
+ }
+ } catch (e: java.lang.Exception) {
+ if (BuildConfig.DEBUG) Log.e(TAG, "", e)
+ }
+ } else if (mimeType.startsWith("video")) {
+ val retriever = MediaMetadataRetriever()
+ try {
+ try {
+ retriever.setDataSource(context, filePath.uri)
+ } catch (e: java.lang.Exception) {
+ // retriever.setDataSource(file.getAbsolutePath());
+ Log.e(TAG, "showSummary: ", e)
+ }
+ bitmap = retriever.frameAtTime
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) try {
+ retriever.close()
+ } catch (e: java.lang.Exception) {
+ Log.e(TAG, "showSummary: ", e)
+ }
+ } catch (e: java.lang.Exception) {
+ Log.e(TAG, "", e)
+ }
+ }
+ }
val downloadComplete = context.getString(R.string.downloader_complete)
- val intent = Intent(Intent.ACTION_VIEW, uri)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
- or Intent.FLAG_FROM_BACKGROUND
- or Intent.FLAG_GRANT_READ_URI_PERMISSION
- or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
- .putExtra(Intent.EXTRA_STREAM, uri)
+ val intent = Intent(Intent.ACTION_VIEW, filePath.uri)
+ .addFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK
+ or Intent.FLAG_FROM_BACKGROUND
+ or Intent.FLAG_GRANT_READ_URI_PERMISSION
+ or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ )
+ .putExtra(Intent.EXTRA_STREAM, filePath.uri)
val pendingIntent = PendingIntent.getActivity(
context,
DOWNLOAD_NOTIFICATION_INTENT_REQUEST_CODE,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_ONE_SHOT
)
- val notificationId = notificationId + count
+ val notificationId: Int = notificationId + count
notificationIds.add(notificationId)
count++
- val builder: NotificationCompat.Builder = NotificationCompat.Builder(context, DOWNLOAD_CHANNEL_ID)
- .setSmallIcon(R.drawable.ic_download)
- .setContentText(null)
- .setContentTitle(downloadComplete)
- .setWhen(System.currentTimeMillis())
- .setOnlyAlertOnce(true)
- .setAutoCancel(true)
- .setGroup(NOTIF_GROUP_NAME + "_" + id)
- .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
- .setContentIntent(pendingIntent)
- .addAction(R.drawable.ic_delete,
- context.getString(R.string.delete),
- DeleteImageIntentService.pendingIntent(context, filePath, notificationId))
+ val builder: NotificationCompat.Builder =
+ NotificationCompat.Builder(context, DOWNLOAD_CHANNEL_ID)
+ .setSmallIcon(R.drawable.ic_download)
+ .setContentText(null)
+ .setContentTitle(downloadComplete)
+ .setWhen(System.currentTimeMillis())
+ .setOnlyAlertOnce(true)
+ .setAutoCancel(true)
+ .setGroup(NOTIF_GROUP_NAME + "_" + id)
+ .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
+ .setContentIntent(pendingIntent)
+ .addAction(
+ R.drawable.ic_delete,
+ context.getString(R.string.delete),
+ DeleteImageIntentService.pendingIntent(context, filePath, notificationId)
+ )
if (bitmap != null) {
builder.setLargeIcon(bitmap)
- .setStyle(NotificationCompat.BigPictureStyle()
- .bigPicture(bitmap)
- .bigLargeIcon(null))
+ .setStyle(
+ NotificationCompat.BigPictureStyle()
+ .bigPicture(bitmap)
+ .bigLargeIcon(null)
+ )
.setBadgeIconType(NotificationCompat.BADGE_ICON_SMALL)
}
notifications.add(builder)
@@ -348,13 +393,15 @@ class DownloadWorker(context: Context, workerParams: WorkerParameters) : Corouti
class Builder {
private var urlToFilePathMap: MutableMap = mutableMapOf()
- fun setUrlToFilePathMap(urlToFilePathMap: MutableMap): Builder {
+ fun setUrlToFilePathMap(urlToFilePathMap: MutableMap): Builder {
this.urlToFilePathMap = urlToFilePathMap
+ .mapValues { it.value.uri.toString() }
+ .toMutableMap()
return this
}
- fun addUrl(url: String, filePath: String): Builder {
- urlToFilePathMap[url] = filePath
+ fun addUrl(url: String, filePath: DocumentFile): Builder {
+ urlToFilePathMap[url] = filePath.uri.toString()
return this
}
diff --git a/app/src/main/res/layout/activity_directory_select.xml b/app/src/main/res/layout/activity_directory_select.xml
new file mode 100644
index 00000000..5d5a0110
--- /dev/null
+++ b/app/src/main/res/layout/activity_directory_select.xml
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_create_backup.xml b/app/src/main/res/layout/dialog_create_backup.xml
index 78df89d5..56b4a71b 100644
--- a/app/src/main/res/layout/dialog_create_backup.xml
+++ b/app/src/main/res/layout/dialog_create_backup.xml
@@ -69,5 +69,5 @@
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="16dp"
- android:text="@string/create_backup" />
+ android:text="@string/backup" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_restore_backup.xml b/app/src/main/res/layout/dialog_restore_backup.xml
index 52686620..195c9625 100644
--- a/app/src/main/res/layout/dialog_restore_backup.xml
+++ b/app/src/main/res/layout/dialog_restore_backup.xml
@@ -127,7 +127,7 @@
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="16dp"
- android:text="@string/restore_backup"
+ android:text="@string/restore"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/bottom_password_divider" />
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 75f446e3..cea7976b 100755
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -501,6 +501,16 @@
If saved, all DM related features will be disabled on next launch
Copy caption
Copy reply
+ Restore
+ Backup
+ Select a folder where Barinsta can store downloads and temporary files.\n\nYou can change this later in More > Settings > Downloads.
+ Android has changed the way apps can access files and directories on storage. Currently Barinsta does not have permission to access the following folder:
+ Permissions for the previously selected folder were revoked by the system:
+ The previously selected folder does not exist now:
+ Re-select the directory or select a new directory by clicking the button below.
+ No folder selected!
+ Success! Please wait. Starting app…
+ Barinsta folder
Top
Recent
Clear