Browse Source

made pr buildable (not functional)

renovate/org.robolectric-robolectric-4.x
Austin Huang 4 years ago
parent
commit
b287f96415
No known key found for this signature in database GPG Key ID: 84C23AA04587A91F
  1. 42
      app/src/main/java/awais/instagrabber/activities/CameraActivity.kt
  2. 4
      app/src/main/java/awais/instagrabber/activities/DirectorySelectActivity.java
  3. 3
      app/src/main/java/awais/instagrabber/dialogs/CreateBackupDialogFragment.java
  4. 2
      app/src/main/java/awais/instagrabber/dialogs/RestoreBackupDialogFragment.java
  5. 5
      app/src/main/java/awais/instagrabber/fragments/imageedit/ImageEditFragment.java
  6. 1
      app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.kt
  7. 7
      app/src/main/java/awais/instagrabber/utils/BitmapUtils.kt
  8. 1
      app/src/main/java/awais/instagrabber/utils/Constants.kt
  9. 10
      app/src/main/java/awais/instagrabber/utils/DownloadUtils.java
  10. 12
      app/src/main/java/awais/instagrabber/utils/MediaUploader.kt
  11. 2
      app/src/main/java/awais/instagrabber/utils/MediaUtils.java
  12. 5
      app/src/main/java/awais/instagrabber/utils/VoiceRecorder.java
  13. 39
      app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.kt
  14. 4
      app/src/main/java/awais/instagrabber/viewmodels/DirectorySelectActivityViewModel.java
  15. 144
      app/src/main/java/awais/instagrabber/workers/DownloadWorker.kt

42
app/src/main/java/awais/instagrabber/activities/CameraActivity.kt

@ -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.File
import java.io.IOException
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 outputFileOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()
val fileName = simpleDateFormat.format(System.currentTimeMillis()) + ".jpg"
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)
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()
intent.data = uri1
setResult(RESULT_OK, intent)
finish()
}
Log.d(TAG, "onImageSaved: $uri")
try { outputStream.close() } catch (ignored: IOException) {}
val intent = Intent()
intent.data = photoFile.uri
setResult(RESULT_OK, intent)
finish()
Log.d(TAG, "onImageSaved: " + photoFile.uri)
} }
override fun onError(exception: ImageCaptureException) { override fun onError(exception: ImageCaptureException) {
Log.e(TAG, "onError: ", exception) Log.e(TAG, "onError: ", exception)
try { outputStream.close() } catch (ignored: IOException) {}
} }
} }
) )

4
app/src/main/java/awais/instagrabber/activities/DirectorySelectActivity.java

