mirror of
https://github.com/KokaKiwi/BarInsta
synced 2024-11-15 19:27:31 +00:00
Merge pull request #934 from ammargitham/support-android-11
Migrate to SAF (Storage Access Framework)
This commit is contained in:
commit
95b90ab72f
@ -20,7 +20,7 @@ android {
|
|||||||
applicationId 'me.austinhuang.instagrabber'
|
applicationId 'me.austinhuang.instagrabber'
|
||||||
|
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 29
|
targetSdkVersion 30
|
||||||
|
|
||||||
versionCode 63
|
versionCode 63
|
||||||
versionName '19.2.2'
|
versionName '19.2.2'
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
package="awais.instagrabber">
|
package="awais.instagrabber">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<!--<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />-->
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
@ -20,7 +20,6 @@
|
|||||||
android:fullBackupContent="@xml/backup_descriptor"
|
android:fullBackupContent="@xml/backup_descriptor"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:requestLegacyExternalStorage="true"
|
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme.Launcher"
|
android:theme="@style/AppTheme.Launcher"
|
||||||
tools:ignore="UnusedAttribute">
|
tools:ignore="UnusedAttribute">
|
||||||
@ -98,6 +97,7 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".utils.ProcessPhoenix"
|
android:name=".utils.ProcessPhoenix"
|
||||||
android:theme="@style/Theme.AppCompat.Translucent" />
|
android:theme="@style/Theme.AppCompat.Translucent" />
|
||||||
|
<activity android:name=".activities.DirectorySelectActivity" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
@ -4,22 +4,19 @@ import android.content.Intent
|
|||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.hardware.display.DisplayManager
|
import android.hardware.display.DisplayManager
|
||||||
import android.hardware.display.DisplayManager.DisplayListener
|
import android.hardware.display.DisplayManager.DisplayListener
|
||||||
import android.media.MediaScannerConnection
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.webkit.MimeTypeMap
|
|
||||||
import androidx.camera.core.*
|
import androidx.camera.core.*
|
||||||
import androidx.camera.lifecycle.ProcessCameraProvider
|
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import awais.instagrabber.databinding.ActivityCameraBinding
|
import awais.instagrabber.databinding.ActivityCameraBinding
|
||||||
import awais.instagrabber.utils.DirectoryUtils
|
import awais.instagrabber.utils.DownloadUtils
|
||||||
import awais.instagrabber.utils.PermissionUtils
|
import awais.instagrabber.utils.PermissionUtils
|
||||||
import awais.instagrabber.utils.Utils
|
import awais.instagrabber.utils.Utils
|
||||||
import awais.instagrabber.utils.extensions.TAG
|
import awais.instagrabber.utils.extensions.TAG
|
||||||
import com.google.common.io.Files
|
import java.io.IOException
|
||||||
import java.io.File
|
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.ExecutionException
|
import java.util.concurrent.ExecutionException
|
||||||
@ -28,10 +25,10 @@ import java.util.concurrent.Executors
|
|||||||
|
|
||||||
class CameraActivity : BaseLanguageActivity() {
|
class CameraActivity : BaseLanguageActivity() {
|
||||||
private lateinit var binding: ActivityCameraBinding
|
private lateinit var binding: ActivityCameraBinding
|
||||||
private lateinit var outputDirectory: File
|
|
||||||
private lateinit var displayManager: DisplayManager
|
private lateinit var displayManager: DisplayManager
|
||||||
private lateinit var cameraExecutor: ExecutorService
|
private lateinit var cameraExecutor: ExecutorService
|
||||||
|
|
||||||
|
private var outputDirectory: DocumentFile? = null
|
||||||
private var imageCapture: ImageCapture? = null
|
private var imageCapture: ImageCapture? = null
|
||||||
private var displayId = -1
|
private var displayId = -1
|
||||||
private var cameraProvider: ProcessCameraProvider? = null
|
private var cameraProvider: ProcessCameraProvider? = null
|
||||||
@ -55,7 +52,7 @@ class CameraActivity : BaseLanguageActivity() {
|
|||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
Utils.transparentStatusBar(this, true, false)
|
Utils.transparentStatusBar(this, true, false)
|
||||||
displayManager = getSystemService(DISPLAY_SERVICE) as DisplayManager
|
displayManager = getSystemService(DISPLAY_SERVICE) as DisplayManager
|
||||||
outputDirectory = DirectoryUtils.getOutputMediaDirectory(this, "Camera")
|
outputDirectory = DownloadUtils.getCameraDir()
|
||||||
cameraExecutor = Executors.newSingleThreadExecutor()
|
cameraExecutor = Executors.newSingleThreadExecutor()
|
||||||
displayManager.registerDisplayListener(displayListener, null)
|
displayManager.registerDisplayListener(displayListener, null)
|
||||||
binding.viewFinder.post {
|
binding.viewFinder.post {
|
||||||
@ -176,33 +173,28 @@ class CameraActivity : BaseLanguageActivity() {
|
|||||||
|
|
||||||
private fun takePhoto() {
|
private fun takePhoto() {
|
||||||
if (imageCapture == null) return
|
if (imageCapture == null) return
|
||||||
val photoFile = File(outputDirectory, simpleDateFormat.format(System.currentTimeMillis()) + ".jpg")
|
val fileName = simpleDateFormat.format(System.currentTimeMillis()) + ".jpg"
|
||||||
val outputFileOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()
|
val mimeType = "image/jpg"
|
||||||
|
val photoFile = outputDirectory?.createFile(mimeType, fileName)?.let { it } ?: return
|
||||||
|
val outputStream = contentResolver.openOutputStream(photoFile.uri)?.let { it } ?: return
|
||||||
|
val outputFileOptions = ImageCapture.OutputFileOptions.Builder(outputStream).build()
|
||||||
imageCapture?.takePicture(
|
imageCapture?.takePicture(
|
||||||
outputFileOptions,
|
outputFileOptions,
|
||||||
cameraExecutor,
|
cameraExecutor,
|
||||||
object : ImageCapture.OnImageSavedCallback {
|
object : ImageCapture.OnImageSavedCallback {
|
||||||
@Suppress("UnstableApiUsage")
|
@Suppress("UnstableApiUsage")
|
||||||
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
|
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
|
||||||
val uri = Uri.fromFile(photoFile)
|
try { outputStream.close() } catch (ignored: IOException) {}
|
||||||
val mimeType = MimeTypeMap.getSingleton()
|
|
||||||
.getMimeTypeFromExtension(Files.getFileExtension(photoFile.name))
|
|
||||||
MediaScannerConnection.scanFile(
|
|
||||||
this@CameraActivity,
|
|
||||||
arrayOf(photoFile.absolutePath),
|
|
||||||
arrayOf(mimeType)
|
|
||||||
) { _: String?, uri1: Uri? ->
|
|
||||||
Log.d(TAG, "onImageSaved: scan complete")
|
|
||||||
val intent = Intent()
|
val intent = Intent()
|
||||||
intent.data = uri1
|
intent.data = photoFile.uri
|
||||||
setResult(RESULT_OK, intent)
|
setResult(RESULT_OK, intent)
|
||||||
finish()
|
finish()
|
||||||
}
|
Log.d(TAG, "onImageSaved: " + photoFile.uri)
|
||||||
Log.d(TAG, "onImageSaved: $uri")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onError(exception: ImageCaptureException) {
|
override fun onError(exception: ImageCaptureException) {
|
||||||
Log.e(TAG, "onError: ", exception)
|
Log.e(TAG, "onError: ", exception)
|
||||||
|
try { outputStream.close() } catch (ignored: IOException) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@ import android.content.ComponentName
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.ServiceConnection
|
import android.content.ServiceConnection
|
||||||
import android.os.*
|
import android.os.*
|
||||||
|
import android.provider.DocumentsContract.EXTRA_INITIAL_URI
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
@ -53,6 +54,7 @@ import awais.instagrabber.services.ActivityCheckerService
|
|||||||
import awais.instagrabber.services.DMSyncAlarmReceiver
|
import awais.instagrabber.services.DMSyncAlarmReceiver
|
||||||
import awais.instagrabber.utils.*
|
import awais.instagrabber.utils.*
|
||||||
import awais.instagrabber.utils.AppExecutors.tasksThread
|
import awais.instagrabber.utils.AppExecutors.tasksThread
|
||||||
|
import awais.instagrabber.utils.DownloadUtils.ReselectDocumentTreeException
|
||||||
import awais.instagrabber.utils.TextUtils.isEmpty
|
import awais.instagrabber.utils.TextUtils.isEmpty
|
||||||
import awais.instagrabber.utils.TextUtils.shortcodeToId
|
import awais.instagrabber.utils.TextUtils.shortcodeToId
|
||||||
import awais.instagrabber.utils.emoji.EmojiParser
|
import awais.instagrabber.utils.emoji.EmojiParser
|
||||||
@ -73,6 +75,7 @@ import kotlinx.coroutines.withContext
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.stream.Collectors
|
import java.util.stream.Collectors
|
||||||
|
|
||||||
|
|
||||||
class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedListener {
|
class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedListener {
|
||||||
private lateinit var binding: ActivityMainBinding
|
private lateinit var binding: ActivityMainBinding
|
||||||
|
|
||||||
@ -107,6 +110,16 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL
|
|||||||
private val graphQLRepository: GraphQLRepository by lazy { GraphQLRepository.getInstance() }
|
private val graphQLRepository: GraphQLRepository by lazy { GraphQLRepository.getInstance() }
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
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)
|
super.onCreate(savedInstanceState)
|
||||||
instance = this
|
instance = this
|
||||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||||
|
@ -11,6 +11,7 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
import androidx.fragment.app.DialogFragment;
|
import androidx.fragment.app.DialogFragment;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.fragment.app.FragmentActivity;
|
||||||
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
|
|
||||||
@ -73,6 +74,7 @@ public class ConfirmDialogFragment extends DialogFragment {
|
|||||||
ConfirmDialogFragment fragment = new ConfirmDialogFragment();
|
ConfirmDialogFragment fragment = new ConfirmDialogFragment();
|
||||||
fragment.setArguments(args);
|
fragment.setArguments(args);
|
||||||
return fragment;
|
return fragment;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConfirmDialogFragment() {}
|
public ConfirmDialogFragment() {}
|
||||||
@ -80,11 +82,16 @@ public class ConfirmDialogFragment extends DialogFragment {
|
|||||||
@Override
|
@Override
|
||||||
public void onAttach(@NonNull final Context context) {
|
public void onAttach(@NonNull final Context context) {
|
||||||
super.onAttach(context);
|
super.onAttach(context);
|
||||||
|
this.context = context;
|
||||||
final Fragment parentFragment = getParentFragment();
|
final Fragment parentFragment = getParentFragment();
|
||||||
if (parentFragment instanceof ConfirmDialogFragmentCallback) {
|
if (parentFragment instanceof ConfirmDialogFragmentCallback) {
|
||||||
callback = (ConfirmDialogFragmentCallback) parentFragment;
|
callback = (ConfirmDialogFragmentCallback) parentFragment;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final FragmentActivity fragmentActivity = getActivity();
|
||||||
|
if (fragmentActivity instanceof ConfirmDialogFragmentCallback) {
|
||||||
|
callback = (ConfirmDialogFragmentCallback) fragmentActivity;
|
||||||
}
|
}
|
||||||
this.context = context;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
@ -2,8 +2,10 @@ package awais.instagrabber.dialogs;
|
|||||||
|
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.Intent;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.provider.DocumentsContract;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@ -14,27 +16,26 @@ import android.view.inputmethod.InputMethodManager;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.fragment.app.DialogFragment;
|
import androidx.fragment.app.DialogFragment;
|
||||||
import androidx.fragment.app.FragmentTransaction;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import awais.instagrabber.databinding.DialogCreateBackupBinding;
|
import awais.instagrabber.databinding.DialogCreateBackupBinding;
|
||||||
import awais.instagrabber.utils.DirectoryChooser;
|
import awais.instagrabber.utils.DownloadUtils;
|
||||||
import awais.instagrabber.utils.ExportImportUtils;
|
import awais.instagrabber.utils.ExportImportUtils;
|
||||||
import awais.instagrabber.utils.TextUtils;
|
import awais.instagrabber.utils.TextUtils;
|
||||||
import awais.instagrabber.utils.Utils;
|
import awais.instagrabber.utils.Utils;
|
||||||
|
|
||||||
import static awais.instagrabber.fragments.settings.PreferenceKeys.FOLDER_PATH;
|
import static android.app.Activity.RESULT_OK;
|
||||||
import static awais.instagrabber.utils.DownloadUtils.PERMS;
|
|
||||||
|
|
||||||
public class CreateBackupDialogFragment extends DialogFragment {
|
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 int STORAGE_PERM_REQUEST_CODE = 8020;
|
||||||
private static final DateTimeFormatter BACKUP_FILE_DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("yyyyMMddHHmmss", Locale.US);
|
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 final OnResultListener onResultListener;
|
||||||
private DialogCreateBackupBinding binding;
|
private DialogCreateBackupBinding binding;
|
||||||
@ -113,36 +114,37 @@ public class CreateBackupDialogFragment extends DialogFragment {
|
|||||||
imm.hideSoftInputFromWindow(binding.etPassword.getWindowToken(), InputMethodManager.RESULT_UNCHANGED_SHOWN);
|
imm.hideSoftInputFromWindow(binding.etPassword.getWindowToken(), InputMethodManager.RESULT_UNCHANGED_SHOWN);
|
||||||
});
|
});
|
||||||
binding.btnSaveTo.setOnClickListener(v -> {
|
binding.btnSaveTo.setOnClickListener(v -> {
|
||||||
if (ContextCompat.checkSelfPermission(context, PERMS[0]) == PackageManager.PERMISSION_GRANTED) {
|
createFile();
|
||||||
showChooser(context);
|
// if (ContextCompat.checkSelfPermission(context, PERMS[0]) == PackageManager.PERMISSION_GRANTED) {
|
||||||
} else {
|
// showChooser(context);
|
||||||
requestPermissions(PERMS, STORAGE_PERM_REQUEST_CODE);
|
// } else {
|
||||||
}
|
// requestPermissions(PERMS, STORAGE_PERM_REQUEST_CODE);
|
||||||
|
// }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
|
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
if (requestCode == STORAGE_PERM_REQUEST_CODE && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
// if (requestCode == STORAGE_PERM_REQUEST_CODE && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
final Context context = getContext();
|
// final Context context = getContext();
|
||||||
if (context == null) return;
|
// if (context == null) return;
|
||||||
showChooser(context);
|
// showChooser(context);
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showChooser(@NonNull final Context context) {
|
@Override
|
||||||
final String folderPath = Utils.settingsHelper.getString(FOLDER_PATH);
|
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 Editable passwordText = binding.etPassword.getText();
|
||||||
final String password = binding.cbPassword.isChecked()
|
final String password = binding.cbPassword.isChecked()
|
||||||
&& passwordText != null
|
&& passwordText != null
|
||||||
&& !TextUtils.isEmpty(passwordText.toString())
|
&& !TextUtils.isEmpty(passwordText.toString())
|
||||||
? passwordText.toString().trim()
|
? passwordText.toString().trim()
|
||||||
: null;
|
: 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;
|
int flags = 0;
|
||||||
if (binding.cbExportFavorites.isChecked()) {
|
if (binding.cbExportFavorites.isChecked()) {
|
||||||
flags |= ExportImportUtils.FLAG_FAVORITES;
|
flags |= ExportImportUtils.FLAG_FAVORITES;
|
||||||
@ -153,19 +155,71 @@ public class CreateBackupDialogFragment extends DialogFragment {
|
|||||||
if (binding.cbExportLogins.isChecked()) {
|
if (binding.cbExportLogins.isChecked()) {
|
||||||
flags |= ExportImportUtils.FLAG_COOKIES;
|
flags |= ExportImportUtils.FLAG_COOKIES;
|
||||||
}
|
}
|
||||||
ExportImportUtils.exportData(context, flags, file, password, result -> {
|
ExportImportUtils.exportData(context, flags, data.getData(), password, result -> {
|
||||||
if (onResultListener != null) {
|
if (onResultListener != null) {
|
||||||
onResultListener.onResult(result);
|
onResultListener.onResult(result);
|
||||||
}
|
}
|
||||||
dismiss();
|
dismiss();
|
||||||
});
|
});
|
||||||
|
// try (final OutputStream stream = context.getContentResolver().openOutputStream(data.getData())) {
|
||||||
});
|
// } catch (Exception e) {
|
||||||
directoryChooser.setEnterTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
|
// Log.e(TAG, "onActivityResult: ", e);
|
||||||
directoryChooser.setExitTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
|
// }
|
||||||
directoryChooser.show(getChildFragmentManager(), "directory_chooser");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 {
|
public interface OnResultListener {
|
||||||
void onResult(boolean result);
|
void onResult(boolean result);
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ import android.graphics.Color;
|
|||||||
import android.graphics.drawable.Animatable;
|
import android.graphics.drawable.Animatable;
|
||||||
import android.graphics.drawable.ColorDrawable;
|
import android.graphics.drawable.ColorDrawable;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Environment;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@ -16,7 +15,7 @@ import android.widget.Toast;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.documentfile.provider.DocumentFile;
|
||||||
import androidx.fragment.app.DialogFragment;
|
import androidx.fragment.app.DialogFragment;
|
||||||
|
|
||||||
import com.facebook.drawee.backends.pipeline.Fresco;
|
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.drawee.interfaces.DraweeController;
|
||||||
import com.facebook.imagepipeline.image.ImageInfo;
|
import com.facebook.imagepipeline.image.ImageInfo;
|
||||||
|
|
||||||
import java.io.File;
|
// import java.io.File;
|
||||||
|
|
||||||
import awais.instagrabber.R;
|
import awais.instagrabber.R;
|
||||||
import awais.instagrabber.customviews.drawee.AnimatedZoomableController;
|
import awais.instagrabber.customviews.drawee.AnimatedZoomableController;
|
||||||
@ -36,6 +35,7 @@ import awais.instagrabber.utils.CookieUtils;
|
|||||||
import awais.instagrabber.utils.CoroutineUtilsKt;
|
import awais.instagrabber.utils.CoroutineUtilsKt;
|
||||||
import awais.instagrabber.utils.DownloadUtils;
|
import awais.instagrabber.utils.DownloadUtils;
|
||||||
import awais.instagrabber.utils.TextUtils;
|
import awais.instagrabber.utils.TextUtils;
|
||||||
|
import awais.instagrabber.utils.Utils;
|
||||||
import awais.instagrabber.webservices.UserRepository;
|
import awais.instagrabber.webservices.UserRepository;
|
||||||
import kotlinx.coroutines.Dispatchers;
|
import kotlinx.coroutines.Dispatchers;
|
||||||
|
|
||||||
@ -114,11 +114,11 @@ public class ProfilePicDialogFragment extends DialogFragment {
|
|||||||
binding.download.setOnClickListener(v -> {
|
binding.download.setOnClickListener(v -> {
|
||||||
final Context context = getContext();
|
final Context context = getContext();
|
||||||
if (context == null) return;
|
if (context == null) return;
|
||||||
if (ContextCompat.checkSelfPermission(context, DownloadUtils.PERMS[0]) == PackageManager.PERMISSION_GRANTED) {
|
// if (ContextCompat.checkSelfPermission(context, DownloadUtils.PERMS[0]) == PackageManager.PERMISSION_GRANTED) {
|
||||||
downloadProfilePicture();
|
downloadProfilePicture();
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
requestPermissions(DownloadUtils.PERMS, 8020);
|
// requestPermissions(DownloadUtils.PERMS, 8020);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,14 +195,18 @@ public class ProfilePicDialogFragment extends DialogFragment {
|
|||||||
|
|
||||||
private void downloadProfilePicture() {
|
private void downloadProfilePicture() {
|
||||||
if (url == null) return;
|
if (url == null) return;
|
||||||
final File dir = new File(Environment.getExternalStorageDirectory(), "Download");
|
// final File dir = new File(Environment.getExternalStorageDirectory(), "Download");
|
||||||
final Context context = getContext();
|
final Context context = getContext();
|
||||||
if (context == null) return;
|
if (context == null) return;
|
||||||
if (dir.exists() || dir.mkdirs()) {
|
// if (dir.exists() || dir.mkdirs()) {
|
||||||
final File saveFile = new File(dir, name + '_' + System.currentTimeMillis() + ".jpg");
|
//
|
||||||
DownloadUtils.download(context, url, saveFile.getAbsolutePath());
|
// }
|
||||||
return;
|
final String fileName = name + '_' + System.currentTimeMillis() + ".jpg";
|
||||||
}
|
// final File saveFile = new File(dir, fileName);
|
||||||
Toast.makeText(context, R.string.downloader_error_creating_folder, Toast.LENGTH_SHORT).show();
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,18 @@
|
|||||||
package awais.instagrabber.dialogs;
|
package awais.instagrabber.dialogs;
|
||||||
|
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
|
import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
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.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.provider.MediaStore;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@ -15,30 +21,28 @@ import android.view.inputmethod.InputMethodManager;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.fragment.app.DialogFragment;
|
import androidx.fragment.app.DialogFragment;
|
||||||
import androidx.fragment.app.FragmentTransaction;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
import awais.instagrabber.databinding.DialogRestoreBackupBinding;
|
import awais.instagrabber.databinding.DialogRestoreBackupBinding;
|
||||||
import awais.instagrabber.utils.DirectoryChooser;
|
import awais.instagrabber.utils.AppExecutors;
|
||||||
import awais.instagrabber.utils.ExportImportUtils;
|
import awais.instagrabber.utils.ExportImportUtils;
|
||||||
import awais.instagrabber.utils.PasswordUtils.IncorrectPasswordException;
|
import awais.instagrabber.utils.PasswordUtils.IncorrectPasswordException;
|
||||||
import awais.instagrabber.utils.TextUtils;
|
import awais.instagrabber.utils.TextUtils;
|
||||||
import awais.instagrabber.utils.Utils;
|
import awais.instagrabber.utils.Utils;
|
||||||
|
|
||||||
import static awais.instagrabber.fragments.settings.PreferenceKeys.FOLDER_PATH;
|
import static android.app.Activity.RESULT_OK;
|
||||||
import static awais.instagrabber.utils.DownloadUtils.PERMS;
|
|
||||||
|
|
||||||
public class RestoreBackupDialogFragment extends DialogFragment {
|
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 STORAGE_PERM_REQUEST_CODE = 8020;
|
||||||
|
private static final int OPEN_FILE_REQUEST_CODE = 1;
|
||||||
|
|
||||||
private OnResultListener onResultListener;
|
private OnResultListener onResultListener;
|
||||||
|
|
||||||
private DialogRestoreBackupBinding binding;
|
private DialogRestoreBackupBinding binding;
|
||||||
private File file;
|
// private File file;
|
||||||
private boolean isEncrypted;
|
private boolean isEncrypted;
|
||||||
|
private Uri uri;
|
||||||
|
|
||||||
public RestoreBackupDialogFragment() {}
|
public RestoreBackupDialogFragment() {}
|
||||||
|
|
||||||
@ -83,18 +87,62 @@ public class RestoreBackupDialogFragment extends DialogFragment {
|
|||||||
@Override
|
@Override
|
||||||
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
|
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
if (requestCode == STORAGE_PERM_REQUEST_CODE && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
// if (requestCode == STORAGE_PERM_REQUEST_CODE && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
showChooser();
|
// 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() {
|
private void init() {
|
||||||
final Context context = getContext();
|
final Context context = getContext();
|
||||||
if (context == null) {
|
if (context == null) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
binding.btnRestore.setEnabled(false);
|
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;
|
int flags = 0;
|
||||||
if (binding.cbFavorites.isChecked()) {
|
if (binding.cbFavorites.isChecked()) {
|
||||||
flags |= ExportImportUtils.FLAG_FAVORITES;
|
flags |= ExportImportUtils.FLAG_FAVORITES;
|
||||||
@ -111,7 +159,7 @@ public class RestoreBackupDialogFragment extends DialogFragment {
|
|||||||
ExportImportUtils.importData(
|
ExportImportUtils.importData(
|
||||||
context,
|
context,
|
||||||
flags,
|
flags,
|
||||||
file,
|
uri,
|
||||||
!isEncrypted ? null : text.toString(),
|
!isEncrypted ? null : text.toString(),
|
||||||
result -> {
|
result -> {
|
||||||
if (onResultListener != null) {
|
if (onResultListener != null) {
|
||||||
@ -137,45 +185,55 @@ public class RestoreBackupDialogFragment extends DialogFragment {
|
|||||||
@Override
|
@Override
|
||||||
public void afterTextChanged(final Editable s) {}
|
public void afterTextChanged(final Editable s) {}
|
||||||
});
|
});
|
||||||
if (ContextCompat.checkSelfPermission(context, PERMS[0]) == PackageManager.PERMISSION_GRANTED) {
|
// if (ContextCompat.checkSelfPermission(context, PERMS[0]) == PackageManager.PERMISSION_GRANTED) {
|
||||||
showChooser();
|
// showChooser();
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
requestPermissions(PERMS, STORAGE_PERM_REQUEST_CODE);
|
// 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() {
|
// private void showChooser() {
|
||||||
final String folderPath = Utils.settingsHelper.getString(FOLDER_PATH);
|
// final String folderPath = Utils.settingsHelper.getString(FOLDER_PATH);
|
||||||
final Context context = getContext();
|
// final Context context = getContext();
|
||||||
if (context == null) return;
|
// if (context == null) return;
|
||||||
final DirectoryChooser directoryChooser = new DirectoryChooser()
|
// final DirectoryChooser directoryChooser = new DirectoryChooser()
|
||||||
.setInitialDirectory(folderPath)
|
// .setInitialDirectory(folderPath)
|
||||||
.setShowBackupFiles(true)
|
// .setShowBackupFiles(true)
|
||||||
.setInteractionListener(file -> {
|
// .setInteractionListener(file -> {
|
||||||
isEncrypted = ExportImportUtils.isEncrypted(file);
|
// isEncrypted = ExportImportUtils.isEncrypted(file);
|
||||||
if (isEncrypted) {
|
// if (isEncrypted) {
|
||||||
binding.passwordGroup.setVisibility(View.VISIBLE);
|
// binding.passwordGroup.setVisibility(View.VISIBLE);
|
||||||
binding.passwordGroup.post(() -> {
|
// binding.passwordGroup.post(() -> {
|
||||||
binding.etPassword.requestFocus();
|
// binding.etPassword.requestFocus();
|
||||||
binding.etPassword.post(() -> {
|
// binding.etPassword.post(() -> {
|
||||||
final InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
|
// final InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
if (imm == null) return;
|
// if (imm == null) return;
|
||||||
imm.showSoftInput(binding.etPassword, InputMethodManager.SHOW_IMPLICIT);
|
// imm.showSoftInput(binding.etPassword, InputMethodManager.SHOW_IMPLICIT);
|
||||||
});
|
// });
|
||||||
binding.btnRestore.setEnabled(!TextUtils.isEmpty(binding.etPassword.getText()));
|
// binding.btnRestore.setEnabled(!TextUtils.isEmpty(binding.etPassword.getText()));
|
||||||
});
|
// });
|
||||||
} else {
|
// } else {
|
||||||
binding.passwordGroup.setVisibility(View.GONE);
|
// binding.passwordGroup.setVisibility(View.GONE);
|
||||||
binding.btnRestore.setEnabled(true);
|
// binding.btnRestore.setEnabled(true);
|
||||||
}
|
// }
|
||||||
this.file = file;
|
// this.file = file;
|
||||||
binding.filePath.setText(file.getAbsolutePath());
|
// binding.filePath.setText(file.getAbsolutePath());
|
||||||
});
|
// });
|
||||||
directoryChooser.setEnterTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
|
// directoryChooser.setEnterTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
|
||||||
directoryChooser.setExitTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
|
// directoryChooser.setExitTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
|
||||||
directoryChooser.setOnCancelListener(this::dismiss);
|
// directoryChooser.setOnCancelListener(this::dismiss);
|
||||||
directoryChooser.show(getChildFragmentManager(), "directory_chooser");
|
// directoryChooser.show(getChildFragmentManager(), "directory_chooser");
|
||||||
}
|
// }
|
||||||
|
|
||||||
public interface OnResultListener {
|
public interface OnResultListener {
|
||||||
void onResult(boolean result);
|
void onResult(boolean result);
|
||||||
|
@ -27,7 +27,6 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||||
import androidx.core.content.PermissionChecker;
|
|
||||||
import androidx.core.graphics.ColorUtils;
|
import androidx.core.graphics.ColorUtils;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.navigation.NavController;
|
import androidx.navigation.NavController;
|
||||||
@ -65,9 +64,6 @@ import awais.instagrabber.utils.Utils;
|
|||||||
import awais.instagrabber.webservices.CollectionService;
|
import awais.instagrabber.webservices.CollectionService;
|
||||||
import awais.instagrabber.webservices.ServiceCallback;
|
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 {
|
public class CollectionPostsFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
|
||||||
private static final String TAG = "CollectionPostsFragment";
|
private static final String TAG = "CollectionPostsFragment";
|
||||||
private static final int STORAGE_PERM_REQUEST_CODE = 8020;
|
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;
|
if (CollectionPostsFragment.this.selectedFeedModels == null) return false;
|
||||||
final Context context = getContext();
|
final Context context = getContext();
|
||||||
if (context == null) return false;
|
if (context == null) return false;
|
||||||
if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
|
// if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
|
||||||
DownloadUtils.download(context, ImmutableList.copyOf(CollectionPostsFragment.this.selectedFeedModels));
|
DownloadUtils.download(context, ImmutableList.copyOf(CollectionPostsFragment.this.selectedFeedModels));
|
||||||
binding.posts.endSelection();
|
binding.posts.endSelection();
|
||||||
return true;
|
// return true;
|
||||||
}
|
// }
|
||||||
requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION);
|
// requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -141,13 +137,13 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay
|
|||||||
public void onDownloadClick(final Media feedModel, final int childPosition) {
|
public void onDownloadClick(final Media feedModel, final int childPosition) {
|
||||||
final Context context = getContext();
|
final Context context = getContext();
|
||||||
if (context == null) return;
|
if (context == null) return;
|
||||||
if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
|
// if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
|
||||||
DownloadUtils.showDownloadDialog(context, feedModel, childPosition);
|
DownloadUtils.showDownloadDialog(context, feedModel, childPosition);
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
downloadFeedModel = feedModel;
|
// downloadFeedModel = feedModel;
|
||||||
downloadChildPosition = -1;
|
// downloadChildPosition = -1;
|
||||||
requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE);
|
// requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -105,7 +105,7 @@ import awais.instagrabber.utils.TextUtils;
|
|||||||
import awais.instagrabber.utils.Utils;
|
import awais.instagrabber.utils.Utils;
|
||||||
import awais.instagrabber.viewmodels.PostViewV2ViewModel;
|
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.HashTagFragment.ARG_HASHTAG;
|
||||||
import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_SHOWN_COUNT_TOOLTIP;
|
import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_SHOWN_COUNT_TOOLTIP;
|
||||||
import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION;
|
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 static final int STORAGE_PERM_REQUEST_CODE = 8020;
|
||||||
|
|
||||||
private DialogPostViewBinding binding;
|
private DialogPostViewBinding binding;
|
||||||
|
private Context context;
|
||||||
private boolean detailsVisible = true;
|
private boolean detailsVisible = true;
|
||||||
private boolean video;
|
private boolean video;
|
||||||
private VideoPlayerViewHelper videoPlayerViewHelper;
|
private VideoPlayerViewHelper videoPlayerViewHelper;
|
||||||
@ -211,6 +212,12 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme
|
|||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(@NonNull final Context context) {
|
||||||
|
super.onAttach(context);
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPause() {
|
public void onPause() {
|
||||||
super.onPause();
|
super.onPause();
|
||||||
@ -454,13 +461,7 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme
|
|||||||
|
|
||||||
private void setupDownload() {
|
private void setupDownload() {
|
||||||
bottom.download.setOnClickListener(v -> {
|
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);
|
DownloadUtils.showDownloadDialog(context, viewModel.getMedia(), sliderPosition);
|
||||||
return;
|
|
||||||
}
|
|
||||||
requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE);
|
|
||||||
});
|
});
|
||||||
TooltipCompat.setTooltipText(bottom.download, getString(R.string.action_download));
|
TooltipCompat.setTooltipText(bottom.download, getString(R.string.action_download));
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,6 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.core.content.PermissionChecker;
|
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.navigation.NavController;
|
import androidx.navigation.NavController;
|
||||||
import androidx.navigation.NavDirections;
|
import androidx.navigation.NavDirections;
|
||||||
@ -46,8 +45,6 @@ import awais.instagrabber.utils.DownloadUtils;
|
|||||||
import awais.instagrabber.utils.TextUtils;
|
import awais.instagrabber.utils.TextUtils;
|
||||||
import awais.instagrabber.utils.Utils;
|
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;
|
import static awais.instagrabber.utils.Utils.settingsHelper;
|
||||||
|
|
||||||
public final class SavedViewerFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
|
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;
|
if (SavedViewerFragment.this.selectedFeedModels == null) return false;
|
||||||
final Context context = getContext();
|
final Context context = getContext();
|
||||||
if (context == null) return false;
|
if (context == null) return false;
|
||||||
if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
|
// if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
|
||||||
DownloadUtils.download(context, ImmutableList.copyOf(SavedViewerFragment.this.selectedFeedModels));
|
DownloadUtils.download(context, ImmutableList.copyOf(SavedViewerFragment.this.selectedFeedModels));
|
||||||
binding.posts.endSelection();
|
binding.posts.endSelection();
|
||||||
return true;
|
// return true;
|
||||||
}
|
// }
|
||||||
requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION);
|
// requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -123,13 +120,13 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL
|
|||||||
public void onDownloadClick(final Media feedModel, final int childPosition) {
|
public void onDownloadClick(final Media feedModel, final int childPosition) {
|
||||||
final Context context = getContext();
|
final Context context = getContext();
|
||||||
if (context == null) return;
|
if (context == null) return;
|
||||||
if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
|
// if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
|
||||||
DownloadUtils.showDownloadDialog(context, feedModel, childPosition);
|
DownloadUtils.showDownloadDialog(context, feedModel, childPosition);
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
downloadFeedModel = feedModel;
|
// downloadFeedModel = feedModel;
|
||||||
downloadChildPosition = childPosition;
|
// downloadChildPosition = childPosition;
|
||||||
requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE);
|
// requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -24,7 +24,6 @@ import androidx.activity.OnBackPressedDispatcher;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||||
import androidx.core.content.PermissionChecker;
|
|
||||||
import androidx.core.graphics.ColorUtils;
|
import androidx.core.graphics.ColorUtils;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.navigation.NavController;
|
import androidx.navigation.NavController;
|
||||||
@ -60,9 +59,6 @@ import awais.instagrabber.utils.ResponseBodyUtils;
|
|||||||
import awais.instagrabber.utils.Utils;
|
import awais.instagrabber.utils.Utils;
|
||||||
import awais.instagrabber.webservices.DiscoverService;
|
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 {
|
public class TopicPostsFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
|
||||||
private static final String TAG = TopicPostsFragment.class.getSimpleName();
|
private static final String TAG = TopicPostsFragment.class.getSimpleName();
|
||||||
private static final int STORAGE_PERM_REQUEST_CODE = 8020;
|
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;
|
if (TopicPostsFragment.this.selectedFeedModels == null) return false;
|
||||||
final Context context = getContext();
|
final Context context = getContext();
|
||||||
if (context == null) return false;
|
if (context == null) return false;
|
||||||
if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
|
// if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
|
||||||
DownloadUtils.download(context, ImmutableList.copyOf(TopicPostsFragment.this.selectedFeedModels));
|
DownloadUtils.download(context, ImmutableList.copyOf(TopicPostsFragment.this.selectedFeedModels));
|
||||||
binding.posts.endSelection();
|
binding.posts.endSelection();
|
||||||
return true;
|
return true;
|
||||||
}
|
// }
|
||||||
requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION);
|
// requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -134,13 +130,13 @@ public class TopicPostsFragment extends Fragment implements SwipeRefreshLayout.O
|
|||||||
public void onDownloadClick(final Media feedModel, final int childPosition) {
|
public void onDownloadClick(final Media feedModel, final int childPosition) {
|
||||||
final Context context = getContext();
|
final Context context = getContext();
|
||||||
if (context == null) return;
|
if (context == null) return;
|
||||||
if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
|
// if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
|
||||||
DownloadUtils.showDownloadDialog(context, feedModel, childPosition);
|
DownloadUtils.showDownloadDialog(context, feedModel, childPosition);
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
downloadFeedModel = feedModel;
|
// downloadFeedModel = feedModel;
|
||||||
downloadChildPosition = -1;
|
// downloadChildPosition = -1;
|
||||||
requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE);
|
// requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1408,13 +1408,13 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
|
|||||||
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
|
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (ContextCompat.checkSelfPermission(context, DownloadUtils.PERMS[0]) == PackageManager.PERMISSION_GRANTED) {
|
// if (ContextCompat.checkSelfPermission(context, DownloadUtils.PERMS[0]) == PackageManager.PERMISSION_GRANTED) {
|
||||||
DownloadUtils.download(context, media);
|
DownloadUtils.download(context, media);
|
||||||
Toast.makeText(context, R.string.downloader_downloading_media, Toast.LENGTH_SHORT).show();
|
Toast.makeText(context, R.string.downloader_downloading_media, Toast.LENGTH_SHORT).show();
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
tempMedia = media;
|
// tempMedia = media;
|
||||||
requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE);
|
// requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@ -183,11 +183,12 @@ public class ImageEditFragment extends Fragment {
|
|||||||
if (context == null) return;
|
if (context == null) return;
|
||||||
final Uri resultUri = viewModel.getResultUri().getValue();
|
final Uri resultUri = viewModel.getResultUri().getValue();
|
||||||
if (resultUri == null) return;
|
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);
|
final NavController navController = NavHostFragment.findNavController(this);
|
||||||
setNavControllerResult(navController, resultUri);
|
setNavControllerResult(navController, resultUri);
|
||||||
navController.navigateUp();
|
navController.navigateUp();
|
||||||
}));
|
});
|
||||||
|
// Utils.mediaScanFile(context, new File(resultUri.toString()), (path, uri) -> );
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,27 +1,41 @@
|
|||||||
package awais.instagrabber.fragments.settings;
|
package awais.instagrabber.fragments.settings;
|
||||||
|
|
||||||
import android.content.Context;
|
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.annotation.NonNull;
|
||||||
import androidx.appcompat.widget.AppCompatButton;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.widget.AppCompatTextView;
|
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
import androidx.preference.PreferenceScreen;
|
import androidx.preference.PreferenceScreen;
|
||||||
import androidx.preference.PreferenceViewHolder;
|
|
||||||
import androidx.preference.SwitchPreferenceCompat;
|
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.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.TextUtils;
|
||||||
|
import awais.instagrabber.utils.Utils;
|
||||||
|
|
||||||
import static awais.instagrabber.fragments.settings.PreferenceKeys.FOLDER_PATH;
|
import static android.app.Activity.RESULT_OK;
|
||||||
import static awais.instagrabber.fragments.settings.PreferenceKeys.FOLDER_SAVE_TO;
|
import static awais.instagrabber.activities.DirectorySelectActivity.SELECT_DIR_REQUEST_CODE;
|
||||||
import static awais.instagrabber.utils.Utils.settingsHelper;
|
import static awais.instagrabber.utils.Utils.settingsHelper;
|
||||||
|
|
||||||
public class DownloadsPreferencesFragment extends BasePreferencesFragment {
|
public class DownloadsPreferencesFragment extends BasePreferencesFragment {
|
||||||
|
private static final String TAG = DownloadsPreferencesFragment.class.getSimpleName();
|
||||||
|
// private SaveToCustomFolderPreference.ResultCallback resultCallback;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void setupPreferenceScreen(final PreferenceScreen screen) {
|
void setupPreferenceScreen(final PreferenceScreen screen) {
|
||||||
final Context context = getContext();
|
final Context context = getContext();
|
||||||
@ -40,13 +54,88 @@ public class DownloadsPreferencesFragment extends BasePreferencesFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Preference getSaveToCustomFolderPreference(@NonNull final Context context) {
|
private Preference getSaveToCustomFolderPreference(@NonNull final Context context) {
|
||||||
return new SaveToCustomFolderPreference(context, (resultCallback) -> new DirectoryChooser()
|
final Preference preference = new Preference(context);
|
||||||
.setInitialDirectory(settingsHelper.getString(FOLDER_PATH))
|
preference.setKey(PreferenceKeys.PREF_BARINSTA_DIR_URI);
|
||||||
.setInteractionListener(file -> {
|
preference.setIconSpaceReserved(false);
|
||||||
settingsHelper.putString(FOLDER_PATH, file.getAbsolutePath());
|
preference.setTitle(R.string.barinsta_folder);
|
||||||
resultCallback.onResult(file.getAbsolutePath());
|
preference.setSummaryProvider(p -> {
|
||||||
})
|
final String currentValue = settingsHelper.getString(PreferenceKeys.PREF_BARINSTA_DIR_URI);
|
||||||
.show(getParentFragmentManager(), null));
|
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) {
|
private Preference getPrependUsernameToFilenamePreference(@NonNull final Context context) {
|
||||||
@ -57,53 +146,74 @@ public class DownloadsPreferencesFragment extends BasePreferencesFragment {
|
|||||||
return preference;
|
return preference;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class SaveToCustomFolderPreference extends Preference {
|
// public static class SaveToCustomFolderPreference extends Preference {
|
||||||
private AppCompatTextView customPathTextView;
|
// private AppCompatTextView customPathTextView;
|
||||||
private final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener;
|
// private final OnSaveToChangeListener onSaveToChangeListener;
|
||||||
private final String key;
|
// private final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener;
|
||||||
|
// private final String key;
|
||||||
public SaveToCustomFolderPreference(final Context context, final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener) {
|
//
|
||||||
super(context);
|
// public SaveToCustomFolderPreference(final Context context,
|
||||||
this.onSelectFolderButtonClickListener = onSelectFolderButtonClickListener;
|
// final OnSaveToChangeListener onSaveToChangeListener,
|
||||||
key = PreferenceKeys.FOLDER_SAVE_TO;
|
// final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener) {
|
||||||
setLayoutResource(R.layout.pref_custom_folder);
|
// super(context);
|
||||||
setKey(key);
|
// this.onSaveToChangeListener = onSaveToChangeListener;
|
||||||
setTitle(R.string.save_to_folder);
|
// this.onSelectFolderButtonClickListener = onSelectFolderButtonClickListener;
|
||||||
setIconSpaceReserved(false);
|
// key = FOLDER_SAVE_TO;
|
||||||
}
|
// setLayoutResource(R.layout.pref_custom_folder);
|
||||||
|
// setKey(key);
|
||||||
@Override
|
// setTitle(R.string.save_to_folder);
|
||||||
public void onBindViewHolder(final PreferenceViewHolder holder) {
|
// setIconSpaceReserved(false);
|
||||||
super.onBindViewHolder(holder);
|
// }
|
||||||
final SwitchMaterial cbSaveTo = (SwitchMaterial) holder.findViewById(R.id.cbSaveTo);
|
//
|
||||||
final View buttonContainer = holder.findViewById(R.id.button_container);
|
// @Override
|
||||||
customPathTextView = (AppCompatTextView) holder.findViewById(R.id.custom_path);
|
// public void onBindViewHolder(final PreferenceViewHolder holder) {
|
||||||
cbSaveTo.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
// super.onBindViewHolder(holder);
|
||||||
settingsHelper.putBoolean(FOLDER_SAVE_TO, isChecked);
|
// final SwitchMaterial cbSaveTo = (SwitchMaterial) holder.findViewById(R.id.cbSaveTo);
|
||||||
buttonContainer.setVisibility(isChecked ? View.VISIBLE : View.GONE);
|
// final View buttonContainer = holder.findViewById(R.id.button_container);
|
||||||
final String customPath = settingsHelper.getString(FOLDER_PATH);
|
// customPathTextView = (AppCompatTextView) holder.findViewById(R.id.custom_path);
|
||||||
customPathTextView.setText(customPath);
|
// cbSaveTo.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||||
});
|
// settingsHelper.putBoolean(FOLDER_SAVE_TO, isChecked);
|
||||||
final boolean savedToEnabled = settingsHelper.getBoolean(key);
|
// buttonContainer.setVisibility(isChecked ? View.VISIBLE : View.GONE);
|
||||||
holder.itemView.setOnClickListener(v -> cbSaveTo.toggle());
|
// final Context context = getContext();
|
||||||
cbSaveTo.setChecked(savedToEnabled);
|
// String customPath = settingsHelper.getString(FOLDER_PATH);
|
||||||
buttonContainer.setVisibility(savedToEnabled ? View.VISIBLE : View.GONE);
|
// if (!TextUtils.isEmpty(customPath) && customPath.startsWith("content") && context != null) {
|
||||||
final AppCompatButton btnSaveTo = (AppCompatButton) holder.findViewById(R.id.btnSaveTo);
|
// final Uri uri = Uri.parse(customPath);
|
||||||
btnSaveTo.setOnClickListener(v -> {
|
// final DocumentFile documentFile = DocumentFile.fromSingleUri(context, uri);
|
||||||
if (onSelectFolderButtonClickListener == null) return;
|
// try {
|
||||||
onSelectFolderButtonClickListener.onClick(result -> {
|
// customPath = Utils.getDocumentFileRealPath(context, documentFile).getAbsolutePath();
|
||||||
if (TextUtils.isEmpty(result)) return;
|
// } catch (Exception e) {
|
||||||
customPathTextView.setText(result);
|
// Log.e(TAG, "onBindViewHolder: ", e);
|
||||||
});
|
// }
|
||||||
});
|
// }
|
||||||
}
|
// customPathTextView.setText(customPath);
|
||||||
|
// if (onSaveToChangeListener != null) {
|
||||||
public interface ResultCallback {
|
// onSaveToChangeListener.onChange(isChecked);
|
||||||
void onResult(String result);
|
// }
|
||||||
}
|
// });
|
||||||
|
// final boolean savedToEnabled = settingsHelper.getBoolean(key);
|
||||||
public interface OnSelectFolderButtonClickListener {
|
// holder.itemView.setOnClickListener(v -> cbSaveTo.toggle());
|
||||||
void onClick(ResultCallback resultCallback);
|
// 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);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ object PreferenceKeys {
|
|||||||
const val APP_THEME = "app_theme_v19"
|
const val APP_THEME = "app_theme_v19"
|
||||||
const val APP_LANGUAGE = "app_language_v19"
|
const val APP_LANGUAGE = "app_language_v19"
|
||||||
const val STORY_SORT = "story_sort"
|
const val STORY_SORT = "story_sort"
|
||||||
|
const val PREF_BARINSTA_DIR_URI = "barinsta_dir_uri"
|
||||||
|
|
||||||
// set string prefs
|
// set string prefs
|
||||||
const val KEYWORD_FILTERS = "keyword_filters"
|
const val KEYWORD_FILTERS = "keyword_filters"
|
||||||
|
@ -4,13 +4,14 @@ import android.app.IntentService;
|
|||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.app.NotificationManagerCompat;
|
import androidx.core.app.NotificationManagerCompat;
|
||||||
|
import androidx.documentfile.provider.DocumentFile;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import awais.instagrabber.utils.TextUtils;
|
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)) {
|
if (intent != null && Intent.ACTION_DELETE.equals(intent.getAction()) && intent.hasExtra(EXTRA_IMAGE_PATH)) {
|
||||||
final String path = intent.getStringExtra(EXTRA_IMAGE_PATH);
|
final String path = intent.getStringExtra(EXTRA_IMAGE_PATH);
|
||||||
if (TextUtils.isEmpty(path)) return;
|
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;
|
boolean deleted;
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
deleted = file.delete();
|
deleted = file.delete();
|
||||||
@ -58,11 +62,11 @@ public class DeleteImageIntentService extends IntentService {
|
|||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public static PendingIntent pendingIntent(@NonNull final Context context,
|
public static PendingIntent pendingIntent(@NonNull final Context context,
|
||||||
@NonNull final String imagePath,
|
@NonNull final DocumentFile imagePath,
|
||||||
final int notificationId) {
|
final int notificationId) {
|
||||||
final Intent intent = new Intent(context, DeleteImageIntentService.class);
|
final Intent intent = new Intent(context, DeleteImageIntentService.class);
|
||||||
intent.setAction(Intent.ACTION_DELETE);
|
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);
|
intent.putExtra(EXTRA_NOTIFICATION_ID, notificationId);
|
||||||
return PendingIntent.getService(context, random.nextInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
return PendingIntent.getService(context, random.nextInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import android.net.Uri
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.util.LruCache
|
import android.util.LruCache
|
||||||
import androidx.core.util.Pair
|
import androidx.core.util.Pair
|
||||||
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import awais.instagrabber.utils.extensions.TAG
|
import awais.instagrabber.utils.extensions.TAG
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@ -192,9 +193,9 @@ object BitmapUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun convertToJpegAndSaveToFile(bitmap: Bitmap, file: File?): File {
|
fun convertToJpegAndSaveToFile(contentResolver: ContentResolver, bitmap: Bitmap, file: DocumentFile?): DocumentFile {
|
||||||
val tempFile = file ?: DownloadUtils.getTempFile()
|
val tempFile = file ?: DownloadUtils.getTempFile(null, "jpg")
|
||||||
FileOutputStream(tempFile).use { output ->
|
contentResolver.openOutputStream(tempFile.uri).use { output ->
|
||||||
val compressResult = bitmap.compress(Bitmap.CompressFormat.JPEG, 100, output)
|
val compressResult = bitmap.compress(Bitmap.CompressFormat.JPEG, 100, output)
|
||||||
if (!compressResult) {
|
if (!compressResult) {
|
||||||
throw RuntimeException("Compression failed!")
|
throw RuntimeException("Compression failed!")
|
||||||
|
@ -89,4 +89,5 @@ object Constants {
|
|||||||
const val DM_THREAD_ACTION_EXTRA_THREAD_ID = "thread_id"
|
const val DM_THREAD_ACTION_EXTRA_THREAD_ID = "thread_id"
|
||||||
const val DM_THREAD_ACTION_EXTRA_THREAD_TITLE = "thread_title"
|
const val DM_THREAD_ACTION_EXTRA_THREAD_TITLE = "thread_title"
|
||||||
const val X_IG_APP_ID = "936619743392459"
|
const val X_IG_APP_ID = "936619743392459"
|
||||||
|
const val EXTRA_INITIAL_URI = "initial_uri"
|
||||||
}
|
}
|
@ -3,7 +3,6 @@ package awais.instagrabber.utils;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
@ -11,19 +10,14 @@ import android.os.FileObserver;
|
|||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.fragment.app.DialogFragment;
|
import androidx.fragment.app.DialogFragment;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
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.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -95,15 +89,15 @@ public final class DirectoryChooser extends DialogFragment {
|
|||||||
if (context == null) context = getContext();
|
if (context == null) context = getContext();
|
||||||
if (context == null) context = getActivity();
|
if (context == null) context = getActivity();
|
||||||
if (context == null) return;
|
if (context == null) return;
|
||||||
if (ContextCompat.checkSelfPermission(context, DownloadUtils.PERMS[0]) != PackageManager.PERMISSION_GRANTED) {
|
// if (ContextCompat.checkSelfPermission(context, DownloadUtils.PERMS[0]) != PackageManager.PERMISSION_GRANTED) {
|
||||||
final String text = "Storage permissions denied!";
|
// final String text = "Storage permissions denied!";
|
||||||
if (container == null) {
|
// if (container == null) {
|
||||||
Toast.makeText(context, text, Toast.LENGTH_LONG).show();
|
// Toast.makeText(context, text, Toast.LENGTH_LONG).show();
|
||||||
} else {
|
// } else {
|
||||||
Snackbar.make(container, text, BaseTransientBottomBar.LENGTH_LONG).show();
|
// Snackbar.make(container, text, BaseTransientBottomBar.LENGTH_LONG).show();
|
||||||
}
|
// }
|
||||||
dismiss();
|
// dismiss();
|
||||||
}
|
// }
|
||||||
final View.OnClickListener clickListener = v -> {
|
final View.OnClickListener clickListener = v -> {
|
||||||
if (v == binding.btnConfirm) {
|
if (v == binding.btnConfirm) {
|
||||||
if (interactionListener != null && isValidFile(selectedDir))
|
if (interactionListener != null && isValidFile(selectedDir))
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
package awais.instagrabber.utils;
|
package awais.instagrabber.utils;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@ -10,8 +8,6 @@ import java.util.HashSet;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import awais.instagrabber.R;
|
|
||||||
|
|
||||||
public class DirectoryUtils {
|
public class DirectoryUtils {
|
||||||
private static final Pattern DIR_SEPORATOR = Pattern.compile("/");
|
private static final Pattern DIR_SEPORATOR = Pattern.compile("/");
|
||||||
|
|
||||||
@ -73,22 +69,22 @@ public class DirectoryUtils {
|
|||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static File getOutputMediaDirectory(final Context context, final String... dirs) {
|
// public static File getOutputMediaDirectory(final Context context, final String... dirs) {
|
||||||
if (context == null) return null;
|
// if (context == null) return null;
|
||||||
final File[] externalMediaDirs = context.getExternalMediaDirs();
|
// final File[] externalMediaDirs = context.getExternalMediaDirs();
|
||||||
if (externalMediaDirs == null || externalMediaDirs.length == 0) return context.getFilesDir();
|
// if (externalMediaDirs == null || externalMediaDirs.length == 0) return context.getFilesDir();
|
||||||
final File externalMediaDir = externalMediaDirs[0];
|
// final File externalMediaDir = externalMediaDirs[0];
|
||||||
File subDir = new File(externalMediaDir, context.getString(R.string.app_name));
|
// File subDir = new File(externalMediaDir, context.getString(R.string.app_name));
|
||||||
if (dirs != null) {
|
// if (dirs != null) {
|
||||||
for (final String dir : dirs) {
|
// for (final String dir : dirs) {
|
||||||
subDir = new File(subDir, dir);
|
// subDir = new File(subDir, dir);
|
||||||
//noinspection ResultOfMethodCallIgnored
|
// //noinspection ResultOfMethodCallIgnored
|
||||||
subDir.mkdirs();
|
// subDir.mkdirs();
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
if (!subDir.exists()) {
|
// if (!subDir.exists()) {
|
||||||
return context.getFilesDir();
|
// return context.getFilesDir();
|
||||||
}
|
// }
|
||||||
return subDir;
|
// return subDir;
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
package awais.instagrabber.utils;
|
package awais.instagrabber.utils;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
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.util.Log;
|
||||||
import android.webkit.MimeTypeMap;
|
import android.webkit.MimeTypeMap;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
@ -11,6 +13,8 @@ import android.widget.Toast;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.core.util.Pair;
|
||||||
|
import androidx.documentfile.provider.DocumentFile;
|
||||||
import androidx.work.Constraints;
|
import androidx.work.Constraints;
|
||||||
import androidx.work.Data;
|
import androidx.work.Data;
|
||||||
import androidx.work.NetworkType;
|
import androidx.work.NetworkType;
|
||||||
@ -21,9 +25,9 @@ import androidx.work.WorkRequest;
|
|||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
import java.io.BufferedWriter;
|
import java.io.BufferedWriter;
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileWriter;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
@ -42,51 +46,159 @@ import awais.instagrabber.repositories.responses.User;
|
|||||||
import awais.instagrabber.repositories.responses.VideoVersion;
|
import awais.instagrabber.repositories.responses.VideoVersion;
|
||||||
import awais.instagrabber.workers.DownloadWorker;
|
import awais.instagrabber.workers.DownloadWorker;
|
||||||
|
|
||||||
import static awais.instagrabber.fragments.settings.PreferenceKeys.FOLDER_PATH;
|
import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_BARINSTA_DIR_URI;
|
||||||
import static awais.instagrabber.fragments.settings.PreferenceKeys.FOLDER_SAVE_TO;
|
|
||||||
|
|
||||||
public final class DownloadUtils {
|
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 WRITE_PERMISSION = Manifest.permission.WRITE_EXTERNAL_STORAGE;
|
||||||
public static final String[] PERMS = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE};
|
public static final String[] PERMS = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE};
|
||||||
|
|
||||||
@NonNull
|
public static void init(@NonNull final Context context) throws ReselectDocumentTreeException {
|
||||||
private static File getDownloadDir() {
|
final String barinstaDirUri = Utils.settingsHelper.getString(PREF_BARINSTA_DIR_URI);
|
||||||
File dir = new File(Environment.getExternalStorageDirectory(), "Download");
|
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<UriPermission> 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)) {
|
public static void destroy() {
|
||||||
final String customPath = Utils.settingsHelper.getString(FOLDER_PATH);
|
root = null;
|
||||||
if (!TextUtils.isEmpty(customPath)) {
|
|
||||||
dir = new File(customPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dir;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static File getDownloadDir(@NonNull final Context context, @Nullable final String username) {
|
public static DocumentFile getDownloadDir(final String... dirs) {
|
||||||
return getDownloadDir(context, username, false);
|
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 subDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static File getDownloadDir(final Context context,
|
public static DocumentFile getDownloadDir() {
|
||||||
@Nullable final String username,
|
// final File parent = new File(Environment.getExternalStorageDirectory(), Environment.DIRECTORY_DOWNLOADS);
|
||||||
final boolean skipCreateDir) {
|
// final File dir = new File(new File(parent, "barinsta"), "downloads");
|
||||||
File dir = getDownloadDir();
|
// if (!dir.exists()) {
|
||||||
|
// final boolean mkdirs = dir.mkdirs();
|
||||||
if (Utils.settingsHelper.getBoolean(PreferenceKeys.DOWNLOAD_USER_FOLDER) && !TextUtils.isEmpty(username)) {
|
// if (!mkdirs) {
|
||||||
final String finaleUsername = username.startsWith("@") ? username.substring(1) : username;
|
// Log.e(TAG, "getDownloadDir: failed to create dir");
|
||||||
dir = new File(dir, finaleUsername);
|
// }
|
||||||
|
// }
|
||||||
|
// 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context != null && !skipCreateDir && !dir.exists() && !dir.mkdirs()) {
|
@Nullable
|
||||||
|
public static DocumentFile getCameraDir() {
|
||||||
|
return getDownloadDir(DIR_CAMERA);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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<String> 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();
|
Toast.makeText(context, R.string.error_creating_folders, Toast.LENGTH_SHORT).show();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return dir;
|
return dir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static List<String> getSubPathForUserFolder(final String username) {
|
||||||
|
final List<String> 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,
|
// public static void dmDownload(@NonNull final Context context,
|
||||||
// @Nullable final String username,
|
// @Nullable final String username,
|
||||||
// final String modelId,
|
// final String modelId,
|
||||||
@ -103,66 +215,78 @@ public final class DownloadUtils {
|
|||||||
// @Nullable final String username,
|
// @Nullable final String username,
|
||||||
// final String modelId,
|
// final String modelId,
|
||||||
// final String url) {
|
// final String url) {
|
||||||
// final File dir = getDownloadDir(context, username);
|
// final DocumentFile dir = getDownloadDir(context, username);
|
||||||
// if (dir.exists() || dir.mkdirs()) {
|
// if (dir != null && dir.exists()) {
|
||||||
// download(context,
|
// download(context, url, getDownloadSavePaths(dir, modelId, url));
|
||||||
// url,
|
|
||||||
// getDownloadSaveFile(dir, modelId, url).getAbsolutePath());
|
|
||||||
// return;
|
// return;
|
||||||
// }
|
// }
|
||||||
// Toast.makeText(context, R.string.error_creating_folders, Toast.LENGTH_SHORT).show();
|
// Toast.makeText(context, R.string.error_creating_folders, Toast.LENGTH_SHORT).show();
|
||||||
// }
|
// }
|
||||||
|
|
||||||
@NonNull
|
private static Pair<List<String>, String> getDownloadSavePaths(final List<String> paths,
|
||||||
private static File getDownloadSaveFile(final File finalDir,
|
|
||||||
final String postId,
|
final String postId,
|
||||||
final String displayUrl) {
|
final String displayUrl) {
|
||||||
return getDownloadSaveFile(finalDir, postId, "", displayUrl, "");
|
return getDownloadSavePaths(paths, postId, "", displayUrl, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
private static Pair<List<String>, String> getDownloadSavePaths(final List<String> paths,
|
||||||
private static File getDownloadSaveFile(final File finalDir,
|
|
||||||
final String postId,
|
final String postId,
|
||||||
final String displayUrl,
|
final String displayUrl,
|
||||||
final String username) {
|
final String username) {
|
||||||
return getDownloadSaveFile(finalDir, postId, "", displayUrl, username);
|
return getDownloadSavePaths(paths, postId, "", displayUrl, username);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static File getDownloadChildSaveFile(final File downloadDir,
|
private static Pair<List<String>, String> getDownloadChildSavePaths(final List<String> paths,
|
||||||
final String postId,
|
final String postId,
|
||||||
final int childPosition,
|
final int childPosition,
|
||||||
final String url,
|
final String url,
|
||||||
final String username) {
|
final String username) {
|
||||||
final String sliderPostfix = "_slide_" + childPosition;
|
final String sliderPostfix = "_slide_" + childPosition;
|
||||||
return getDownloadSaveFile(downloadDir, postId, sliderPostfix, url, username);
|
return getDownloadSavePaths(paths, postId, sliderPostfix, url, username);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
private static Pair<List<String>, String> getDownloadSavePaths(final List<String> paths,
|
||||||
private static File getDownloadSaveFile(final File finalDir,
|
|
||||||
final String postId,
|
final String postId,
|
||||||
final String sliderPostfix,
|
final String sliderPostfix,
|
||||||
final String displayUrl,
|
final String displayUrl,
|
||||||
final String username) {
|
final String username) {
|
||||||
|
if (paths == null) return null;
|
||||||
|
final String extension = getFileExtensionFromUrl(displayUrl);
|
||||||
final String usernamePrepend = TextUtils.isEmpty(username) ? "" : (username + "_");
|
final String usernamePrepend = TextUtils.isEmpty(username) ? "" : (username + "_");
|
||||||
final String fileName = usernamePrepend + postId + sliderPostfix + getFileExtensionFromUrl(displayUrl);
|
final String fileName = usernamePrepend + postId + sliderPostfix + extension;
|
||||||
return new File(finalDir, fileName);
|
// 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 DocumentFile getTempFile() {
|
||||||
public static File getTempFile() {
|
// return getTempFile(null, null);
|
||||||
return getTempFile(null, null);
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
public static File getTempFile(final String fileName, final String extension) {
|
public static DocumentFile getTempFile(final String fileName, final String extension) {
|
||||||
final File dir = getDownloadDir();
|
final DocumentFile dir = getTempDir();
|
||||||
String name = fileName;
|
String name = fileName;
|
||||||
if (TextUtils.isEmpty(name)) {
|
if (TextUtils.isEmpty(name)) {
|
||||||
name = UUID.randomUUID().toString();
|
name = UUID.randomUUID().toString();
|
||||||
}
|
}
|
||||||
|
String mimeType = "application/octet-stream";
|
||||||
if (!TextUtils.isEmpty(extension)) {
|
if (!TextUtils.isEmpty(extension)) {
|
||||||
name += "." + extension;
|
name += "." + extension;
|
||||||
|
final String mimeType1 = Utils.mimeTypeMap.getMimeTypeFromExtension(extension);
|
||||||
|
if (mimeType1 != null) {
|
||||||
|
mimeType = mimeType1;
|
||||||
}
|
}
|
||||||
return new File(dir, name);
|
}
|
||||||
|
DocumentFile file = dir.findFile(name);
|
||||||
|
if (file == null) {
|
||||||
|
file = dir.createFile(mimeType, name);
|
||||||
|
}
|
||||||
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -212,14 +336,20 @@ public final class DownloadUtils {
|
|||||||
if (user != null) {
|
if (user != null) {
|
||||||
username = user.getUsername();
|
username = user.getUsername();
|
||||||
}
|
}
|
||||||
final File downloadDir = getDownloadDir(null, "@" + username, true);
|
final List<String> userFolderPaths = getSubPathForUserFolder(username);
|
||||||
switch (media.getMediaType()) {
|
switch (media.getMediaType()) {
|
||||||
case MEDIA_TYPE_IMAGE:
|
case MEDIA_TYPE_IMAGE:
|
||||||
case MEDIA_TYPE_VIDEO: {
|
case MEDIA_TYPE_VIDEO: {
|
||||||
final String url = ResponseBodyUtils.getImageUrl(media);
|
final String url = ResponseBodyUtils.getImageUrl(media);
|
||||||
final File file = getDownloadSaveFile(downloadDir, media.getCode(), url, "");
|
final Pair<List<String>, String> file = getDownloadSavePaths(new ArrayList<>(userFolderPaths), media.getCode(), url, "");
|
||||||
final File usernamePrependedFile = getDownloadSaveFile(downloadDir, media.getCode(), url, username);
|
final boolean fileExists = file.first != null && checkPathExists(file.first);
|
||||||
checkList.add(file.exists() || usernamePrependedFile.exists());
|
boolean usernameFileExists = false;
|
||||||
|
if (!fileExists) {
|
||||||
|
final Pair<List<String>, String> usernameFile = getDownloadSavePaths(
|
||||||
|
new ArrayList<>(userFolderPaths), media.getCode(), url, username);
|
||||||
|
usernameFileExists = usernameFile.first != null && checkPathExists(usernameFile.first);
|
||||||
|
}
|
||||||
|
checkList.add(fileExists || usernameFileExists);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MEDIA_TYPE_SLIDER:
|
case MEDIA_TYPE_SLIDER:
|
||||||
@ -228,9 +358,16 @@ public final class DownloadUtils {
|
|||||||
final Media child = sliderItems.get(i);
|
final Media child = sliderItems.get(i);
|
||||||
if (child == null) continue;
|
if (child == null) continue;
|
||||||
final String url = ResponseBodyUtils.getImageUrl(child);
|
final String url = ResponseBodyUtils.getImageUrl(child);
|
||||||
final File file = getDownloadChildSaveFile(downloadDir, media.getCode(), i + 1, url, "");
|
final Pair<List<String>, String> file = getDownloadChildSavePaths(
|
||||||
final File usernamePrependedFile = getDownloadChildSaveFile(downloadDir, media.getCode(), i + 1, url, username);
|
new ArrayList<>(userFolderPaths), media.getCode(), i + 1, url, "");
|
||||||
checkList.add(file.exists() || usernamePrependedFile.exists());
|
final boolean fileExists = file.first != null && checkPathExists(file.first);
|
||||||
|
boolean usernameFileExists = false;
|
||||||
|
if (!fileExists) {
|
||||||
|
final Pair<List<String>, String> usernameFile = getDownloadChildSavePaths(
|
||||||
|
new ArrayList<>(userFolderPaths), media.getCode(), i + 1, url, username);
|
||||||
|
usernameFileExists = usernameFile.first != null && checkPathExists(usernameFile.first);
|
||||||
|
}
|
||||||
|
checkList.add(fileExists || usernameFileExists);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -238,6 +375,18 @@ public final class DownloadUtils {
|
|||||||
return checkList;
|
return checkList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean checkPathExists(@NonNull final List<String> 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,
|
public static void showDownloadDialog(@NonNull Context context,
|
||||||
@NonNull final Media feedModel,
|
@NonNull final Media feedModel,
|
||||||
final int childPosition) {
|
final int childPosition) {
|
||||||
@ -272,17 +421,25 @@ public final class DownloadUtils {
|
|||||||
|
|
||||||
public static void download(@NonNull final Context context,
|
public static void download(@NonNull final Context context,
|
||||||
@NonNull final StoryModel storyModel) {
|
@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
|
final String url = storyModel.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO
|
||||||
? storyModel.getVideoUrl()
|
? storyModel.getVideoUrl()
|
||||||
: storyModel.getStoryUrl();
|
: storyModel.getStoryUrl();
|
||||||
|
final String extension = DownloadUtils.getFileExtensionFromUrl(url);
|
||||||
final String baseFileName = storyModel.getStoryMediaId() + "_"
|
final String baseFileName = storyModel.getStoryMediaId() + "_"
|
||||||
+ storyModel.getTimestamp() + DownloadUtils.getFileExtensionFromUrl(url);
|
+ storyModel.getTimestamp() + extension;
|
||||||
final String usernamePrepend = Utils.settingsHelper.getBoolean(PreferenceKeys.DOWNLOAD_PREPEND_USER_NAME)
|
final String usernamePrepend = Utils.settingsHelper.getBoolean(PreferenceKeys.DOWNLOAD_PREPEND_USER_NAME)
|
||||||
&& storyModel.getUsername() != null ? storyModel.getUsername() + "_" : "";
|
&& storyModel.getUsername() != null ? storyModel.getUsername() + "_" : "";
|
||||||
final File saveFile = new File(downloadDir,
|
final String fileName = usernamePrepend + baseFileName;
|
||||||
usernamePrepend + baseFileName);
|
DocumentFile saveFile = downloadDir.findFile(fileName);
|
||||||
download(context, url, saveFile.getAbsolutePath());
|
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,
|
public static void download(@NonNull final Context context,
|
||||||
@ -304,11 +461,12 @@ public final class DownloadUtils {
|
|||||||
private static void download(@NonNull final Context context,
|
private static void download(@NonNull final Context context,
|
||||||
@NonNull final List<Media> feedModels,
|
@NonNull final List<Media> feedModels,
|
||||||
final int childPositionIfSingle) {
|
final int childPositionIfSingle) {
|
||||||
final Map<String, String> map = new HashMap<>();
|
final Map<String, DocumentFile> map = new HashMap<>();
|
||||||
for (final Media media : feedModels) {
|
for (final Media media : feedModels) {
|
||||||
final User mediaUser = media.getUser();
|
final User mediaUser = media.getUser();
|
||||||
final File downloadDir = getDownloadDir(context, mediaUser == null ? "" : mediaUser.getUsername());
|
final String username = mediaUser == null ? "" : mediaUser.getUsername();
|
||||||
if (downloadDir == null) return;
|
final List<String> userFolderPaths = getSubPathForUserFolder(username);
|
||||||
|
// final DocumentFile downloadDir = getDownloadDir(context, mediaUser == null ? "" : mediaUser.getUsername());
|
||||||
switch (media.getMediaType()) {
|
switch (media.getMediaType()) {
|
||||||
case MEDIA_TYPE_IMAGE:
|
case MEDIA_TYPE_IMAGE:
|
||||||
case MEDIA_TYPE_VIDEO: {
|
case MEDIA_TYPE_VIDEO: {
|
||||||
@ -323,8 +481,10 @@ public final class DownloadUtils {
|
|||||||
fileName = mediaUser.getUsername() + "_" + fileName;
|
fileName = mediaUser.getUsername() + "_" + fileName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final File file = getDownloadSaveFile(downloadDir, fileName, url);
|
final Pair<List<String>, String> pair = getDownloadSavePaths(userFolderPaths, fileName, url);
|
||||||
map.put(url, file.getAbsolutePath());
|
final DocumentFile file = createFile(pair);
|
||||||
|
if (file == null) continue;
|
||||||
|
map.put(url, file);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MEDIA_TYPE_VOICE: {
|
case MEDIA_TYPE_VOICE: {
|
||||||
@ -333,29 +493,54 @@ public final class DownloadUtils {
|
|||||||
if (mediaUser != null) {
|
if (mediaUser != null) {
|
||||||
fileName = mediaUser.getUsername() + "_" + fileName;
|
fileName = mediaUser.getUsername() + "_" + fileName;
|
||||||
}
|
}
|
||||||
final File file = getDownloadSaveFile(downloadDir, fileName, url);
|
final Pair<List<String>, String> pair = getDownloadSavePaths(userFolderPaths, fileName, url);
|
||||||
map.put(url, file.getAbsolutePath());
|
final DocumentFile file = createFile(pair);
|
||||||
|
if (file == null) continue;
|
||||||
|
map.put(url, file);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MEDIA_TYPE_SLIDER:
|
case MEDIA_TYPE_SLIDER:
|
||||||
final List<Media> sliderItems = media.getCarouselMedia();
|
final List<Media> sliderItems = media.getCarouselMedia();
|
||||||
for (int i = 0; i < sliderItems.size(); i++) {
|
for (int i = 0; i < sliderItems.size(); i++) {
|
||||||
if (childPositionIfSingle >= 0 && feedModels.size() == 1 && i != childPositionIfSingle) {
|
if (childPositionIfSingle >= 0 && feedModels.size() == 1 && i != childPositionIfSingle) continue;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
final Media child = sliderItems.get(i);
|
final Media child = sliderItems.get(i);
|
||||||
final String url = getUrlOfType(child);
|
final String url = getUrlOfType(child);
|
||||||
final String usernamePrepend = Utils.settingsHelper.getBoolean(PreferenceKeys.DOWNLOAD_PREPEND_USER_NAME) && mediaUser != null ? mediaUser.getUsername() : "";
|
final String usernamePrepend = Utils.settingsHelper.getBoolean(PreferenceKeys.DOWNLOAD_PREPEND_USER_NAME) && mediaUser != null
|
||||||
final File file = getDownloadChildSaveFile(downloadDir, media.getCode(), i + 1, url, usernamePrepend);
|
? mediaUser.getUsername()
|
||||||
map.put(url, file.getAbsolutePath());
|
: "";
|
||||||
|
final Pair<List<String>, 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;
|
break;
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (map.isEmpty()) return;
|
||||||
download(context, map);
|
download(context, map);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static DocumentFile createFile(@NonNull final Pair<List<String>, String> pair) {
|
||||||
|
if (root == null) return null;
|
||||||
|
if (pair.first == null || pair.second == null) return null;
|
||||||
|
DocumentFile dir = root;
|
||||||
|
final List<String> 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
|
@Nullable
|
||||||
private static String getUrlOfType(@NonNull final Media media) {
|
private static String getUrlOfType(@NonNull final Media media) {
|
||||||
switch (media.getMediaType()) {
|
switch (media.getMediaType()) {
|
||||||
@ -387,12 +572,13 @@ public final class DownloadUtils {
|
|||||||
|
|
||||||
public static void download(final Context context,
|
public static void download(final Context context,
|
||||||
final String url,
|
final String url,
|
||||||
final String filePath) {
|
final DocumentFile filePath) {
|
||||||
if (context == null || url == null || filePath == null) return;
|
if (context == null || url == null || filePath == null) return;
|
||||||
download(context, Collections.singletonMap(url, filePath));
|
download(context, Collections.singletonMap(url, filePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void download(final Context context, final Map<String, String> urlFilePathMap) {
|
private static void download(final Context context, final Map<String, DocumentFile> urlFilePathMap) {
|
||||||
|
if (context == null) return;
|
||||||
final Constraints constraints = new Constraints.Builder()
|
final Constraints constraints = new Constraints.Builder()
|
||||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||||
.build();
|
.build();
|
||||||
@ -400,19 +586,25 @@ public final class DownloadUtils {
|
|||||||
.setUrlToFilePathMap(urlFilePathMap)
|
.setUrlToFilePathMap(urlFilePathMap)
|
||||||
.build();
|
.build();
|
||||||
final String requestJson = new Gson().toJson(request);
|
final String requestJson = new Gson().toJson(request);
|
||||||
final File tempFile = getTempFile();
|
final DocumentFile tempFile = getTempFile(null, "json");
|
||||||
try (BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile))) {
|
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);
|
writer.write(requestJson);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(TAG, "download: Error writing request to file", e);
|
Log.e(TAG, "download: Error writing request to file", e);
|
||||||
//noinspection ResultOfMethodCallIgnored
|
|
||||||
tempFile.delete();
|
tempFile.delete();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final WorkRequest downloadWorkRequest = new OneTimeWorkRequest.Builder(DownloadWorker.class)
|
final WorkRequest downloadWorkRequest = new OneTimeWorkRequest.Builder(DownloadWorker.class)
|
||||||
.setInputData(
|
.setInputData(
|
||||||
new Data.Builder()
|
new Data.Builder()
|
||||||
.putString(DownloadWorker.KEY_DOWNLOAD_REQUEST_JSON, tempFile.getAbsolutePath())
|
.putString(DownloadWorker.KEY_DOWNLOAD_REQUEST_JSON, tempFile.getUri().toString())
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
.setConstraints(constraints)
|
.setConstraints(constraints)
|
||||||
@ -421,4 +613,30 @@ public final class DownloadUtils {
|
|||||||
WorkManager.getInstance(context)
|
WorkManager.getInstance(context)
|
||||||
.enqueue(downloadWorkRequest);
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package awais.instagrabber.utils;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
import android.net.Uri;
|
||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
@ -20,9 +21,8 @@ import org.json.JSONArray;
|
|||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.InputStream;
|
||||||
import java.io.FileInputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
@ -55,14 +55,15 @@ public final class ExportImportUtils {
|
|||||||
|
|
||||||
public static void importData(@NonNull final Context context,
|
public static void importData(@NonNull final Context context,
|
||||||
@ExportImportFlags final int flags,
|
@ExportImportFlags final int flags,
|
||||||
@NonNull final File file,
|
@NonNull final Uri uri,
|
||||||
final String password,
|
final String password,
|
||||||
final FetchListener<Boolean> fetchListener) throws IncorrectPasswordException {
|
final FetchListener<Boolean> fetchListener) throws IncorrectPasswordException {
|
||||||
try (final FileInputStream fis = new FileInputStream(file)) {
|
try (final InputStream stream = context.getContentResolver().openInputStream(uri)) {
|
||||||
final int configType = fis.read();
|
if (stream == null) return;
|
||||||
|
final int configType = stream.read();
|
||||||
final StringBuilder builder = new StringBuilder();
|
final StringBuilder builder = new StringBuilder();
|
||||||
int c;
|
int c;
|
||||||
while ((c = fis.read()) != -1) {
|
while ((c = stream.read()) != -1) {
|
||||||
builder.append((char) c);
|
builder.append((char) c);
|
||||||
}
|
}
|
||||||
if (configType == 'A') {
|
if (configType == 'A') {
|
||||||
@ -223,9 +224,11 @@ public final class ExportImportUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isEncrypted(final File file) {
|
public static boolean isEncrypted(@NonNull final Context context,
|
||||||
try (final FileInputStream fis = new FileInputStream(file)) {
|
@NonNull final Uri uri) {
|
||||||
final int configType = fis.read();
|
try (final InputStream stream = context.getContentResolver().openInputStream(uri)) {
|
||||||
|
if (stream == null) return false;
|
||||||
|
final int configType = stream.read();
|
||||||
if (configType == 'A') {
|
if (configType == 'A') {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -237,7 +240,7 @@ public final class ExportImportUtils {
|
|||||||
|
|
||||||
public static void exportData(@NonNull final Context context,
|
public static void exportData(@NonNull final Context context,
|
||||||
@ExportImportFlags final int flags,
|
@ExportImportFlags final int flags,
|
||||||
@NonNull final File filePath,
|
@NonNull final Uri uri,
|
||||||
final String password,
|
final String password,
|
||||||
final FetchListener<Boolean> fetchListener) {
|
final FetchListener<Boolean> fetchListener) {
|
||||||
getExportString(flags, context, exportString -> {
|
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);
|
exportBytes = Base64.encode(exportString.getBytes(), Base64.DEFAULT | Base64.NO_WRAP | Base64.NO_PADDING);
|
||||||
}
|
}
|
||||||
if (exportBytes != null && exportBytes.length > 1) {
|
if (exportBytes != null && exportBytes.length > 1) {
|
||||||
try (final FileOutputStream fos = new FileOutputStream(filePath)) {
|
try (final OutputStream stream = context.getContentResolver().openOutputStream(uri)) {
|
||||||
fos.write(isPass ? 'A' : 'Z');
|
if (stream == null) return;
|
||||||
fos.write(exportBytes);
|
stream.write(isPass ? 'A' : 'Z');
|
||||||
|
stream.write(exportBytes);
|
||||||
if (fetchListener != null) fetchListener.onResult(true);
|
if (fetchListener != null) fetchListener.onResult(true);
|
||||||
} catch (final Exception e) {
|
} catch (Exception e) {
|
||||||
if (fetchListener != null) fetchListener.onResult(false);
|
if (fetchListener != null) fetchListener.onResult(false);
|
||||||
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
|
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
|
||||||
}
|
}
|
||||||
} else if (fetchListener != null) fetchListener.onResult(false);
|
return;
|
||||||
|
}
|
||||||
|
if (fetchListener != null) {
|
||||||
|
fetchListener.onResult(false);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package awais.instagrabber.utils
|
|||||||
import android.content.ContentResolver
|
import android.content.ContentResolver
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import awais.instagrabber.models.UploadVideoOptions
|
import awais.instagrabber.models.UploadVideoOptions
|
||||||
import awais.instagrabber.webservices.interceptors.AddCookiesInterceptor
|
import awais.instagrabber.webservices.interceptors.AddCookiesInterceptor
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@ -12,8 +13,6 @@ import okio.BufferedSink
|
|||||||
import okio.Okio
|
import okio.Okio
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import ru.gildor.coroutines.okhttp.await
|
import ru.gildor.coroutines.okhttp.await
|
||||||
import java.io.File
|
|
||||||
import java.io.FileInputStream
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
@ -29,20 +28,23 @@ object MediaUploader {
|
|||||||
): MediaUploadResponse = withContext(Dispatchers.IO) {
|
): MediaUploadResponse = withContext(Dispatchers.IO) {
|
||||||
val bitmapResult = BitmapUtils.loadBitmap(contentResolver, uri, 1000f, false)
|
val bitmapResult = BitmapUtils.loadBitmap(contentResolver, uri, 1000f, false)
|
||||||
val bitmap = bitmapResult?.bitmap ?: throw IOException("bitmap is null")
|
val bitmap = bitmapResult?.bitmap ?: throw IOException("bitmap is null")
|
||||||
uploadPhoto(bitmap)
|
uploadPhoto(contentResolver, bitmap)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
private suspend fun uploadPhoto(
|
private suspend fun uploadPhoto(
|
||||||
|
contentResolver: ContentResolver,
|
||||||
bitmap: Bitmap,
|
bitmap: Bitmap,
|
||||||
): MediaUploadResponse = withContext(Dispatchers.IO) {
|
): 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 byteLength: Long = file.length()
|
||||||
val options = createUploadPhotoOptions(byteLength)
|
val options = createUploadPhotoOptions(byteLength)
|
||||||
val headers = getUploadPhotoHeaders(options)
|
val headers = getUploadPhotoHeaders(options)
|
||||||
val url = HOST + "/rupload_igphoto/" + options.name + "/"
|
val url = HOST + "/rupload_igphoto/" + options.name + "/"
|
||||||
try {
|
try {
|
||||||
FileInputStream(file).use { input -> upload(input, url, headers) }
|
contentResolver.openInputStream(file.uri).use { input ->
|
||||||
|
upload(input!!, url, headers)
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
file.delete()
|
file.delete()
|
||||||
}
|
}
|
||||||
|
@ -2,13 +2,17 @@ package awais.instagrabber.utils;
|
|||||||
|
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
import android.media.MediaMetadataRetriever;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.ParcelFileDescriptor;
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.io.FileDescriptor;
|
||||||
|
|
||||||
public final class MediaUtils {
|
public final class MediaUtils {
|
||||||
private static final String TAG = MediaUtils.class.getSimpleName();
|
private static final String TAG = MediaUtils.class.getSimpleName();
|
||||||
private static final String[] PROJECTION_VIDEO = {
|
private static final String[] PROJECTION_VIDEO = {
|
||||||
@ -28,9 +32,7 @@ public final class MediaUtils {
|
|||||||
AppExecutors.INSTANCE.getTasksThread().submit(() -> {
|
AppExecutors.INSTANCE.getTasksThread().submit(() -> {
|
||||||
try (Cursor cursor = MediaStore.Video.query(contentResolver, uri, PROJECTION_VIDEO)) {
|
try (Cursor cursor = MediaStore.Video.query(contentResolver, uri, PROJECTION_VIDEO)) {
|
||||||
if (cursor == null) {
|
if (cursor == null) {
|
||||||
if (listener != null) {
|
|
||||||
listener.onLoad(null);
|
listener.onLoad(null);
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int durationColumn = cursor.getColumnIndex(MediaStore.Video.Media.DURATION);
|
int durationColumn = cursor.getColumnIndex(MediaStore.Video.Media.DURATION);
|
||||||
@ -38,7 +40,6 @@ public final class MediaUtils {
|
|||||||
int heightColumn = cursor.getColumnIndex(MediaStore.Video.Media.HEIGHT);
|
int heightColumn = cursor.getColumnIndex(MediaStore.Video.Media.HEIGHT);
|
||||||
int sizeColumn = cursor.getColumnIndex(MediaStore.Video.Media.SIZE);
|
int sizeColumn = cursor.getColumnIndex(MediaStore.Video.Media.SIZE);
|
||||||
if (cursor.moveToNext()) {
|
if (cursor.moveToNext()) {
|
||||||
if (listener != null) {
|
|
||||||
listener.onLoad(new VideoInfo(
|
listener.onLoad(new VideoInfo(
|
||||||
cursor.getLong(durationColumn),
|
cursor.getLong(durationColumn),
|
||||||
cursor.getInt(widthColumn),
|
cursor.getInt(widthColumn),
|
||||||
@ -46,17 +47,10 @@ public final class MediaUtils {
|
|||||||
cursor.getLong(sizeColumn)
|
cursor.getLong(sizeColumn)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "getVideoInfo: ", e);
|
Log.e(TAG, "getVideoInfo: ", e);
|
||||||
if (listener != null) {
|
|
||||||
listener.onFailure(e);
|
listener.onFailure(e);
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (listener != null) {
|
|
||||||
listener.onLoad(null);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,35 +58,26 @@ public final class MediaUtils {
|
|||||||
@NonNull final Uri uri,
|
@NonNull final Uri uri,
|
||||||
@NonNull final OnInfoLoadListener<VideoInfo> listener) {
|
@NonNull final OnInfoLoadListener<VideoInfo> listener) {
|
||||||
AppExecutors.INSTANCE.getTasksThread().submit(() -> {
|
AppExecutors.INSTANCE.getTasksThread().submit(() -> {
|
||||||
try (Cursor cursor = MediaStore.Video.query(contentResolver, uri, PROJECTION_AUDIO)) {
|
try (ParcelFileDescriptor parcelFileDescriptor = contentResolver.openFileDescriptor(uri, "r")) {
|
||||||
if (cursor == null) {
|
if (parcelFileDescriptor == null) {
|
||||||
if (listener != null) {
|
|
||||||
listener.onLoad(null);
|
listener.onLoad(null);
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int durationColumn = cursor.getColumnIndex(MediaStore.Audio.Media.DURATION);
|
final FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
|
||||||
int sizeColumn = cursor.getColumnIndex(MediaStore.Audio.Media.SIZE);
|
final MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
|
||||||
if (cursor.moveToNext()) {
|
mediaMetadataRetriever.setDataSource(fileDescriptor);
|
||||||
if (listener != null) {
|
String duration = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
|
||||||
|
if (TextUtils.isEmpty(duration)) duration = "0";
|
||||||
listener.onLoad(new VideoInfo(
|
listener.onLoad(new VideoInfo(
|
||||||
cursor.getLong(durationColumn),
|
Long.parseLong(duration),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
cursor.getLong(sizeColumn)
|
0
|
||||||
));
|
));
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "getVoiceInfo: ", e);
|
Log.e(TAG, "getVoiceInfo: ", e);
|
||||||
if (listener != null) {
|
|
||||||
listener.onFailure(e);
|
listener.onFailure(e);
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (listener != null) {
|
|
||||||
listener.onLoad(null);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,17 +13,15 @@ import androidx.fragment.app.Fragment;
|
|||||||
import static android.Manifest.permission.CAMERA;
|
import static android.Manifest.permission.CAMERA;
|
||||||
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
|
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
|
||||||
import static android.Manifest.permission.RECORD_AUDIO;
|
import static android.Manifest.permission.RECORD_AUDIO;
|
||||||
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
|
|
||||||
import static androidx.core.content.PermissionChecker.checkSelfPermission;
|
import static androidx.core.content.PermissionChecker.checkSelfPermission;
|
||||||
|
|
||||||
public class PermissionUtils {
|
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[] ATTACH_MEDIA_PERMS = new String[]{READ_EXTERNAL_STORAGE};
|
||||||
public static final String[] CAMERA_PERMS = new String[]{CAMERA};
|
public static final String[] CAMERA_PERMS = new String[]{CAMERA};
|
||||||
|
|
||||||
public static boolean hasAudioRecordPerms(@NonNull final Context context) {
|
public static boolean hasAudioRecordPerms(@NonNull final Context context) {
|
||||||
return checkSelfPermission(context, WRITE_EXTERNAL_STORAGE) == PermissionChecker.PERMISSION_GRANTED
|
return checkSelfPermission(context, RECORD_AUDIO) == PermissionChecker.PERMISSION_GRANTED;
|
||||||
&& checkSelfPermission(context, RECORD_AUDIO) == PermissionChecker.PERMISSION_GRANTED;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void requestAudioRecordPerms(final Fragment fragment, final int requestCode) {
|
public static void requestAudioRecordPerms(final Fragment fragment, final int requestCode) {
|
||||||
|
@ -11,6 +11,7 @@ import androidx.appcompat.app.AppCompatDelegate;
|
|||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
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;
|
||||||
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_NUMBER;
|
||||||
import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH_FREQ_UNIT;
|
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,
|
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_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,
|
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 {}
|
public @interface StringSettings {}
|
||||||
|
|
||||||
@StringDef({DOWNLOAD_USER_FOLDER, DOWNLOAD_PREPEND_USER_NAME, FOLDER_SAVE_TO, AUTOPLAY_VIDEOS, MUTED_VIDEOS,
|
@StringDef({DOWNLOAD_USER_FOLDER, DOWNLOAD_PREPEND_USER_NAME, FOLDER_SAVE_TO, AUTOPLAY_VIDEOS, MUTED_VIDEOS,
|
||||||
|
@ -13,11 +13,12 @@ import android.graphics.Color;
|
|||||||
import android.graphics.Point;
|
import android.graphics.Point;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.media.MediaScannerConnection;
|
|
||||||
import android.media.MediaScannerConnection.OnScanCompletedListener;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.os.Environment;
|
||||||
|
import android.os.storage.StorageManager;
|
||||||
import android.provider.Browser;
|
import android.provider.Browser;
|
||||||
|
import android.provider.DocumentsContract;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
@ -37,6 +38,7 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.documentfile.provider.DocumentFile;
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MediatorLiveData;
|
import androidx.lifecycle.MediatorLiveData;
|
||||||
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat;
|
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.android.exoplayer2.upstream.cache.SimpleCache;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.Ordering;
|
import com.google.common.collect.Ordering;
|
||||||
import com.google.common.io.Files;
|
|
||||||
|
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -66,6 +69,8 @@ import awais.instagrabber.models.PostsLayoutPreferences;
|
|||||||
import awais.instagrabber.models.Tab;
|
import awais.instagrabber.models.Tab;
|
||||||
import awais.instagrabber.models.enums.FavoriteType;
|
import awais.instagrabber.models.enums.FavoriteType;
|
||||||
|
|
||||||
|
import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_BARINSTA_DIR_URI;
|
||||||
|
|
||||||
public final class Utils {
|
public final class Utils {
|
||||||
private static final String TAG = "Utils";
|
private static final String TAG = "Utils";
|
||||||
private static final int VIDEO_CACHE_MAX_BYTES = 10 * 1024 * 1024;
|
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 cacheDir;
|
||||||
public static String tabOrderString;
|
public static String tabOrderString;
|
||||||
private static int defaultStatusBarColor;
|
private static int defaultStatusBarColor;
|
||||||
|
private static Object[] volumes;
|
||||||
|
|
||||||
public static int convertDpToPx(final float dp) {
|
public static int convertDpToPx(final float dp) {
|
||||||
return Math.round((dp * displayMetrics.densityDpi) / DisplayMetrics.DENSITY_DEFAULT);
|
return Math.round((dp * displayMetrics.densityDpi) / DisplayMetrics.DENSITY_DEFAULT);
|
||||||
@ -346,18 +352,18 @@ public final class Utils {
|
|||||||
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
|
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void mediaScanFile(@NonNull final Context context,
|
// public static void mediaScanFile(@NonNull final Context context,
|
||||||
@NonNull File file,
|
// @NonNull File file,
|
||||||
@NonNull final OnScanCompletedListener callback) {
|
// @NonNull final OnScanCompletedListener callback) {
|
||||||
//noinspection UnstableApiUsage
|
// //noinspection UnstableApiUsage
|
||||||
final String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(Files.getFileExtension(file.getName()));
|
// final String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(Files.getFileExtension(file.getName()));
|
||||||
MediaScannerConnection.scanFile(
|
// MediaScannerConnection.scanFile(
|
||||||
context,
|
// context,
|
||||||
new String[]{file.getAbsolutePath()},
|
// new String[]{file.getAbsolutePath()},
|
||||||
new String[]{mimeType},
|
// new String[]{mimeType},
|
||||||
callback
|
// callback
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
public static void showKeyboard(@NonNull final View view) {
|
public static void showKeyboard(@NonNull final View view) {
|
||||||
final Context context = view.getContext();
|
final Context context = view.getContext();
|
||||||
@ -524,6 +530,73 @@ public final class Utils {
|
|||||||
return tabOrderString.contains(navRootString);
|
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
|
@NonNull
|
||||||
public static Point getNavigationBarSize(@NonNull Context context) {
|
public static Point getNavigationBarSize(@NonNull Context context) {
|
||||||
Point appUsableSize = getAppUsableScreenSize(context);
|
Point appUsableSize = getAppUsableScreenSize(context);
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
package awais.instagrabber.utils;
|
package awais.instagrabber.utils;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.content.ContentResolver;
|
||||||
import android.media.MediaRecorder;
|
import android.media.MediaRecorder;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
|
import android.os.ParcelFileDescriptor;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.webkit.MimeTypeMap;
|
import android.webkit.MimeTypeMap;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.documentfile.provider.DocumentFile;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.time.LocalDateTime;
|
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 static final DateTimeFormatter SIMPLE_DATE_FORMAT = DateTimeFormatter.ofPattern(FILE_FORMAT, Locale.US);
|
||||||
|
|
||||||
private final List<Float> waveform = new ArrayList<>();
|
private final List<Float> waveform = new ArrayList<>();
|
||||||
private final File recordingsDir;
|
private final DocumentFile recordingsDir;
|
||||||
private final VoiceRecorderCallback callback;
|
private final VoiceRecorderCallback callback;
|
||||||
|
|
||||||
private MediaRecorder recorder;
|
private MediaRecorder recorder;
|
||||||
private File audioTempFile;
|
private DocumentFile audioTempFile;
|
||||||
private MaxAmpHandler maxAmpHandler;
|
private MaxAmpHandler maxAmpHandler;
|
||||||
private boolean stopped;
|
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.recordingsDir = recordingsDir;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startRecording() {
|
public void startRecording(final ContentResolver contentResolver) {
|
||||||
stopped = false;
|
stopped = false;
|
||||||
|
ParcelFileDescriptor parcelFileDescriptor = null;
|
||||||
try {
|
try {
|
||||||
recorder = new MediaRecorder();
|
recorder = new MediaRecorder();
|
||||||
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
|
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
|
||||||
recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
|
recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
|
||||||
deleteTempAudioFile();
|
deleteTempAudioFile();
|
||||||
audioTempFile = getAudioRecordFile();
|
audioTempFile = getAudioRecordFile();
|
||||||
recorder.setOutputFile(audioTempFile.getAbsolutePath());
|
parcelFileDescriptor = contentResolver.openFileDescriptor(audioTempFile.getUri(), "rwt");
|
||||||
|
recorder.setOutputFile(parcelFileDescriptor.getFileDescriptor());
|
||||||
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.HE_AAC);
|
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.HE_AAC);
|
||||||
recorder.setAudioEncodingBitRate(AUDIO_BIT_RATE);
|
recorder.setAudioEncodingBitRate(AUDIO_BIT_RATE);
|
||||||
recorder.setAudioSamplingRate(AUDIO_SAMPLE_RATE);
|
recorder.setAudioSamplingRate(AUDIO_SAMPLE_RATE);
|
||||||
@ -63,6 +70,12 @@ public class VoiceRecorder {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "Audio recording failed", e);
|
Log.e(TAG, "Audio recording failed", e);
|
||||||
deleteTempAudioFile();
|
deleteTempAudioFile();
|
||||||
|
} finally {
|
||||||
|
if (parcelFileDescriptor != null) {
|
||||||
|
try {
|
||||||
|
parcelFileDescriptor.close();
|
||||||
|
} catch (IOException ignored) {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,9 +153,13 @@ public class VoiceRecorder {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private File getAudioRecordFile() {
|
private DocumentFile getAudioRecordFile() {
|
||||||
final String name = String.format("%s-%s.%s", FILE_PREFIX, LocalDateTime.now().format(SIMPLE_DATE_FORMAT), EXTENSION);
|
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() {
|
private void deleteTempAudioFile() {
|
||||||
@ -160,11 +177,11 @@ public class VoiceRecorder {
|
|||||||
|
|
||||||
public static class VoiceRecordingResult {
|
public static class VoiceRecordingResult {
|
||||||
private final String mimeType;
|
private final String mimeType;
|
||||||
private final File file;
|
private final DocumentFile file;
|
||||||
private final List<Float> waveform;
|
private final List<Float> waveform;
|
||||||
private final int samplingFreq = 10;
|
private final int samplingFreq = 10;
|
||||||
|
|
||||||
public VoiceRecordingResult(final String mimeType, final File file, final List<Float> waveform) {
|
public VoiceRecordingResult(final String mimeType, final DocumentFile file, final List<Float> waveform) {
|
||||||
this.mimeType = mimeType;
|
this.mimeType = mimeType;
|
||||||
this.file = file;
|
this.file = file;
|
||||||
this.waveform = waveform;
|
this.waveform = waveform;
|
||||||
@ -174,7 +191,7 @@ public class VoiceRecorder {
|
|||||||
return mimeType;
|
return mimeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public File getFile() {
|
public DocumentFile getFile() {
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
package awais.instagrabber.viewmodels
|
package awais.instagrabber.viewmodels
|
||||||
|
|
||||||
|
import android.R.attr
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.ContentResolver
|
import android.content.ContentResolver
|
||||||
import android.media.MediaScannerConnection
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.Log
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import androidx.lifecycle.*
|
import androidx.lifecycle.*
|
||||||
import awais.instagrabber.customviews.emoji.Emoji
|
import awais.instagrabber.customviews.emoji.Emoji
|
||||||
import awais.instagrabber.managers.DirectMessagesManager
|
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.MediaUtils.VideoInfo
|
||||||
import awais.instagrabber.utils.VoiceRecorder.VoiceRecorderCallback
|
import awais.instagrabber.utils.VoiceRecorder.VoiceRecorderCallback
|
||||||
import awais.instagrabber.utils.VoiceRecorder.VoiceRecordingResult
|
import awais.instagrabber.utils.VoiceRecorder.VoiceRecordingResult
|
||||||
import awais.instagrabber.utils.extensions.TAG
|
|
||||||
import java.io.File
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
class DirectThreadViewModel(
|
class DirectThreadViewModel(
|
||||||
application: Application,
|
application: Application,
|
||||||
val threadId: String,
|
val threadId: String,
|
||||||
@ -37,7 +36,7 @@ class DirectThreadViewModel(
|
|||||||
|
|
||||||
// private static final String ERROR_INVALID_THREAD = "Invalid thread";
|
// private static final String ERROR_INVALID_THREAD = "Invalid thread";
|
||||||
private val contentResolver: ContentResolver = application.contentResolver
|
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 var voiceRecorder: VoiceRecorder? = null
|
||||||
private lateinit var threadManager: ThreadManager
|
private lateinit var threadManager: ThreadManager
|
||||||
|
|
||||||
@ -87,33 +86,24 @@ class DirectThreadViewModel(
|
|||||||
|
|
||||||
fun startRecording(): LiveData<Resource<Any?>> {
|
fun startRecording(): LiveData<Resource<Any?>> {
|
||||||
val data = MutableLiveData<Resource<Any?>>()
|
val data = MutableLiveData<Resource<Any?>>()
|
||||||
voiceRecorder = VoiceRecorder(recordingsDir, object : VoiceRecorderCallback {
|
voiceRecorder = VoiceRecorder(recordingsDir!!, object : VoiceRecorderCallback {
|
||||||
override fun onStart() {}
|
override fun onStart() {}
|
||||||
override fun onComplete(result: VoiceRecordingResult) {
|
override fun onComplete(result: VoiceRecordingResult) {
|
||||||
Log.d(TAG, "onComplete: recording complete. Scanning file...")
|
// Log.d(TAG, "onComplete: recording complete. Scanning file...");
|
||||||
MediaScannerConnection.scanFile(
|
MediaUtils.getVoiceInfo(
|
||||||
getApplication(),
|
contentResolver,
|
||||||
arrayOf(result.file.absolutePath),
|
result.file.uri,
|
||||||
arrayOf(result.mimeType)
|
object : OnInfoLoadListener<VideoInfo?> {
|
||||||
) { _: 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<VideoInfo?> {
|
|
||||||
override fun onLoad(videoInfo: VideoInfo?) {
|
override fun onLoad(videoInfo: VideoInfo?) {
|
||||||
if (videoInfo == null) return
|
if (videoInfo == null) return
|
||||||
threadManager.sendVoice(
|
threadManager.sendVoice(
|
||||||
data,
|
data,
|
||||||
uri,
|
result.file.uri,
|
||||||
result.waveform,
|
result.waveform,
|
||||||
result.samplingFreq,
|
result.samplingFreq,
|
||||||
videoInfo.duration,
|
videoInfo.duration,
|
||||||
videoInfo.size,
|
result.file.length(),
|
||||||
viewModelScope,
|
viewModelScope
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,11 +112,10 @@ class DirectThreadViewModel(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCancel() {}
|
override fun onCancel() {}
|
||||||
})
|
})
|
||||||
voiceRecorder?.startRecording()
|
voiceRecorder?.startRecording(contentResolver)
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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<String> message = new MutableLiveData<>();
|
||||||
|
private final MutableLiveData<String> prevUri = new MutableLiveData<>();
|
||||||
|
private final MutableLiveData<Boolean> loading = new MutableLiveData<>(false);
|
||||||
|
private final MutableLiveData<Boolean> dirSuccess = new MutableLiveData<>(false);
|
||||||
|
|
||||||
|
public DirectorySelectActivityViewModel(final Application application) {
|
||||||
|
super(application);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<String> getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<String> getPrevUri() {
|
||||||
|
return prevUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Boolean> isLoading() {
|
||||||
|
return loading;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Boolean> 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<UriPermission> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ import android.graphics.RectF;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.documentfile.provider.DocumentFile;
|
||||||
import androidx.lifecycle.AndroidViewModel;
|
import androidx.lifecycle.AndroidViewModel;
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
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.fragments.imageedit.filters.properties.Property;
|
||||||
import awais.instagrabber.models.SavedImageEditState;
|
import awais.instagrabber.models.SavedImageEditState;
|
||||||
import awais.instagrabber.utils.AppExecutors;
|
import awais.instagrabber.utils.AppExecutors;
|
||||||
import awais.instagrabber.utils.DirectoryUtils;
|
import awais.instagrabber.utils.DownloadUtils;
|
||||||
import awais.instagrabber.utils.SerializablePair;
|
import awais.instagrabber.utils.SerializablePair;
|
||||||
|
import awais.instagrabber.utils.Utils;
|
||||||
import jp.co.cyberagent.android.gpuimage.GPUImage;
|
import jp.co.cyberagent.android.gpuimage.GPUImage;
|
||||||
import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter;
|
import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter;
|
||||||
import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilterGroup;
|
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 CROP = "crop";
|
||||||
private static final String RESULT = "result";
|
private static final String RESULT = "result";
|
||||||
private static final String FILE_FORMAT = "yyyyMMddHHmmssSSS";
|
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 static final DateTimeFormatter SIMPLE_DATE_FORMAT = DateTimeFormatter.ofPattern(FILE_FORMAT, Locale.US);
|
||||||
|
|
||||||
private Uri originalUri;
|
private Uri originalUri;
|
||||||
@ -48,18 +51,18 @@ public class ImageEditViewModel extends AndroidViewModel {
|
|||||||
private final MutableLiveData<Boolean> isCropped = new MutableLiveData<>(false);
|
private final MutableLiveData<Boolean> isCropped = new MutableLiveData<>(false);
|
||||||
private final MutableLiveData<Boolean> isTuned = new MutableLiveData<>(false);
|
private final MutableLiveData<Boolean> isTuned = new MutableLiveData<>(false);
|
||||||
private final MutableLiveData<Boolean> isFiltered = new MutableLiveData<>(false);
|
private final MutableLiveData<Boolean> isFiltered = new MutableLiveData<>(false);
|
||||||
private final File outputDir;
|
private final DocumentFile outputDir;
|
||||||
private List<Filter<? extends GPUImageFilter>> tuningFilters;
|
private List<Filter<? extends GPUImageFilter>> tuningFilters;
|
||||||
private Filter<? extends GPUImageFilter> appliedFilter;
|
private Filter<? extends GPUImageFilter> appliedFilter;
|
||||||
private final File destinationFile;
|
private final DocumentFile destinationFile;
|
||||||
|
|
||||||
public ImageEditViewModel(final Application application) {
|
public ImageEditViewModel(final Application application) {
|
||||||
super(application);
|
super(application);
|
||||||
sessionId = LocalDateTime.now().format(SIMPLE_DATE_FORMAT);
|
sessionId = LocalDateTime.now().format(SIMPLE_DATE_FORMAT);
|
||||||
outputDir = DirectoryUtils.getOutputMediaDirectory(application, "Edit", sessionId);
|
outputDir = DownloadUtils.getImageEditDir(sessionId);
|
||||||
destinationFile = new File(outputDir, RESULT + ".jpg");
|
destinationFile = outputDir.createFile(MIME_TYPE, RESULT + ".jpg");
|
||||||
destinationUri = Uri.fromFile(destinationFile);
|
destinationUri = destinationFile.getUri();
|
||||||
cropDestinationUri = Uri.fromFile(new File(outputDir, CROP + ".jpg"));
|
cropDestinationUri = outputDir.createFile(MIME_TYPE, CROP + ".jpg").getUri();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSessionId() {
|
public String getSessionId() {
|
||||||
@ -159,16 +162,15 @@ public class ImageEditViewModel extends AndroidViewModel {
|
|||||||
delete(outputDir);
|
delete(outputDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void delete(@NonNull final File file) {
|
private void delete(@NonNull final DocumentFile file) {
|
||||||
if (file.isDirectory()) {
|
if (file.isDirectory()) {
|
||||||
final File[] files = file.listFiles();
|
final DocumentFile[] files = file.listFiles();
|
||||||
if (files != null) {
|
if (files != null) {
|
||||||
for (File f : files) {
|
for (DocumentFile f : files) {
|
||||||
delete(f);
|
delete(f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//noinspection ResultOfMethodCallIgnored
|
|
||||||
file.delete();
|
file.delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,9 +208,9 @@ public class ImageEditViewModel extends AndroidViewModel {
|
|||||||
return new SerializablePair<>(type, propertyValueMap);
|
return new SerializablePair<>(type, propertyValueMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
public File getDestinationFile() {
|
// public File getDestinationFile() {
|
||||||
return destinationFile;
|
// return destinationFile;
|
||||||
}
|
// }
|
||||||
|
|
||||||
public enum Tab {
|
public enum Tab {
|
||||||
RESULT,
|
RESULT,
|
||||||
|
@ -6,8 +6,8 @@ import android.content.ContentResolver
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
import android.media.MediaMetadataRetriever
|
import android.media.MediaMetadataRetriever
|
||||||
import android.media.MediaScannerConnection
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
@ -15,7 +15,7 @@ import android.os.Looper
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.core.content.FileProvider
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import androidx.work.CoroutineWorker
|
import androidx.work.CoroutineWorker
|
||||||
import androidx.work.Data
|
import androidx.work.Data
|
||||||
import androidx.work.ForegroundInfo
|
import androidx.work.ForegroundInfo
|
||||||
@ -37,11 +37,11 @@ import kotlinx.coroutines.withContext
|
|||||||
import org.apache.commons.imaging.formats.jpeg.iptc.JpegIptcRewriter
|
import org.apache.commons.imaging.formats.jpeg.iptc.JpegIptcRewriter
|
||||||
import java.io.BufferedInputStream
|
import java.io.BufferedInputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.ExecutionException
|
import java.util.concurrent.ExecutionException
|
||||||
|
import java.util.stream.Collectors
|
||||||
|
import kotlin.collections.Map
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
class DownloadWorker(context: Context, workerParams: WorkerParameters) : CoroutineWorker(context, workerParams) {
|
class DownloadWorker(context: Context, workerParams: WorkerParameters) : CoroutineWorker(context, workerParams) {
|
||||||
@ -55,9 +55,14 @@ class DownloadWorker(context: Context, workerParams: WorkerParameters) : Corouti
|
|||||||
.build())
|
.build())
|
||||||
}
|
}
|
||||||
val downloadRequestString: String
|
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 {
|
try {
|
||||||
downloadRequestString = requestFile.bufferedReader().use { it.readText() }
|
val scanner = Scanner(contentResolver.openInputStream(requestFile))
|
||||||
|
downloadRequestString = scanner.useDelimiter("\\A").next()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "doWork: ", e)
|
Log.e(TAG, "doWork: ", e)
|
||||||
return Result.failure(Data.Builder()
|
return Result.failure(Data.Builder()
|
||||||
@ -82,7 +87,7 @@ class DownloadWorker(context: Context, workerParams: WorkerParameters) : Corouti
|
|||||||
val urlToFilePathMap = downloadRequest.urlToFilePathMap
|
val urlToFilePathMap = downloadRequest.urlToFilePathMap
|
||||||
download(urlToFilePathMap)
|
download(urlToFilePathMap)
|
||||||
Handler(Looper.getMainLooper()).postDelayed({ showSummary(urlToFilePathMap) }, 500)
|
Handler(Looper.getMainLooper()).postDelayed({ showSummary(urlToFilePathMap) }, 500)
|
||||||
val deleted = requestFile.delete()
|
val deleted = DocumentFile.fromSingleUri(context, requestFile)!!.delete()
|
||||||
if (!deleted) {
|
if (!deleted) {
|
||||||
Log.w(TAG, "doWork: requestFile not deleted!")
|
Log.w(TAG, "doWork: requestFile not deleted!")
|
||||||
}
|
}
|
||||||
@ -94,10 +99,11 @@ class DownloadWorker(context: Context, workerParams: WorkerParameters) : Corouti
|
|||||||
val entries = urlToFilePathMap.entries
|
val entries = urlToFilePathMap.entries
|
||||||
var count = 1
|
var count = 1
|
||||||
val total = urlToFilePathMap.size
|
val total = urlToFilePathMap.size
|
||||||
for ((url, value) in entries) {
|
for ((url, uriString) in entries) {
|
||||||
updateDownloadProgress(notificationId, count, total, 0f)
|
updateDownloadProgress(notificationId, count, total, 0f)
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
download(notificationId, count, total, url, value)
|
val file = DocumentFile.fromSingleUri(applicationContext, Uri.parse(uriString))
|
||||||
|
download(notificationId, count, total, url, file!!)
|
||||||
}
|
}
|
||||||
count++
|
count++
|
||||||
}
|
}
|
||||||
@ -111,47 +117,49 @@ class DownloadWorker(context: Context, workerParams: WorkerParameters) : Corouti
|
|||||||
position: Int,
|
position: Int,
|
||||||
total: Int,
|
total: Int,
|
||||||
url: String,
|
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
|
// 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 {
|
try {
|
||||||
val urlConnection = URL(url).openConnection()
|
val urlConnection = URL(url).openConnection()
|
||||||
val fileSize = if (Build.VERSION.SDK_INT >= 24) urlConnection.contentLengthLong else urlConnection.contentLength.toLong()
|
val fileSize = if (Build.VERSION.SDK_INT >= 24) urlConnection.contentLengthLong else urlConnection.contentLength.toLong()
|
||||||
var totalRead = 0f
|
var totalRead = 0f
|
||||||
try {
|
try {
|
||||||
BufferedInputStream(urlConnection.getInputStream()).use { bis ->
|
BufferedInputStream(urlConnection.getInputStream()).use { bis ->
|
||||||
FileOutputStream(outFile).use { fos ->
|
contentResolver.openOutputStream(outFile.uri).use { fos ->
|
||||||
val buffer = ByteArray(0x2000)
|
val buffer = ByteArray(0x2000)
|
||||||
var count: Int
|
var count: Int
|
||||||
while (bis.read(buffer, 0, 0x2000).also { count = it } != -1) {
|
while (bis.read(buffer, 0, 0x2000).also { count = it } != -1) {
|
||||||
totalRead += count
|
totalRead += count
|
||||||
fos.write(buffer, 0, count)
|
fos!!.write(buffer, 0, count)
|
||||||
setProgressAsync(Data.Builder().putString(URL, url)
|
setProgressAsync(Data.Builder().putString(URL, url)
|
||||||
.putFloat(PROGRESS, totalRead * 100f / fileSize)
|
.putFloat(PROGRESS, totalRead * 100f / fileSize)
|
||||||
.build())
|
.build())
|
||||||
updateDownloadProgress(notificationId, position, total, totalRead * 100f / fileSize)
|
updateDownloadProgress(notificationId, position, total, totalRead * 100f / fileSize)
|
||||||
}
|
}
|
||||||
fos.flush()
|
fos!!.flush()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} 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) {
|
if (isJpg) {
|
||||||
val finalFile = File(filePath)
|
|
||||||
try {
|
try {
|
||||||
FileInputStream(outFile).use { fis ->
|
contentResolver.openInputStream(outFile.uri).use { fis ->
|
||||||
FileOutputStream(finalFile).use { fos ->
|
contentResolver.openOutputStream(filePath.uri).use { fos ->
|
||||||
val jpegIptcRewriter = JpegIptcRewriter()
|
val jpegIptcRewriter = JpegIptcRewriter()
|
||||||
jpegIptcRewriter.removeIPTC(fis, fos)
|
jpegIptcRewriter.removeIPTC(fis, fos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Error while removing iptc: url: " + url
|
Log.e(TAG, "Error while removing iptc: url: " + url
|
||||||
+ ", tempFile: " + outFile.absolutePath
|
+ ", tempFile: " + outFile.name
|
||||||
+ ", finalFile: " + finalFile.absolutePath, e)
|
+ ", finalFile: " + filePath.name, e)
|
||||||
}
|
}
|
||||||
val deleted = outFile.delete()
|
val deleted = outFile.delete()
|
||||||
if (!deleted) {
|
if (!deleted) {
|
||||||
@ -218,36 +226,69 @@ class DownloadWorker(context: Context, workerParams: WorkerParameters) : Corouti
|
|||||||
return builder.build()
|
return builder.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showSummary(urlToFilePathMap: Map<String, String>?) {
|
private fun showSummary(urlToFilePathMap: Map<String, String>) {
|
||||||
val context = applicationContext
|
val context = applicationContext
|
||||||
val filePaths = urlToFilePathMap!!.values
|
val filePaths = urlToFilePathMap.mapNotNull { DocumentFile.fromSingleUri(context, Uri.parse(it.value)) }
|
||||||
val notifications: MutableList<NotificationCompat.Builder> = LinkedList()
|
val notifications: MutableList<NotificationCompat.Builder> = LinkedList()
|
||||||
val notificationIds: MutableList<Int> = LinkedList()
|
val notificationIds: MutableList<Int> = LinkedList()
|
||||||
var count = 1
|
var count = 1
|
||||||
for (filePath in filePaths) {
|
for (filePath: DocumentFile in filePaths) {
|
||||||
val file = File(filePath)
|
// final File file = new File(filePath);
|
||||||
context.sendBroadcast(Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file)))
|
// context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, filePath.getUri()));
|
||||||
MediaScannerConnection.scanFile(context, arrayOf(file.absolutePath), null, null)
|
// Utils.scanDocumentFile(context, filePath, (path, uri) -> {});
|
||||||
val uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file)
|
// final Uri uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file);
|
||||||
val contentResolver = context.contentResolver
|
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 downloadComplete = context.getString(R.string.downloader_complete)
|
||||||
val intent = Intent(Intent.ACTION_VIEW, uri)
|
val intent = Intent(Intent.ACTION_VIEW, filePath.uri)
|
||||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
|
.addFlags(
|
||||||
|
Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
or Intent.FLAG_FROM_BACKGROUND
|
or Intent.FLAG_FROM_BACKGROUND
|
||||||
or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||||
.putExtra(Intent.EXTRA_STREAM, uri)
|
)
|
||||||
|
.putExtra(Intent.EXTRA_STREAM, filePath.uri)
|
||||||
val pendingIntent = PendingIntent.getActivity(
|
val pendingIntent = PendingIntent.getActivity(
|
||||||
context,
|
context,
|
||||||
DOWNLOAD_NOTIFICATION_INTENT_REQUEST_CODE,
|
DOWNLOAD_NOTIFICATION_INTENT_REQUEST_CODE,
|
||||||
intent,
|
intent,
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_ONE_SHOT
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_ONE_SHOT
|
||||||
)
|
)
|
||||||
val notificationId = notificationId + count
|
val notificationId: Int = notificationId + count
|
||||||
notificationIds.add(notificationId)
|
notificationIds.add(notificationId)
|
||||||
count++
|
count++
|
||||||
val builder: NotificationCompat.Builder = NotificationCompat.Builder(context, DOWNLOAD_CHANNEL_ID)
|
val builder: NotificationCompat.Builder =
|
||||||
|
NotificationCompat.Builder(context, DOWNLOAD_CHANNEL_ID)
|
||||||
.setSmallIcon(R.drawable.ic_download)
|
.setSmallIcon(R.drawable.ic_download)
|
||||||
.setContentText(null)
|
.setContentText(null)
|
||||||
.setContentTitle(downloadComplete)
|
.setContentTitle(downloadComplete)
|
||||||
@ -257,14 +298,18 @@ class DownloadWorker(context: Context, workerParams: WorkerParameters) : Corouti
|
|||||||
.setGroup(NOTIF_GROUP_NAME + "_" + id)
|
.setGroup(NOTIF_GROUP_NAME + "_" + id)
|
||||||
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
|
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
|
||||||
.setContentIntent(pendingIntent)
|
.setContentIntent(pendingIntent)
|
||||||
.addAction(R.drawable.ic_delete,
|
.addAction(
|
||||||
|
R.drawable.ic_delete,
|
||||||
context.getString(R.string.delete),
|
context.getString(R.string.delete),
|
||||||
DeleteImageIntentService.pendingIntent(context, filePath, notificationId))
|
DeleteImageIntentService.pendingIntent(context, filePath, notificationId)
|
||||||
|
)
|
||||||
if (bitmap != null) {
|
if (bitmap != null) {
|
||||||
builder.setLargeIcon(bitmap)
|
builder.setLargeIcon(bitmap)
|
||||||
.setStyle(NotificationCompat.BigPictureStyle()
|
.setStyle(
|
||||||
|
NotificationCompat.BigPictureStyle()
|
||||||
.bigPicture(bitmap)
|
.bigPicture(bitmap)
|
||||||
.bigLargeIcon(null))
|
.bigLargeIcon(null)
|
||||||
|
)
|
||||||
.setBadgeIconType(NotificationCompat.BADGE_ICON_SMALL)
|
.setBadgeIconType(NotificationCompat.BADGE_ICON_SMALL)
|
||||||
}
|
}
|
||||||
notifications.add(builder)
|
notifications.add(builder)
|
||||||
@ -348,13 +393,15 @@ class DownloadWorker(context: Context, workerParams: WorkerParameters) : Corouti
|
|||||||
|
|
||||||
class Builder {
|
class Builder {
|
||||||
private var urlToFilePathMap: MutableMap<String, String> = mutableMapOf()
|
private var urlToFilePathMap: MutableMap<String, String> = mutableMapOf()
|
||||||
fun setUrlToFilePathMap(urlToFilePathMap: MutableMap<String, String>): Builder {
|
fun setUrlToFilePathMap(urlToFilePathMap: MutableMap<String, DocumentFile>): Builder {
|
||||||
this.urlToFilePathMap = urlToFilePathMap
|
this.urlToFilePathMap = urlToFilePathMap
|
||||||
|
.mapValues { it.value.uri.toString() }
|
||||||
|
.toMutableMap()
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addUrl(url: String, filePath: String): Builder {
|
fun addUrl(url: String, filePath: DocumentFile): Builder {
|
||||||
urlToFilePathMap[url] = filePath
|
urlToFilePathMap[url] = filePath.uri.toString()
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
86
app/src/main/res/layout/activity_directory_select.xml
Normal file
86
app/src/main/res/layout/activity_directory_select.xml
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
android:id="@+id/title"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/app_name"
|
||||||
|
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline4"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
android:id="@+id/message"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/prev_uri"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintVertical_chainStyle="packed"
|
||||||
|
app:layout_goneMarginBottom="0dp"
|
||||||
|
tools:text="@string/dir_select_permission_revoked_message"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
android:id="@+id/prev_uri"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:fontFamily="monospace"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||||
|
android:textColor="@color/blue_500"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/message2"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/message"
|
||||||
|
app:layout_goneMarginBottom="0dp"
|
||||||
|
tools:text="content://something/something/content/content"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
android:id="@+id/message2"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/dir_select_message2"
|
||||||
|
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/prev_uri"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||||
|
android:id="@+id/loading_indicator"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:indeterminate="true"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/select_dir"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/title" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/select_dir"
|
||||||
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/select_folder"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -69,5 +69,5 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="end"
|
android:layout_gravity="end"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
android:text="@string/create_backup" />
|
android:text="@string/backup" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
@ -127,7 +127,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="end"
|
android:layout_gravity="end"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
android:text="@string/restore_backup"
|
android:text="@string/restore"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/bottom_password_divider" />
|
app:layout_constraintTop_toBottomOf="@id/bottom_password_divider" />
|
||||||
|
|
||||||
|
@ -501,6 +501,16 @@
|
|||||||
<string name="dm_remove_warning">If saved, all DM related features will be disabled on next launch</string>
|
<string name="dm_remove_warning">If saved, all DM related features will be disabled on next launch</string>
|
||||||
<string name="copy_caption">Copy caption</string>
|
<string name="copy_caption">Copy caption</string>
|
||||||
<string name="copy_reply">Copy reply</string>
|
<string name="copy_reply">Copy reply</string>
|
||||||
|
<string name="restore">Restore</string>
|
||||||
|
<string name="backup">Backup</string>
|
||||||
|
<string name="dir_select_default_message">Select a folder where Barinsta can store downloads and temporary files.\n\nYou can change this later in More > Settings > Downloads.</string>
|
||||||
|
<string name="dir_select_reselect_message">Android has changed the way apps can access files and directories on storage. Currently Barinsta does not have permission to access the following folder:</string>
|
||||||
|
<string name="dir_select_permission_revoked_message">Permissions for the previously selected folder were revoked by the system:</string>
|
||||||
|
<string name="dir_select_folder_not_exist">The previously selected folder does not exist now:</string>
|
||||||
|
<string name="dir_select_message2">Re-select the directory or select a new directory by clicking the button below.</string>
|
||||||
|
<string name="select_a_folder">No folder selected!</string>
|
||||||
|
<string name="dir_select_success_message">Success! Please wait. Starting app…</string>
|
||||||
|
<string name="barinsta_folder">Barinsta folder</string>
|
||||||
<string name="top">Top</string>
|
<string name="top">Top</string>
|
||||||
<string name="recent">Recent</string>
|
<string name="recent">Recent</string>
|
||||||
<string name="clear">Clear</string>
|
<string name="clear">Clear</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user