@ -39,7 +39,7 @@ public class DirectorySelectActivity extends BaseLanguageActivity {
viewModel = new ViewModelProvider(this).get(DirectorySelectActivityViewModel.class); viewModel = new ViewModelProvider(this).get(DirectorySelectActivityViewModel.class);
setupObservers(); setupObservers();
binding.selectDir.setOnClickListener(v -> openDirectoryChooser()); binding.selectDir.setOnClickListener(v -> openDirectoryChooser());
AppExecutors.getInstance().mainThread().execute(() -> viewModel.setInitialUri(getIntent()));
AppExecutors.INSTANCE.getMainThread().execute(() -> viewModel.setInitialUri(getIntent()));
} }
private void setupObservers() { private void setupObservers() {
@ -81,7 +81,7 @@ public class DirectorySelectActivity extends BaseLanguageActivity {
showErrorDialog(getString(R.string.select_a_folder)); showErrorDialog(getString(R.string.select_a_folder));
return; return;
} }
AppExecutors.getInstance().mainThread().execute(() -> {
AppExecutors.INSTANCE.getMainThread().execute(() -> {
try { try {
viewModel.setupSelectedDir(data); viewModel.setupSelectedDir(data);
final Intent intent = new Intent(this, MainActivity.class); final Intent intent = new Intent(this, MainActivity.class);

3
app/src/main/java/awais/instagrabber/dialogs/CreateBackupDialogFragment.java

@ -207,8 +207,7 @@ public class CreateBackupDialogFragment extends DialogFragment {
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE); intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("application/octet-stream"); intent.setType("application/octet-stream");
final Date now = new Date();
final String fileName = String.format("barinsta_%s.backup", BACKUP_FILE_DATE_TIME_FORMAT.format(now));
final String fileName = String.format("barinsta_%s.backup", LocalDateTime.now().format(BACKUP_FILE_DATE_TIME_FORMAT));
intent.putExtra(Intent.EXTRA_TITLE, fileName); intent.putExtra(Intent.EXTRA_TITLE, fileName);
// Optionally, specify a URI for the directory that should be opened in // Optionally, specify a URI for the directory that should be opened in

2
app/src/main/java/awais/instagrabber/dialogs/RestoreBackupDialogFragment.java

@ -115,7 +115,7 @@ public class RestoreBackupDialogFragment extends DialogFragment {
binding.btnRestore.setEnabled(true); binding.btnRestore.setEnabled(true);
} }
uri = data.getData(); uri = data.getData();
AppExecutors.getInstance().mainThread().execute(() -> {
AppExecutors.INSTANCE.getMainThread().execute(() -> {
Cursor c = null; Cursor c = null;
try { try {
String[] projection = {MediaStore.Files.FileColumns.DISPLAY_NAME}; String[] projection = {MediaStore.Files.FileColumns.DISPLAY_NAME};

5
app/src/main/java/awais/instagrabber/fragments/imageedit/ImageEditFragment.java

@ -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
app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.kt

@ -19,6 +19,7 @@ object PreferenceKeys {
const val APP_THEME = "app_theme_v19" const val APP_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"

7
app/src/main/java/awais/instagrabber/utils/BitmapUtils.kt

@ -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 {
val tempFile = file ?: DownloadUtils.getTempFile()
FileOutputStream(tempFile).use { output ->
fun convertToJpegAndSaveToFile(contentResolver: ContentResolver, bitmap: Bitmap, file: DocumentFile?): DocumentFile {
val tempFile = file ?: DownloadUtils.getTempFile(null, "jpg")
contentResolver.openOutputStream(tempFile.uri).use { output ->
val compressResult = bitmap.compress(Bitmap.CompressFormat.JPEG, 100, output) val compressResult = bitmap.compress(Bitmap.CompressFormat.JPEG, 100, output)
if (!compressResult) { if (!compressResult) {
throw RuntimeException("Compression failed!") throw RuntimeException("Compression failed!")

1
app/src/main/java/awais/instagrabber/utils/Constants.kt

@ -89,4 +89,5 @@ object Constants {
const val DM_THREAD_ACTION_EXTRA_THREAD_ID = "thread_id" const val DM_THREAD_ACTION_EXTRA_THREAD_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"
} }

10
app/src/main/java/awais/instagrabber/utils/DownloadUtils.java

@ -4,6 +4,7 @@ import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.UriPermission; import android.content.UriPermission;
import android.Manifest;
import android.net.Uri; import android.net.Uri;
import android.util.Log; import android.util.Log;
import android.webkit.MimeTypeMap; import android.webkit.MimeTypeMap;
@ -59,6 +60,9 @@ public final class DownloadUtils {
private static DocumentFile root; private static DocumentFile root;
public static final String WRITE_PERMISSION = Manifest.permission.WRITE_EXTERNAL_STORAGE;
public static final String[] PERMS = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE};
public static void init(@NonNull final Context context) throws ReselectDocumentTreeException { public static void init(@NonNull final Context context) throws ReselectDocumentTreeException {
final String barinstaDirUri = Utils.settingsHelper.getString(PREF_BARINSTA_DIR_URI); final String barinstaDirUri = Utils.settingsHelper.getString(PREF_BARINSTA_DIR_URI);
if (TextUtils.isEmpty(barinstaDirUri)) { if (TextUtils.isEmpty(barinstaDirUri)) {
@ -177,7 +181,7 @@ public final class DownloadUtils {
private static List<String> getSubPathForUserFolder(final String username) { private static List<String> getSubPathForUserFolder(final String username) {
final List<String> list = new ArrayList<>(); final List<String> list = new ArrayList<>();
if (!Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_USER_FOLDER) || TextUtils.isEmpty(username)) {
if (!Utils.settingsHelper.getBoolean(PreferenceKeys.DOWNLOAD_USER_FOLDER) || TextUtils.isEmpty(username)) {
list.add(DIR_DOWNLOADS); list.add(DIR_DOWNLOADS);
return list; return list;
} }
@ -425,7 +429,7 @@ public final class DownloadUtils {
final String extension = DownloadUtils.getFileExtensionFromUrl(url); final String extension = DownloadUtils.getFileExtensionFromUrl(url);
final String baseFileName = storyModel.getStoryMediaId() + "_" final String baseFileName = storyModel.getStoryMediaId() + "_"
+ storyModel.getTimestamp() + extension; + storyModel.getTimestamp() + extension;
final String usernamePrepend = Utils.settingsHelper.getBoolean(Constants.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 String fileName = usernamePrepend + baseFileName; final String fileName = usernamePrepend + baseFileName;
DocumentFile saveFile = downloadDir.findFile(fileName); DocumentFile saveFile = downloadDir.findFile(fileName);
@ -501,7 +505,7 @@ public final class DownloadUtils {
if (childPositionIfSingle >= 0 && feedModels.size() == 1 && i != childPositionIfSingle) continue; if (childPositionIfSingle >= 0 && feedModels.size() == 1 && i != childPositionIfSingle) continue;
final Media child = sliderItems.get(i); final Media child = sliderItems.get(i);
final String url = getUrlOfType(child); final String url = getUrlOfType(child);
final String usernamePrepend = Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_PREPEND_USER_NAME) && mediaUser != null
final String usernamePrepend = Utils.settingsHelper.getBoolean(PreferenceKeys.DOWNLOAD_PREPEND_USER_NAME) && mediaUser != null
? mediaUser.getUsername() ? mediaUser.getUsername()
: ""; : "";
final Pair<List<String>, String> pair = getDownloadChildSavePaths( final Pair<List<String>, String> pair = getDownloadChildSavePaths(

12
app/src/main/java/awais/instagrabber/utils/MediaUploader.kt

@ -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
app/src/main/java/awais/instagrabber/utils/MediaUtils.java

@ -57,7 +57,7 @@ public final class MediaUtils {
public static void getVoiceInfo(@NonNull final ContentResolver contentResolver, public static void getVoiceInfo(@NonNull final ContentResolver contentResolver,
@NonNull final Uri uri, @NonNull final Uri uri,
@NonNull final OnInfoLoadListener<VideoInfo> listener) { @NonNull final OnInfoLoadListener<VideoInfo> listener) {
AppExecutors.getInstance().tasksThread().submit(() -> {
AppExecutors.INSTANCE.getTasksThread().submit(() -> {
try (ParcelFileDescriptor parcelFileDescriptor = contentResolver.openFileDescriptor(uri, "r")) { try (ParcelFileDescriptor parcelFileDescriptor = contentResolver.openFileDescriptor(uri, "r")) {
if (parcelFileDescriptor == null) { if (parcelFileDescriptor == null) {
listener.onLoad(null); listener.onLoad(null);

5
app/src/main/java/awais/instagrabber/utils/VoiceRecorder.java

@ -1,6 +1,7 @@
package awais.instagrabber.utils; package awais.instagrabber.utils;
import android.app.Application; 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;
@ -44,7 +45,7 @@ public class VoiceRecorder {
this.callback = callback; this.callback = callback;
} }
public void startRecording(final Application application) {
public void startRecording(final ContentResolver contentResolver) {
stopped = false; stopped = false;
ParcelFileDescriptor parcelFileDescriptor = null; ParcelFileDescriptor parcelFileDescriptor = null;
try { try {
@ -53,7 +54,7 @@ public class VoiceRecorder {
recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
deleteTempAudioFile(); deleteTempAudioFile();
audioTempFile = getAudioRecordFile(); audioTempFile = getAudioRecordFile();
parcelFileDescriptor = application.getContentResolver().openFileDescriptor(audioTempFile.getUri(), "rwt");
parcelFileDescriptor = contentResolver.openFileDescriptor(audioTempFile.getUri(), "rwt");
recorder.setOutputFile(parcelFileDescriptor.getFileDescriptor()); 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);

39
app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.kt

@ -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...")
MediaScannerConnection.scanFile(
getApplication(),
arrayOf(result.file.absolutePath),
arrayOf(result.mimeType)
) { _: String?, uri: Uri? ->
if (uri == null) {
val msg = "Scan failed!"
Log.e(TAG, msg)
data.postValue(error(msg, null))
return@scanFile
}
Log.d(TAG, "onComplete: scan complete")
MediaUtils.getVoiceInfo(contentResolver, uri, object : OnInfoLoadListener<VideoInfo?> {
// Log.d(TAG, "onComplete: recording complete. Scanning file...");
MediaUtils.getVoiceInfo(
contentResolver,
result.file.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,
viewModelScope,
result.file.length(),
viewModelScope
) )
} }
@ -121,12 +111,11 @@ class DirectThreadViewModel(
data.postValue(error(t.message, null)) data.postValue(error(t.message, null))
} }
}) })
}
} }
override fun onCancel() {} override fun onCancel() {}
}) })
voiceRecorder?.startRecording()
voiceRecorder?.startRecording(contentResolver)
return data return data
} }

4
app/src/main/java/awais/instagrabber/viewmodels/DirectorySelectActivityViewModel.java

@ -24,6 +24,8 @@ 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 awais.instagrabber.fragments.settings.PreferenceKeys.FOLDER_PATH;
public class DirectorySelectActivityViewModel extends AndroidViewModel { public class DirectorySelectActivityViewModel extends AndroidViewModel {
private static final String TAG = DirectorySelectActivityViewModel.class.getSimpleName(); private static final String TAG = DirectorySelectActivityViewModel.class.getSimpleName();
@ -67,7 +69,7 @@ public class DirectorySelectActivityViewModel extends AndroidViewModel {
private void setMessage(@Nullable final Uri initialUri) { private void setMessage(@Nullable final Uri initialUri) {
if (initialUri == null) { if (initialUri == null) {
final String prevVersionFolderPath = Utils.settingsHelper.getString(Constants.FOLDER_PATH);
final String prevVersionFolderPath = Utils.settingsHelper.getString(FOLDER_PATH);
if (TextUtils.isEmpty(prevVersionFolderPath)) { if (TextUtils.isEmpty(prevVersionFolderPath)) {
// default message // default message
message.postValue(getApplication().getString(R.string.dir_select_default_message)); message.postValue(getApplication().getString(R.string.dir_select_default_message));

144
app/src/main/java/awais/instagrabber/workers/DownloadWorker.kt

@ -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,13 +37,12 @@ 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 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) {
private val notificationManager: NotificationManagerCompat = NotificationManagerCompat.from(context) private val notificationManager: NotificationManagerCompat = NotificationManagerCompat.from(context)
@ -89,15 +88,15 @@ class DownloadWorker(context: Context, workerParams: WorkerParameters) : Corouti
return Result.success() return Result.success()
} }
private suspend fun download(urlToFilePathMap: Map<String, String>) {
private suspend fun download(urlToFilePathMap: Map<String, DocumentFile>) {
val notificationId = notificationId val notificationId = notificationId
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, file) in entries) {
updateDownloadProgress(notificationId, count, total, 0f) updateDownloadProgress(notificationId, count, total, 0f)
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
download(notificationId, count, total, url, value)
download(notificationId, count, total, url, file)
} }
count++ count++
} }
@ -111,47 +110,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 ->
FileOutputStream(finalFile).use { fos ->
contentResolver.openInputStream(outFile.uri).use { fis ->
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
+ ", finalFile: " + finalFile.absolutePath, e)
+ ", tempFile: " + outFile.name
+ ", finalFile: " + filePath.name, e)
} }
val deleted = outFile.delete() val deleted = outFile.delete()
if (!deleted) { if (!deleted) {
@ -218,53 +219,90 @@ 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, DocumentFile>?) {
val context = applicationContext val context = applicationContext
val filePaths = urlToFilePathMap!!.values val filePaths = urlToFilePathMap!!.values
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) {
val file = File(filePath)
context.sendBroadcast(Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file)))
MediaScannerConnection.scanFile(context, arrayOf(file.absolutePath), null, null)
val uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file)
for (filePath: DocumentFile in filePaths) {
// final File file = new File(filePath);
// context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, filePath.getUri()));
// Utils.scanDocumentFile(context, filePath, (path, uri) -> {});
// final Uri uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file);
val contentResolver = context.contentResolver val 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)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
or Intent.FLAG_FROM_BACKGROUND
or Intent.FLAG_GRANT_READ_URI_PERMISSION
or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
.putExtra(Intent.EXTRA_STREAM, uri)
val intent = Intent(Intent.ACTION_VIEW, filePath.uri)
.addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK
or Intent.FLAG_FROM_BACKGROUND
or Intent.FLAG_GRANT_READ_URI_PERMISSION
or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
.putExtra(Intent.EXTRA_STREAM, filePath.uri)
val pendingIntent = PendingIntent.getActivity( 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)
.setSmallIcon(R.drawable.ic_download)
.setContentText(null)
.setContentTitle(downloadComplete)
.setWhen(System.currentTimeMillis())
.setOnlyAlertOnce(true)
.setAutoCancel(true)
.setGroup(NOTIF_GROUP_NAME + "_" + id)
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
.setContentIntent(pendingIntent)
.addAction(R.drawable.ic_delete,
context.getString(R.string.delete),
DeleteImageIntentService.pendingIntent(context, filePath, notificationId))
val builder: NotificationCompat.Builder =
NotificationCompat.Builder(context, DOWNLOAD_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_download)
.setContentText(null)
.setContentTitle(downloadComplete)
.setWhen(System.currentTimeMillis())
.setOnlyAlertOnce(true)
.setAutoCancel(true)
.setGroup(NOTIF_GROUP_NAME + "_" + id)
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
.setContentIntent(pendingIntent)
.addAction(
R.drawable.ic_delete,
context.getString(R.string.delete),
DeleteImageIntentService.pendingIntent(context, filePath, notificationId)
)
if (bitmap != null) { if (bitmap != null) {
builder.setLargeIcon(bitmap) builder.setLargeIcon(bitmap)
.setStyle(NotificationCompat.BigPictureStyle()
.bigPicture(bitmap)
.bigLargeIcon(null))
.setStyle(
NotificationCompat.BigPictureStyle()
.bigPicture(bitmap)
.bigLargeIcon(null)
)
.setBadgeIconType(NotificationCompat.BADGE_ICON_SMALL) .setBadgeIconType(NotificationCompat.BADGE_ICON_SMALL)
} }
notifications.add(builder) notifications.add(builder)
@ -344,16 +382,16 @@ class DownloadWorker(context: Context, workerParams: WorkerParameters) : Corouti
return bitmap return bitmap
} }
class DownloadRequest private constructor(val urlToFilePathMap: Map<String, String>) {
class DownloadRequest private constructor(val urlToFilePathMap: Map<String, DocumentFile>) {
class Builder { class Builder {
private var urlToFilePathMap: MutableMap<String, String> = mutableMapOf()
fun setUrlToFilePathMap(urlToFilePathMap: MutableMap<String, String>): Builder {
private var urlToFilePathMap: MutableMap<String, DocumentFile> = mutableMapOf()
fun setUrlToFilePathMap(urlToFilePathMap: MutableMap<String, DocumentFile>): Builder {
this.urlToFilePathMap = urlToFilePathMap this.urlToFilePathMap = urlToFilePathMap
return this return this
} }
fun addUrl(url: String, filePath: String): Builder {
fun addUrl(url: String, filePath: DocumentFile): Builder {
urlToFilePathMap[url] = filePath urlToFilePathMap[url] = filePath
return this return this
} }

Loading…
Cancel
Save