mirror of
				https://github.com/KokaKiwi/BarInsta
				synced 2025-10-30 19:15:35 +00:00 
			
		
		
		
	Migrate File usage to DocumentFile
This commit is contained in:
		
							parent
							
								
									91c70b778a
								
							
						
					
					
						commit
						7c0acdbd6e
					
				| @ -14,6 +14,7 @@ import java.text.SimpleDateFormat; | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| import awais.instagrabber.utils.Constants; | ||||
| import awais.instagrabber.utils.DownloadUtils; | ||||
| import awais.instagrabber.utils.LocaleUtils; | ||||
| import awais.instagrabber.utils.SettingsHelper; | ||||
| import awais.instagrabber.utils.TextUtils; | ||||
| @ -86,5 +87,6 @@ public final class InstaGrabberApplication extends Application { | ||||
|         if (TextUtils.isEmpty(settingsHelper.getString(Constants.DEVICE_UUID))) { | ||||
|             settingsHelper.putString(Constants.DEVICE_UUID, UUID.randomUUID().toString()); | ||||
|         } | ||||
|         DownloadUtils.init(this); | ||||
|     } | ||||
| } | ||||
| @ -5,12 +5,9 @@ import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.content.res.Configuration; | ||||
| import android.hardware.display.DisplayManager; | ||||
| import android.media.MediaScannerConnection; | ||||
| import android.net.Uri; | ||||
| import android.os.Bundle; | ||||
| import android.util.Log; | ||||
| import android.view.LayoutInflater; | ||||
| import android.webkit.MimeTypeMap; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| @ -21,11 +18,13 @@ import androidx.camera.core.ImageCaptureException; | ||||
| import androidx.camera.core.Preview; | ||||
| import androidx.camera.lifecycle.ProcessCameraProvider; | ||||
| import androidx.core.content.ContextCompat; | ||||
| import androidx.documentfile.provider.DocumentFile; | ||||
| 
 | ||||
| import com.google.common.io.Files; | ||||
| import com.google.common.util.concurrent.ListenableFuture; | ||||
| 
 | ||||
| import java.io.File; | ||||
| import java.io.FileNotFoundException; | ||||
| import java.io.IOException; | ||||
| import java.io.OutputStream; | ||||
| import java.text.SimpleDateFormat; | ||||
| import java.util.Locale; | ||||
| import java.util.concurrent.ExecutionException; | ||||
| @ -45,7 +44,7 @@ public class CameraActivity extends BaseLanguageActivity { | ||||
| 
 | ||||
|     private ActivityCameraBinding binding; | ||||
|     private ImageCapture imageCapture; | ||||
|     private File outputDirectory; | ||||
|     private DocumentFile outputDirectory; | ||||
|     private ExecutorService cameraExecutor; | ||||
|     private int displayId = -1; | ||||
| 
 | ||||
| @ -113,7 +112,13 @@ public class CameraActivity extends BaseLanguageActivity { | ||||
|     } | ||||
| 
 | ||||
|     private void updateUi() { | ||||
|         binding.cameraCaptureButton.setOnClickListener(v -> takePhoto()); | ||||
|         binding.cameraCaptureButton.setOnClickListener(v -> { | ||||
|             try { | ||||
|                 takePhoto(); | ||||
|             } catch (FileNotFoundException e) { | ||||
|                 Log.e(TAG, "updateUi: ", e); | ||||
|             } | ||||
|         }); | ||||
|         // Disable the button until the camera is set up | ||||
|         binding.switchCamera.setEnabled(false); | ||||
|         // Listener for button used to switch cameras. Only called if the button is enabled | ||||
| @ -200,37 +205,44 @@ public class CameraActivity extends BaseLanguageActivity { | ||||
|         preview.setSurfaceProvider(binding.viewFinder.getSurfaceProvider()); | ||||
|     } | ||||
| 
 | ||||
|     private void takePhoto() { | ||||
|     private void takePhoto() throws FileNotFoundException { | ||||
|         if (imageCapture == null) return; | ||||
|         final File photoFile = new File(outputDirectory, SIMPLE_DATE_FORMAT.format(System.currentTimeMillis()) + ".jpg"); | ||||
|         final ImageCapture.OutputFileOptions outputFileOptions = new ImageCapture.OutputFileOptions.Builder(photoFile).build(); | ||||
|         final String extension = "jpg"; | ||||
|         final String fileName = SIMPLE_DATE_FORMAT.format(System.currentTimeMillis()) + "." + extension; | ||||
|         // final File photoFile = new File(outputDirectory, fileName); | ||||
|         final String mimeType = Utils.mimeTypeMap.getMimeTypeFromExtension(extension); | ||||
|         final DocumentFile photoFile = outputDirectory.createFile(mimeType, fileName); | ||||
|         final OutputStream outputStream = getContentResolver().openOutputStream(photoFile.getUri()); | ||||
|         final ImageCapture.OutputFileOptions outputFileOptions = new ImageCapture.OutputFileOptions.Builder(outputStream).build(); | ||||
|         imageCapture.takePicture( | ||||
|                 outputFileOptions, | ||||
|                 cameraExecutor, | ||||
|                 new ImageCapture.OnImageSavedCallback() { | ||||
|                     @Override | ||||
|                     public void onImageSaved(@NonNull final ImageCapture.OutputFileResults outputFileResults) { | ||||
|                         final Uri uri = Uri.fromFile(photoFile); | ||||
|                         //noinspection UnstableApiUsage | ||||
|                         final String mimeType = MimeTypeMap.getSingleton() | ||||
|                                                            .getMimeTypeFromExtension(Files.getFileExtension(photoFile.getName())); | ||||
|                         MediaScannerConnection.scanFile( | ||||
|                                 CameraActivity.this, | ||||
|                                 new String[]{photoFile.getAbsolutePath()}, | ||||
|                                 new String[]{mimeType}, | ||||
|                                 (path, uri1) -> { | ||||
|                                     Log.d(TAG, "onImageSaved: scan complete"); | ||||
|                                     final Intent intent = new Intent(); | ||||
|                                     intent.setData(uri1); | ||||
|                                     setResult(Activity.RESULT_OK, intent); | ||||
|                                     finish(); | ||||
|                                 }); | ||||
|                         Log.d(TAG, "onImageSaved: " + uri); | ||||
|                         if (outputStream != null) { | ||||
|                             try { outputStream.close(); } catch (IOException ignored) {} | ||||
|                         } | ||||
|                         // final Uri uri = Uri.fromFile(photoFile); | ||||
|                         // final String mimeType = MimeTypeMap.getSingleton() | ||||
|                         //                                    .getMimeTypeFromExtension(Files.getFileExtension(photoFile.getName())); | ||||
|                         sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, photoFile.getUri())); | ||||
|                         Utils.scanDocumentFile(CameraActivity.this, photoFile, (path, uri1) -> { | ||||
|                             Log.d(TAG, "onImageSaved: scan complete"); | ||||
|                             final Intent intent = new Intent(); | ||||
|                             intent.setData(uri1); | ||||
|                             setResult(Activity.RESULT_OK, intent); | ||||
|                             finish(); | ||||
|                         }); | ||||
|                         Log.d(TAG, "onImageSaved: " + photoFile.getUri()); | ||||
|                     } | ||||
| 
 | ||||
|                     @Override | ||||
|                     public void onError(@NonNull final ImageCaptureException exception) { | ||||
|                         Log.e(TAG, "onError: ", exception); | ||||
|                         if (outputStream != null) { | ||||
|                             try { outputStream.close(); } catch (IOException ignored) {} | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|         ); | ||||
|  | ||||
| @ -102,7 +102,7 @@ public class FeedGridItemViewHolder extends RecyclerView.ViewHolder { | ||||
|             binding.typeIcon.setVisibility(View.VISIBLE); | ||||
|             binding.typeIcon.setImageResource(typeIconRes); | ||||
|         } | ||||
|         final DownloadedCheckerAsyncTask task = new DownloadedCheckerAsyncTask(result -> { | ||||
|         final DownloadedCheckerAsyncTask task = new DownloadedCheckerAsyncTask(itemView.getContext(), result -> { | ||||
|             final List<Boolean> checkList = result.get(media.getPk()); | ||||
|             if (checkList == null || checkList.isEmpty()) { | ||||
|                 return; | ||||
|  | ||||
| @ -1,7 +1,9 @@ | ||||
| package awais.instagrabber.asyncs; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.os.AsyncTask; | ||||
| 
 | ||||
| import java.lang.ref.WeakReference; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| @ -12,9 +14,12 @@ import awais.instagrabber.utils.DownloadUtils; | ||||
| public final class DownloadedCheckerAsyncTask extends AsyncTask<Media, Void, Map<String, List<Boolean>>> { | ||||
|     private static final String TAG = "DownloadedCheckerAsyncTask"; | ||||
| 
 | ||||
|     private final WeakReference<Context> context; | ||||
|     private final OnCheckResultListener listener; | ||||
| 
 | ||||
|     public DownloadedCheckerAsyncTask(final OnCheckResultListener listener) { | ||||
|     public DownloadedCheckerAsyncTask(final Context context, | ||||
|                                       final OnCheckResultListener listener) { | ||||
|         this.context = new WeakReference<>(context); | ||||
|         this.listener = listener; | ||||
|     } | ||||
| 
 | ||||
| @ -25,7 +30,9 @@ public final class DownloadedCheckerAsyncTask extends AsyncTask<Media, Void, Map | ||||
|         } | ||||
|         final Map<String, List<Boolean>> map = new HashMap<>(); | ||||
|         for (final Media media : feedModels) { | ||||
|             map.put(media.getPk(), DownloadUtils.checkDownloaded(media)); | ||||
|             final Context context = this.context.get(); | ||||
|             if (context == null) return map; | ||||
|             map.put(media.getPk(), DownloadUtils.checkDownloaded(context, media)); | ||||
|         } | ||||
|         return map; | ||||
|     } | ||||
|  | ||||
| @ -3,7 +3,6 @@ package awais.instagrabber.dialogs; | ||||
| import android.app.Dialog; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.net.Uri; | ||||
| import android.os.Build; | ||||
| import android.os.Bundle; | ||||
| import android.provider.DocumentsContract; | ||||
| @ -214,7 +213,7 @@ public class CreateBackupDialogFragment extends DialogFragment { | ||||
|         // 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, Uri.fromFile(DownloadUtils.getDownloadDir())); | ||||
|             intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, DownloadUtils.getDownloadDir().getUri()); | ||||
|         } | ||||
| 
 | ||||
|         startActivityForResult(intent, CREATE_FILE_REQUEST_CODE); | ||||
|  | ||||
| @ -7,7 +7,6 @@ import android.graphics.Color; | ||||
| import android.graphics.drawable.Animatable; | ||||
| import android.graphics.drawable.ColorDrawable; | ||||
| import android.os.Bundle; | ||||
| import android.os.Environment; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| @ -17,6 +16,7 @@ import android.widget.Toast; | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.core.content.ContextCompat; | ||||
| import androidx.documentfile.provider.DocumentFile; | ||||
| import androidx.fragment.app.DialogFragment; | ||||
| 
 | ||||
| import com.facebook.drawee.backends.pipeline.Fresco; | ||||
| @ -24,15 +24,13 @@ import com.facebook.drawee.controller.BaseControllerListener; | ||||
| import com.facebook.drawee.interfaces.DraweeController; | ||||
| import com.facebook.imagepipeline.image.ImageInfo; | ||||
| 
 | ||||
| import java.io.File; | ||||
| 
 | ||||
| import awais.instagrabber.R; | ||||
| import awais.instagrabber.databinding.DialogProfilepicBinding; | ||||
| import awais.instagrabber.repositories.responses.User; | ||||
| import awais.instagrabber.utils.Constants; | ||||
| import awais.instagrabber.utils.CookieUtils; | ||||
| import awais.instagrabber.utils.DownloadUtils; | ||||
| import awais.instagrabber.utils.TextUtils; | ||||
| import awais.instagrabber.utils.Utils; | ||||
| import awais.instagrabber.webservices.ServiceCallback; | ||||
| import awais.instagrabber.webservices.UserService; | ||||
| 
 | ||||
| @ -182,14 +180,18 @@ public class ProfilePicDialogFragment extends DialogFragment { | ||||
| 
 | ||||
|     private void downloadProfilePicture() { | ||||
|         if (url == null) return; | ||||
|         final File dir = new File(Environment.getExternalStorageDirectory(), "Download"); | ||||
|         // final File dir = new File(Environment.getExternalStorageDirectory(), "Download"); | ||||
|         final Context context = getContext(); | ||||
|         if (context == null) return; | ||||
|         if (dir.exists() || dir.mkdirs()) { | ||||
|             final File saveFile = new File(dir, name + '_' + System.currentTimeMillis() + ".jpg"); | ||||
|             DownloadUtils.download(context, url, saveFile.getAbsolutePath()); | ||||
|             return; | ||||
|         } | ||||
|         Toast.makeText(context, R.string.downloader_error_creating_folder, Toast.LENGTH_SHORT).show(); | ||||
|         // if (dir.exists() || dir.mkdirs()) { | ||||
|         // | ||||
|         // } | ||||
|         final String fileName = name + '_' + System.currentTimeMillis() + ".jpg"; | ||||
|         // final File saveFile = new File(dir, fileName); | ||||
|         final DocumentFile downloadDir = DownloadUtils.getDownloadDir(); | ||||
|         final DocumentFile saveFile = downloadDir.createFile(Utils.mimeTypeMap.getMimeTypeFromExtension("jpg"), fileName); | ||||
|         DownloadUtils.download(context, url, saveFile); | ||||
|         // return; | ||||
|         // Toast.makeText(context, R.string.downloader_error_creating_folder, Toast.LENGTH_SHORT).show(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,22 +1,43 @@ | ||||
| package awais.instagrabber.fragments.settings; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.net.Uri; | ||||
| import android.util.Log; | ||||
| import android.view.View; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.appcompat.widget.AppCompatButton; | ||||
| import androidx.appcompat.widget.AppCompatTextView; | ||||
| import androidx.documentfile.provider.DocumentFile; | ||||
| import androidx.preference.Preference; | ||||
| import androidx.preference.PreferenceScreen; | ||||
| import androidx.preference.PreferenceViewHolder; | ||||
| import androidx.preference.SwitchPreferenceCompat; | ||||
| 
 | ||||
| import com.google.android.material.switchmaterial.SwitchMaterial; | ||||
| 
 | ||||
| import awais.instagrabber.R; | ||||
| import awais.instagrabber.utils.Constants; | ||||
| import awais.instagrabber.utils.TextUtils; | ||||
| 
 | ||||
| import static android.app.Activity.RESULT_OK; | ||||
| import static awais.instagrabber.utils.Constants.FOLDER_PATH; | ||||
| import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO; | ||||
| import static awais.instagrabber.utils.Utils.settingsHelper; | ||||
| 
 | ||||
| public class DownloadsPreferencesFragment extends BasePreferencesFragment { | ||||
|     private static final String TAG = DownloadsPreferencesFragment.class.getSimpleName(); | ||||
|     private static final int SELECT_DIR_REQUEST_CODE = 1; | ||||
|     private SaveToCustomFolderPreference.ResultCallback resultCallback; | ||||
| 
 | ||||
|     @Override | ||||
|     void setupPreferenceScreen(final PreferenceScreen screen) { | ||||
|         final Context context = getContext(); | ||||
|         if (context == null) return; | ||||
|         screen.addPreference(getDownloadUserFolderPreference(context)); | ||||
|         // screen.addPreference(getSaveToCustomFolderPreference(context)); | ||||
|         screen.addPreference(getSaveToCustomFolderPreference(context)); | ||||
|     } | ||||
| 
 | ||||
|     private Preference getDownloadUserFolderPreference(@NonNull final Context context) { | ||||
| @ -27,63 +48,89 @@ public class DownloadsPreferencesFragment extends BasePreferencesFragment { | ||||
|         return preference; | ||||
|     } | ||||
| 
 | ||||
|     // private Preference getSaveToCustomFolderPreference(@NonNull final Context context) { | ||||
|     //     return new SaveToCustomFolderPreference(context, (resultCallback) -> new DirectoryChooser() | ||||
|     //             .setInitialDirectory(settingsHelper.getString(FOLDER_PATH)) | ||||
|     //             .setInteractionListener(file -> { | ||||
|     //                 settingsHelper.putString(FOLDER_PATH, file.getAbsolutePath()); | ||||
|     //                 resultCallback.onResult(file.getAbsolutePath()); | ||||
|     //             }) | ||||
|     //             .show(getParentFragmentManager(), null)); | ||||
|     // } | ||||
|     private Preference getSaveToCustomFolderPreference(@NonNull final Context context) { | ||||
|         return new SaveToCustomFolderPreference(context, (resultCallback) -> { | ||||
|             // Choose a directory using the system's file picker. | ||||
|             final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); | ||||
|             startActivityForResult(intent, SELECT_DIR_REQUEST_CODE); | ||||
|             this.resultCallback = resultCallback; | ||||
| 
 | ||||
|     // public static class SaveToCustomFolderPreference extends Preference { | ||||
|     //     private AppCompatTextView customPathTextView; | ||||
|     //     private final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener; | ||||
|     //     private final String key; | ||||
|     // | ||||
|     //     public SaveToCustomFolderPreference(final Context context, final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener) { | ||||
|     //         super(context); | ||||
|     //         this.onSelectFolderButtonClickListener = onSelectFolderButtonClickListener; | ||||
|     //         key = Constants.FOLDER_SAVE_TO; | ||||
|     //         setLayoutResource(R.layout.pref_custom_folder); | ||||
|     //         setKey(key); | ||||
|     //         setTitle(R.string.save_to_folder); | ||||
|     //         setIconSpaceReserved(false); | ||||
|     //     } | ||||
|     // | ||||
|     //     @Override | ||||
|     //     public void onBindViewHolder(final PreferenceViewHolder holder) { | ||||
|     //         super.onBindViewHolder(holder); | ||||
|     //         final SwitchMaterial cbSaveTo = (SwitchMaterial) holder.findViewById(R.id.cbSaveTo); | ||||
|     //         final View buttonContainer = holder.findViewById(R.id.button_container); | ||||
|     //         customPathTextView = (AppCompatTextView) holder.findViewById(R.id.custom_path); | ||||
|     //         cbSaveTo.setOnCheckedChangeListener((buttonView, isChecked) -> { | ||||
|     //             settingsHelper.putBoolean(FOLDER_SAVE_TO, isChecked); | ||||
|     //             buttonContainer.setVisibility(isChecked ? View.VISIBLE : View.GONE); | ||||
|     //             final String customPath = settingsHelper.getString(FOLDER_PATH); | ||||
|     //             customPathTextView.setText(customPath); | ||||
|     //         }); | ||||
|     //         final boolean savedToEnabled = settingsHelper.getBoolean(key); | ||||
|     //         holder.itemView.setOnClickListener(v -> cbSaveTo.toggle()); | ||||
|     //         cbSaveTo.setChecked(savedToEnabled); | ||||
|     //         buttonContainer.setVisibility(savedToEnabled ? View.VISIBLE : View.GONE); | ||||
|     //         final AppCompatButton btnSaveTo = (AppCompatButton) holder.findViewById(R.id.btnSaveTo); | ||||
|     //         btnSaveTo.setOnClickListener(v -> { | ||||
|     //             if (onSelectFolderButtonClickListener == null) return; | ||||
|     //             onSelectFolderButtonClickListener.onClick(result -> { | ||||
|     //                 if (TextUtils.isEmpty(result)) return; | ||||
|     //                 customPathTextView.setText(result); | ||||
|     //             }); | ||||
|     //         }); | ||||
|     //     } | ||||
|     // | ||||
|     //     public interface ResultCallback { | ||||
|     //         void onResult(String result); | ||||
|     //     } | ||||
|     // | ||||
|     //     public interface OnSelectFolderButtonClickListener { | ||||
|     //         void onClick(ResultCallback resultCallback); | ||||
|     //     } | ||||
|     // } | ||||
|             // new DirectoryChooser() | ||||
|             //         .setInitialDirectory(settingsHelper.getString(FOLDER_PATH)) | ||||
|             //         .setInteractionListener(file -> { | ||||
|             //             settingsHelper.putString(FOLDER_PATH, file.getAbsolutePath()); | ||||
|             //             resultCallback.onResult(file.getAbsolutePath()); | ||||
|             //         }) | ||||
|             //         .show(getParentFragmentManager(), null); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     @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 != SELECT_DIR_REQUEST_CODE) return; | ||||
|         final Context context = getContext(); | ||||
|         if (context == null) return; | ||||
|         final Uri dirUri = data.getData(); | ||||
|         Log.d(TAG, "onActivityResult: " + dirUri); | ||||
|         final int takeFlags = data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); | ||||
|         context.getContentResolver().takePersistableUriPermission(dirUri, takeFlags); | ||||
|         final DocumentFile root = DocumentFile.fromTreeUri(context, dirUri); | ||||
|         settingsHelper.putString(FOLDER_PATH, data.getData().toString()); | ||||
|         if (resultCallback != null) { | ||||
|             resultCallback.onResult(root.getName()); | ||||
|             resultCallback = null; | ||||
|         } | ||||
|         // Log.d(TAG, "onActivityResult: " + root); | ||||
|     } | ||||
| 
 | ||||
|     public static class SaveToCustomFolderPreference extends Preference { | ||||
|         private AppCompatTextView customPathTextView; | ||||
|         private final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener; | ||||
|         private final String key; | ||||
| 
 | ||||
|         public SaveToCustomFolderPreference(final Context context, final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener) { | ||||
|             super(context); | ||||
|             this.onSelectFolderButtonClickListener = onSelectFolderButtonClickListener; | ||||
|             key = FOLDER_SAVE_TO; | ||||
|             setLayoutResource(R.layout.pref_custom_folder); | ||||
|             setKey(key); | ||||
|             setTitle(R.string.save_to_folder); | ||||
|             setIconSpaceReserved(false); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void onBindViewHolder(final PreferenceViewHolder holder) { | ||||
|             super.onBindViewHolder(holder); | ||||
|             final SwitchMaterial cbSaveTo = (SwitchMaterial) holder.findViewById(R.id.cbSaveTo); | ||||
|             final View buttonContainer = holder.findViewById(R.id.button_container); | ||||
|             customPathTextView = (AppCompatTextView) holder.findViewById(R.id.custom_path); | ||||
|             cbSaveTo.setOnCheckedChangeListener((buttonView, isChecked) -> { | ||||
|                 settingsHelper.putBoolean(FOLDER_SAVE_TO, isChecked); | ||||
|                 buttonContainer.setVisibility(isChecked ? View.VISIBLE : View.GONE); | ||||
|                 final String customPath = settingsHelper.getString(FOLDER_PATH); | ||||
|                 customPathTextView.setText(customPath); | ||||
|             }); | ||||
|             final boolean savedToEnabled = settingsHelper.getBoolean(key); | ||||
|             holder.itemView.setOnClickListener(v -> cbSaveTo.toggle()); | ||||
|             cbSaveTo.setChecked(savedToEnabled); | ||||
|             buttonContainer.setVisibility(savedToEnabled ? View.VISIBLE : View.GONE); | ||||
|             final AppCompatButton btnSaveTo = (AppCompatButton) holder.findViewById(R.id.btnSaveTo); | ||||
|             btnSaveTo.setOnClickListener(v -> { | ||||
|                 if (onSelectFolderButtonClickListener == null) return; | ||||
|                 onSelectFolderButtonClickListener.onClick(result -> { | ||||
|                     if (TextUtils.isEmpty(result)) return; | ||||
|                     customPathTextView.setText(result); | ||||
|                 }); | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         public interface ResultCallback { | ||||
|             void onResult(String result); | ||||
|         } | ||||
| 
 | ||||
|         public interface OnSelectFolderButtonClickListener { | ||||
|             void onClick(ResultCallback resultCallback); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -4,13 +4,14 @@ import android.app.IntentService; | ||||
| import android.app.PendingIntent; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.net.Uri; | ||||
| import android.util.Log; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.core.app.NotificationManagerCompat; | ||||
| import androidx.documentfile.provider.DocumentFile; | ||||
| 
 | ||||
| import java.io.File; | ||||
| import java.util.Random; | ||||
| 
 | ||||
| import awais.instagrabber.utils.TextUtils; | ||||
| @ -39,7 +40,10 @@ public class DeleteImageIntentService extends IntentService { | ||||
|         if (intent != null && Intent.ACTION_DELETE.equals(intent.getAction()) && intent.hasExtra(EXTRA_IMAGE_PATH)) { | ||||
|             final String path = intent.getStringExtra(EXTRA_IMAGE_PATH); | ||||
|             if (TextUtils.isEmpty(path)) return; | ||||
|             final File file = new File(path); | ||||
|             // final File file = new File(path); | ||||
|             final Uri parse = Uri.parse(path); | ||||
|             if (parse == null) return; | ||||
|             final DocumentFile file = DocumentFile.fromSingleUri(getApplicationContext(), parse); | ||||
|             boolean deleted; | ||||
|             if (file.exists()) { | ||||
|                 deleted = file.delete(); | ||||
| @ -58,11 +62,11 @@ public class DeleteImageIntentService extends IntentService { | ||||
| 
 | ||||
|     @NonNull | ||||
|     public static PendingIntent pendingIntent(@NonNull final Context context, | ||||
|                                               @NonNull final String imagePath, | ||||
|                                               @NonNull final DocumentFile imagePath, | ||||
|                                               final int notificationId) { | ||||
|         final Intent intent = new Intent(context, DeleteImageIntentService.class); | ||||
|         intent.setAction(Intent.ACTION_DELETE); | ||||
|         intent.putExtra(EXTRA_IMAGE_PATH, imagePath); | ||||
|         intent.putExtra(EXTRA_IMAGE_PATH, imagePath.getUri().toString()); | ||||
|         intent.putExtra(EXTRA_NOTIFICATION_ID, notificationId); | ||||
|         return PendingIntent.getService(context, random.nextInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT); | ||||
|     } | ||||
|  | ||||
| @ -11,14 +11,13 @@ import android.util.LruCache; | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.core.util.Pair; | ||||
| import androidx.documentfile.provider.DocumentFile; | ||||
| 
 | ||||
| import com.google.common.util.concurrent.FutureCallback; | ||||
| import com.google.common.util.concurrent.Futures; | ||||
| import com.google.common.util.concurrent.ListenableFuture; | ||||
| 
 | ||||
| import java.io.File; | ||||
| import java.io.FileNotFoundException; | ||||
| import java.io.FileOutputStream; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.io.OutputStream; | ||||
| @ -247,12 +246,14 @@ public final class BitmapUtils { | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     public static File convertToJpegAndSaveToFile(@NonNull final Bitmap bitmap, @Nullable final File file) throws IOException { | ||||
|         File tempFile = file; | ||||
|     public static DocumentFile convertToJpegAndSaveToFile(@NonNull final ContentResolver contentResolver, | ||||
|                                                           @NonNull final Bitmap bitmap, | ||||
|                                                           @Nullable final DocumentFile file) throws IOException { | ||||
|         DocumentFile tempFile = file; | ||||
|         if (file == null) { | ||||
|             tempFile = DownloadUtils.getTempFile(); | ||||
|             tempFile = DownloadUtils.getTempFile(null, "jpg"); | ||||
|         } | ||||
|         try (OutputStream output = new FileOutputStream(tempFile)) { | ||||
|         try (OutputStream output = contentResolver.openOutputStream(tempFile.getUri())) { | ||||
|             final boolean compressResult = bitmap.compress(Bitmap.CompressFormat.JPEG, 100, output); | ||||
|             if (!compressResult) { | ||||
|                 throw new RuntimeException("Compression failed!"); | ||||
|  | ||||
| @ -1,9 +1,11 @@ | ||||
| package awais.instagrabber.utils; | ||||
| 
 | ||||
| import android.Manifest; | ||||
| import android.content.ContentResolver; | ||||
| import android.content.Context; | ||||
| import android.content.DialogInterface; | ||||
| import android.os.Environment; | ||||
| import android.net.Uri; | ||||
| import android.provider.DocumentsContract; | ||||
| import android.util.Log; | ||||
| import android.webkit.MimeTypeMap; | ||||
| import android.widget.Toast; | ||||
| @ -11,6 +13,8 @@ import android.widget.Toast; | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.appcompat.app.AlertDialog; | ||||
| import androidx.core.util.Pair; | ||||
| import androidx.documentfile.provider.DocumentFile; | ||||
| import androidx.work.Constraints; | ||||
| import androidx.work.Data; | ||||
| import androidx.work.NetworkType; | ||||
| @ -21,9 +25,9 @@ import androidx.work.WorkRequest; | ||||
| import com.google.gson.Gson; | ||||
| 
 | ||||
| import java.io.BufferedWriter; | ||||
| import java.io.File; | ||||
| import java.io.FileWriter; | ||||
| import java.io.IOException; | ||||
| import java.io.OutputStreamWriter; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collections; | ||||
| import java.util.HashMap; | ||||
| import java.util.LinkedList; | ||||
| @ -41,31 +45,50 @@ import awais.instagrabber.repositories.responses.User; | ||||
| import awais.instagrabber.repositories.responses.VideoVersion; | ||||
| import awais.instagrabber.workers.DownloadWorker; | ||||
| 
 | ||||
| import static awais.instagrabber.utils.Constants.FOLDER_PATH; | ||||
| 
 | ||||
| public final class DownloadUtils { | ||||
|     private static final String TAG = DownloadUtils.class.getSimpleName(); | ||||
| 
 | ||||
|     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 DIR_BARINSTA = "Barinsta"; | ||||
|     public static final String DIR_DOWNLOADS = "Downloads"; | ||||
|     public static final String DIR_CAMERA = "Camera"; | ||||
|     public static final String DIR_EDIT = "Edit"; | ||||
|     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 TEMP_DIR = "Temp"; | ||||
| 
 | ||||
|     public static File getDownloadDir(final String... dirs) { | ||||
|         final File parent = new File(Environment.getExternalStorageDirectory(), Environment.DIRECTORY_DOWNLOADS); | ||||
|         File subDir = new File(parent, DIR_BARINSTA); | ||||
|     private static DocumentFile root; | ||||
| 
 | ||||
|     public static void init(@NonNull final Context context) { | ||||
|         // if (!Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) return; | ||||
|         final String customPath = Utils.settingsHelper.getString(FOLDER_PATH); | ||||
|         if (TextUtils.isEmpty(customPath)) return; | ||||
|         // dir = new File(customPath); | ||||
|         root = DocumentFile.fromTreeUri(context, Uri.parse(customPath)); | ||||
|         Log.d(TAG, "init: " + root); | ||||
|         // final File parent = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); | ||||
|         // final DocumentFile documentFile = DocumentFile.fromFile(parent); | ||||
|         // Log.d(TAG, "init: " + documentFile); | ||||
|     } | ||||
| 
 | ||||
|     public static DocumentFile getDownloadDir(final String... dirs) { | ||||
|         // final File parent = new File(Environment.getExternalStorageDirectory(), Environment.DIRECTORY_DOWNLOADS); | ||||
|         // File subDir = new File(parent, DIR_BARINSTA); | ||||
|         DocumentFile subDir = root; | ||||
|         if (dirs != null) { | ||||
|             for (final String dir : dirs) { | ||||
|                 subDir = new File(subDir, dir); | ||||
|                 //noinspection ResultOfMethodCallIgnored | ||||
|                 subDir.mkdirs(); | ||||
|                 final DocumentFile subDirFile = subDir.findFile(dir); | ||||
|                 if (subDirFile == null) { | ||||
|                     subDir = subDir.createDirectory(dir); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return subDir; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     public static File getDownloadDir() { | ||||
|     public static DocumentFile getDownloadDir() { | ||||
|         // final File parent = new File(Environment.getExternalStorageDirectory(), Environment.DIRECTORY_DOWNLOADS); | ||||
|         // final File dir = new File(new File(parent, "barinsta"), "downloads"); | ||||
|         // if (!dir.exists()) { | ||||
| @ -83,37 +106,63 @@ public final class DownloadUtils { | ||||
|         return getDownloadDir(DIR_DOWNLOADS); | ||||
|     } | ||||
| 
 | ||||
|     public static File getCameraDir() { | ||||
|     public static DocumentFile getCameraDir() { | ||||
|         return getDownloadDir(DIR_CAMERA); | ||||
|     } | ||||
| 
 | ||||
|     public static File getImageEditDir(final String sessionId) { | ||||
|     public static DocumentFile getImageEditDir(final String sessionId) { | ||||
|         return getDownloadDir(DIR_EDIT, sessionId); | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     private static File getDownloadDir(@NonNull final Context context, @Nullable final String username) { | ||||
|         return getDownloadDir(context, username, false); | ||||
|     } | ||||
|     // @Nullable | ||||
|     // private static DocumentFile getDownloadDir(@NonNull final Context context, @Nullable final String username) { | ||||
|     //     return getDownloadDir(context, username, false); | ||||
|     // } | ||||
| 
 | ||||
|     @Nullable | ||||
|     private static File getDownloadDir(final Context context, | ||||
|                                        @Nullable final String username, | ||||
|                                        final boolean skipCreateDir) { | ||||
|         File dir = getDownloadDir(); | ||||
| 
 | ||||
|         if (Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_USER_FOLDER) && !TextUtils.isEmpty(username)) { | ||||
|             final String finaleUsername = username.startsWith("@") ? username.substring(1) : username; | ||||
|             dir = new File(dir, finaleUsername); | ||||
|     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; | ||||
|         } | ||||
| 
 | ||||
|         if (context != null && !skipCreateDir && !dir.exists() && !dir.mkdirs()) { | ||||
|         // final String joined = android.text.TextUtils.join("/", userFolderPaths); | ||||
|         // final Uri userFolderUri = DocumentsContract.buildDocumentUriUsingTree(root.getUri(), joined); | ||||
|         // final DocumentFile userFolder = DocumentFile.fromSingleUri(context, userFolderUri); | ||||
|         if (context != null && (dir == null || !dir.exists())) { | ||||
|             Toast.makeText(context, R.string.error_creating_folders, Toast.LENGTH_SHORT).show(); | ||||
|             return null; | ||||
|         } | ||||
|         return dir; | ||||
|     } | ||||
| 
 | ||||
|     private static List<String> getSubPathForUserFolder(final String username) { | ||||
|         final List<String> list = new ArrayList<>(); | ||||
|         if (!Utils.settingsHelper.getBoolean(Constants.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(TEMP_DIR); | ||||
|         if (file == null) { | ||||
|             file = root.createDirectory(TEMP_DIR); | ||||
|         } | ||||
|         return file; | ||||
|     } | ||||
| 
 | ||||
|     //    public static void dmDownload(@NonNull final Context context, | ||||
|     //                                  @Nullable final String username, | ||||
|     //                                  final String modelId, | ||||
| @ -126,59 +175,71 @@ public final class DownloadUtils { | ||||
|     //        } | ||||
|     //    } | ||||
| 
 | ||||
|     private static void dmDownloadImpl(@NonNull final Context context, | ||||
|                                        @Nullable final String username, | ||||
|                                        final String modelId, | ||||
|                                        final String url) { | ||||
|         final File dir = getDownloadDir(context, username); | ||||
|         if (dir.exists() || dir.mkdirs()) { | ||||
|             download(context, | ||||
|                      url, | ||||
|                      getDownloadSaveFile(dir, modelId, url).getAbsolutePath()); | ||||
|             return; | ||||
|         } | ||||
|         Toast.makeText(context, R.string.error_creating_folders, Toast.LENGTH_SHORT).show(); | ||||
|     } | ||||
|     // private static void dmDownloadImpl(@NonNull final Context context, | ||||
|     //                                    @Nullable final String username, | ||||
|     //                                    final String modelId, | ||||
|     //                                    final String url) { | ||||
|     //     final DocumentFile dir = getDownloadDir(context, username); | ||||
|     //     if (dir != null && dir.exists()) { | ||||
|     //         download(context, url, getDownloadSavePaths(dir, modelId, url)); | ||||
|     //         return; | ||||
|     //     } | ||||
|     //     Toast.makeText(context, R.string.error_creating_folders, Toast.LENGTH_SHORT).show(); | ||||
|     // } | ||||
| 
 | ||||
|     @NonNull | ||||
|     private static File getDownloadSaveFile(final File finalDir, | ||||
|                                             final String postId, | ||||
|                                             final String displayUrl) { | ||||
|         return getDownloadSaveFile(finalDir, postId, "", displayUrl); | ||||
|     private static Pair<List<String>, String> getDownloadSavePaths(final List<String> paths, | ||||
|                                                                    final String postId, | ||||
|                                                                    final String displayUrl) { | ||||
|         return getDownloadSavePaths(paths, postId, "", displayUrl); | ||||
|     } | ||||
| 
 | ||||
|     private static File getDownloadChildSaveFile(final File downloadDir, | ||||
|                                                  final String postId, | ||||
|                                                  final int childPosition, | ||||
|                                                  final String url) { | ||||
|     private static Pair<List<String>, String> getDownloadChildSaveFile(final List<String> paths, | ||||
|                                                                        final String postId, | ||||
|                                                                        final int childPosition, | ||||
|                                                                        final String url) { | ||||
|         final String sliderPostfix = "_slide_" + childPosition; | ||||
|         return getDownloadSaveFile(downloadDir, postId, sliderPostfix, url); | ||||
|         return getDownloadSavePaths(paths, postId, sliderPostfix, url); | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     private static File getDownloadSaveFile(final File finalDir, | ||||
|                                             final String postId, | ||||
|                                             final String sliderPostfix, | ||||
|                                             final String displayUrl) { | ||||
|         final String fileName = postId + sliderPostfix + getFileExtensionFromUrl(displayUrl); | ||||
|         return new File(finalDir, fileName); | ||||
|     @Nullable | ||||
|     private static Pair<List<String>, String> getDownloadSavePaths(final List<String> paths, | ||||
|                                                                    final String postId, | ||||
|                                                                    final String sliderPostfix, | ||||
|                                                                    final String displayUrl) { | ||||
|         if (paths == null) return null; | ||||
|         final String extension = getFileExtensionFromUrl(displayUrl); | ||||
|         final String fileName = postId + sliderPostfix + extension; | ||||
|         // return new File(finalDir, fileName); | ||||
|         // DocumentFile file = finalDir.findFile(fileName); | ||||
|         // if (file == null) { | ||||
|         final String mimeType = Utils.mimeTypeMap.getMimeTypeFromExtension(extension.startsWith(".") ? extension.substring(1) : extension); | ||||
|         // file = finalDir.createFile(mimeType, fileName); | ||||
|         // } | ||||
|         paths.add(fileName); | ||||
|         return new Pair<>(paths, mimeType); | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     public static File getTempFile() { | ||||
|     public static DocumentFile getTempFile() { | ||||
|         return getTempFile(null, null); | ||||
|     } | ||||
| 
 | ||||
|     public static File getTempFile(final String fileName, final String extension) { | ||||
|         final File dir = getDownloadDir(); | ||||
|     public static DocumentFile getTempFile(final String fileName, final String extension) { | ||||
|         final DocumentFile dir = getTempDir(); | ||||
|         String name = fileName; | ||||
|         if (TextUtils.isEmpty(name)) { | ||||
|             name = UUID.randomUUID().toString(); | ||||
|         } | ||||
|         String mimeType = "application/octet-stream"; | ||||
|         if (!TextUtils.isEmpty(extension)) { | ||||
|             name += "." + extension; | ||||
|             mimeType = Utils.mimeTypeMap.getMimeTypeFromExtension(extension); | ||||
|         } | ||||
|         return new File(dir, name); | ||||
|         DocumentFile file = dir.findFile(name); | ||||
|         if (file == null) { | ||||
|             file = dir.createFile(mimeType, name); | ||||
|         } | ||||
|         return file; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -221,20 +282,21 @@ public final class DownloadUtils { | ||||
|         return ""; | ||||
|     } | ||||
| 
 | ||||
|     public static List<Boolean> checkDownloaded(@NonNull final Media media) { | ||||
|     public static List<Boolean> checkDownloaded(@NonNull final Context context, | ||||
|                                                 @NonNull final Media media) { | ||||
|         final List<Boolean> checkList = new LinkedList<>(); | ||||
|         final User user = media.getUser(); | ||||
|         String username = "username"; | ||||
|         if (user != null) { | ||||
|             username = user.getUsername(); | ||||
|         } | ||||
|         final File downloadDir = getDownloadDir(null, "@" + username, true); | ||||
|         final List<String> userFolderPaths = getSubPathForUserFolder(username); | ||||
|         switch (media.getMediaType()) { | ||||
|             case MEDIA_TYPE_IMAGE: | ||||
|             case MEDIA_TYPE_VIDEO: { | ||||
|                 final String url = ResponseBodyUtils.getImageUrl(media); | ||||
|                 final File file = getDownloadSaveFile(downloadDir, media.getCode(), url); | ||||
|                 checkList.add(file.exists()); | ||||
|                 final Pair<List<String>, String> pair = getDownloadSavePaths(userFolderPaths, media.getCode(), url); | ||||
|                 checkList.add(checkPathExists(context, pair.first)); | ||||
|                 break; | ||||
|             } | ||||
|             case MEDIA_TYPE_SLIDER: | ||||
| @ -243,8 +305,8 @@ public final class DownloadUtils { | ||||
|                     final Media child = sliderItems.get(i); | ||||
|                     if (child == null) continue; | ||||
|                     final String url = ResponseBodyUtils.getImageUrl(child); | ||||
|                     final File file = getDownloadChildSaveFile(downloadDir, media.getCode(), i + 1, url); | ||||
|                     checkList.add(file.exists()); | ||||
|                     final Pair<List<String>, String> pair = getDownloadChildSaveFile(userFolderPaths, media.getCode(), i + 1, url); | ||||
|                     checkList.add(checkPathExists(context, pair.first)); | ||||
|                 } | ||||
|                 break; | ||||
|             default: | ||||
| @ -252,6 +314,14 @@ public final class DownloadUtils { | ||||
|         return checkList; | ||||
|     } | ||||
| 
 | ||||
|     private static boolean checkPathExists(@NonNull final Context context, | ||||
|                                            @NonNull final List<String> paths) { | ||||
|         final String joined = android.text.TextUtils.join("/", paths); | ||||
|         final Uri userFolderUri = DocumentsContract.buildDocumentUriUsingTree(root.getUri(), joined); | ||||
|         final DocumentFile userFolder = DocumentFile.fromSingleUri(context, userFolderUri); | ||||
|         return userFolder != null && userFolder.exists(); | ||||
|     } | ||||
| 
 | ||||
|     public static void showDownloadDialog(@NonNull Context context, | ||||
|                                           @NonNull final Media feedModel, | ||||
|                                           final int childPosition) { | ||||
| @ -286,15 +356,20 @@ public final class DownloadUtils { | ||||
| 
 | ||||
|     public static void download(@NonNull final Context context, | ||||
|                                 @NonNull final StoryModel storyModel) { | ||||
|         final File downloadDir = getDownloadDir(context, "@" + storyModel.getUsername()); | ||||
|         final DocumentFile downloadDir = getDownloadDir(context, "@" + storyModel.getUsername()); | ||||
|         final String url = storyModel.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO | ||||
|                            ? storyModel.getVideoUrl() | ||||
|                            : storyModel.getStoryUrl(); | ||||
|         final File saveFile = new File(downloadDir, | ||||
|                                        storyModel.getStoryMediaId() | ||||
|                                                + "_" + storyModel.getTimestamp() | ||||
|                                                + DownloadUtils.getFileExtensionFromUrl(url)); | ||||
|         download(context, url, saveFile.getAbsolutePath()); | ||||
|         final String extension = DownloadUtils.getFileExtensionFromUrl(url); | ||||
|         final String fileName = storyModel.getStoryMediaId() + "_" + storyModel.getTimestamp() + extension; | ||||
|         DocumentFile saveFile = downloadDir.findFile(fileName); | ||||
|         if (saveFile == null) { | ||||
|             saveFile = downloadDir.createFile( | ||||
|                     Utils.mimeTypeMap.getMimeTypeFromExtension(extension.startsWith(".") ? extension.substring(1) : extension), | ||||
|                     fileName); | ||||
|         } | ||||
|         // final File saveFile = new File(downloadDir, fileName); | ||||
|         download(context, url, saveFile); | ||||
|     } | ||||
| 
 | ||||
|     public static void download(@NonNull final Context context, | ||||
| @ -316,17 +391,19 @@ public final class DownloadUtils { | ||||
|     private static void download(@NonNull final Context context, | ||||
|                                  @NonNull final List<Media> feedModels, | ||||
|                                  final int childPositionIfSingle) { | ||||
|         final Map<String, String> map = new HashMap<>(); | ||||
|         final Map<String, DocumentFile> map = new HashMap<>(); | ||||
|         for (final Media media : feedModels) { | ||||
|             final User mediaUser = media.getUser(); | ||||
|             final File downloadDir = getDownloadDir(context, mediaUser == null ? "" : "@" + mediaUser.getUsername()); | ||||
|             if (downloadDir == null) return; | ||||
|             final List<String> userFolderPaths = getSubPathForUserFolder(mediaUser == null ? "" : "@" + mediaUser.getUsername()); | ||||
|             // final DocumentFile downloadDir = getDownloadDir(context, mediaUser == null ? "" : "@" + mediaUser.getUsername()); | ||||
|             switch (media.getMediaType()) { | ||||
|                 case MEDIA_TYPE_IMAGE: | ||||
|                 case MEDIA_TYPE_VIDEO: { | ||||
|                     final String url = getUrlOfType(media); | ||||
|                     final File file = getDownloadSaveFile(downloadDir, media.getCode(), url); | ||||
|                     map.put(url, file.getAbsolutePath()); | ||||
|                     final Pair<List<String>, String> pair = getDownloadSavePaths(userFolderPaths, media.getCode(), url); | ||||
|                     final DocumentFile file = createFile(pair); | ||||
|                     if (file == null) continue; | ||||
|                     map.put(url, file); | ||||
|                     break; | ||||
|                 } | ||||
|                 case MEDIA_TYPE_VOICE: { | ||||
| @ -335,28 +412,48 @@ public final class DownloadUtils { | ||||
|                     if (mediaUser != null) { | ||||
|                         fileName = mediaUser.getUsername() + "_" + fileName; | ||||
|                     } | ||||
|                     final File file = getDownloadSaveFile(downloadDir, fileName, url); | ||||
|                     map.put(url, file.getAbsolutePath()); | ||||
|                     final Pair<List<String>, String> pair = getDownloadSavePaths(userFolderPaths, fileName, url); | ||||
|                     final DocumentFile file = createFile(pair); | ||||
|                     if (file == null) continue; | ||||
|                     map.put(url, file); | ||||
|                     break; | ||||
|                 } | ||||
|                 case MEDIA_TYPE_SLIDER: | ||||
|                     final List<Media> sliderItems = media.getCarouselMedia(); | ||||
|                     for (int i = 0; i < sliderItems.size(); i++) { | ||||
|                         if (childPositionIfSingle >= 0 && feedModels.size() == 1 && i != childPositionIfSingle) { | ||||
|                             continue; | ||||
|                         } | ||||
|                         if (childPositionIfSingle >= 0 && feedModels.size() == 1 && i != childPositionIfSingle) continue; | ||||
|                         final Media child = sliderItems.get(i); | ||||
|                         final String url = getUrlOfType(child); | ||||
|                         final File file = getDownloadChildSaveFile(downloadDir, media.getCode(), i + 1, url); | ||||
|                         map.put(url, file.getAbsolutePath()); | ||||
|                         final Pair<List<String>, String> pair = getDownloadChildSaveFile(userFolderPaths, media.getCode(), i + 1, url); | ||||
|                         final DocumentFile file = createFile(pair); | ||||
|                         if (file == null) continue; | ||||
|                         map.put(url, file); | ||||
|                     } | ||||
|                     break; | ||||
|                 default: | ||||
|             } | ||||
|         } | ||||
|         if (map.isEmpty()) return; | ||||
|         download(context, map); | ||||
|     } | ||||
| 
 | ||||
|     private static DocumentFile createFile(@NonNull final Pair<List<String>, String> pair) { | ||||
|         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 | ||||
|     private static String getUrlOfType(@NonNull final Media media) { | ||||
|         switch (media.getMediaType()) { | ||||
| @ -388,12 +485,13 @@ public final class DownloadUtils { | ||||
| 
 | ||||
|     public static void download(final Context context, | ||||
|                                 final String url, | ||||
|                                 final String filePath) { | ||||
|                                 final DocumentFile filePath) { | ||||
|         if (context == null || url == null || filePath == null) return; | ||||
|         download(context, Collections.singletonMap(url, filePath)); | ||||
|     } | ||||
| 
 | ||||
|     private static void download(final Context context, final Map<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() | ||||
|                 .setRequiredNetworkType(NetworkType.CONNECTED) | ||||
|                 .build(); | ||||
| @ -401,19 +499,25 @@ public final class DownloadUtils { | ||||
|                                                                                      .setUrlToFilePathMap(urlFilePathMap) | ||||
|                                                                                      .build(); | ||||
|         final String requestJson = new Gson().toJson(request); | ||||
|         final File tempFile = getTempFile(); | ||||
|         try (BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile))) { | ||||
|         final DocumentFile tempFile = getTempFile(null, "json"); | ||||
|         if (tempFile == null) { | ||||
|             Log.e(TAG, "download: temp file is null"); | ||||
|             return; | ||||
|         } | ||||
|         final Uri uri = tempFile.getUri(); | ||||
|         final ContentResolver contentResolver = context.getContentResolver(); | ||||
|         if (contentResolver == null) return; | ||||
|         try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(contentResolver.openOutputStream(uri)))) { | ||||
|             writer.write(requestJson); | ||||
|         } catch (IOException e) { | ||||
|             Log.e(TAG, "download: Error writing request to file", e); | ||||
|             //noinspection ResultOfMethodCallIgnored | ||||
|             tempFile.delete(); | ||||
|             return; | ||||
|         } | ||||
|         final WorkRequest downloadWorkRequest = new OneTimeWorkRequest.Builder(DownloadWorker.class) | ||||
|                 .setInputData( | ||||
|                         new Data.Builder() | ||||
|                                 .putString(DownloadWorker.KEY_DOWNLOAD_REQUEST_JSON, tempFile.getAbsolutePath()) | ||||
|                                 .putString(DownloadWorker.KEY_DOWNLOAD_REQUEST_JSON, tempFile.getUri().toString()) | ||||
|                                 .build() | ||||
|                 ) | ||||
|                 .setConstraints(constraints) | ||||
|  | ||||
| @ -6,11 +6,10 @@ import android.net.Uri; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.documentfile.provider.DocumentFile; | ||||
| 
 | ||||
| import org.json.JSONObject; | ||||
| 
 | ||||
| import java.io.File; | ||||
| import java.io.FileInputStream; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.util.Map; | ||||
| @ -45,7 +44,7 @@ public final class MediaUploader { | ||||
|                     listener.onFailure(new RuntimeException("Bitmap result was null")); | ||||
|                     return; | ||||
|                 } | ||||
|                 uploadPhoto(bitmap, listener); | ||||
|                 uploadPhoto(contentResolver, bitmap, listener); | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
| @ -55,13 +54,14 @@ public final class MediaUploader { | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     private static void uploadPhoto(@NonNull final Bitmap bitmap, | ||||
|     private static void uploadPhoto(@NonNull final ContentResolver contentResolver, | ||||
|                                     @NonNull final Bitmap bitmap, | ||||
|                                     @NonNull final OnMediaUploadCompleteListener listener) { | ||||
|         appExecutors.tasksThread().submit(() -> { | ||||
|             final File file; | ||||
|             final DocumentFile file; | ||||
|             final long byteLength; | ||||
|             try { | ||||
|                 file = BitmapUtils.convertToJpegAndSaveToFile(bitmap, null); | ||||
|                 file = BitmapUtils.convertToJpegAndSaveToFile(contentResolver, bitmap, null); | ||||
|                 byteLength = file.length(); | ||||
|             } catch (Exception e) { | ||||
|                 listener.onFailure(e); | ||||
| @ -71,12 +71,11 @@ public final class MediaUploader { | ||||
|             final Map<String, String> headers = MediaUploadHelper.getUploadPhotoHeaders(options); | ||||
|             final String url = HOST + "/rupload_igphoto/" + options.getName() + "/"; | ||||
|             appExecutors.networkIO().execute(() -> { | ||||
|                 try (FileInputStream input = new FileInputStream(file)) { | ||||
|                 try (InputStream input = contentResolver.openInputStream(file.getUri())) { | ||||
|                     upload(input, url, headers, listener); | ||||
|                 } catch (IOException e) { | ||||
|                     listener.onFailure(e); | ||||
|                 } finally { | ||||
|                     //noinspection ResultOfMethodCallIgnored | ||||
|                     file.delete(); | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
| @ -15,8 +15,11 @@ import android.media.MediaScannerConnection; | ||||
| import android.media.MediaScannerConnection.OnScanCompletedListener; | ||||
| import android.net.Uri; | ||||
| import android.os.Build; | ||||
| import android.os.Environment; | ||||
| import android.os.Handler; | ||||
| import android.os.storage.StorageManager; | ||||
| import android.provider.Browser; | ||||
| import android.provider.DocumentsContract; | ||||
| import android.util.DisplayMetrics; | ||||
| import android.util.Log; | ||||
| import android.util.Pair; | ||||
| @ -35,6 +38,7 @@ import androidx.annotation.Nullable; | ||||
| import androidx.appcompat.app.ActionBar; | ||||
| import androidx.appcompat.app.AppCompatActivity; | ||||
| import androidx.core.content.ContextCompat; | ||||
| import androidx.documentfile.provider.DocumentFile; | ||||
| import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat; | ||||
| 
 | ||||
| import com.google.android.exoplayer2.database.ExoDatabaseProvider; | ||||
| @ -46,6 +50,8 @@ import org.json.JSONObject; | ||||
| 
 | ||||
| import java.io.File; | ||||
| import java.lang.reflect.Field; | ||||
| import java.lang.reflect.InvocationTargetException; | ||||
| import java.lang.reflect.Method; | ||||
| import java.text.SimpleDateFormat; | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
| @ -71,6 +77,7 @@ public final class Utils { | ||||
|     public static Handler applicationHandler; | ||||
|     public static String cacheDir; | ||||
|     private static int defaultStatusBarColor; | ||||
|     private static Object[] volumes; | ||||
| 
 | ||||
|     public static int convertDpToPx(final float dp) { | ||||
|         return Math.round((dp * displayMetrics.densityDpi) / DisplayMetrics.DENSITY_DEFAULT); | ||||
| @ -367,4 +374,51 @@ public final class Utils { | ||||
|         if (window == null) return; | ||||
|         window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); | ||||
|     } | ||||
| 
 | ||||
|     public static void scanDocumentFile(@NonNull final Context context, | ||||
|                                         @NonNull final DocumentFile documentFile, | ||||
|                                         @NonNull final OnScanCompletedListener callback) { | ||||
|         if (!documentFile.isFile()) 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); | ||||
|     } | ||||
| 
 | ||||
|     private static File getDocumentFileRealPath(Context context, 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 (volumes == null) { | ||||
|                 StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE); | ||||
|                 Method getVolumeListMethod = sm.getClass().getMethod("getVolumeList"); | ||||
|                 volumes = (Object[]) getVolumeListMethod.invoke(sm); | ||||
|             } | ||||
| 
 | ||||
|             for (Object volume : volumes) { | ||||
|                 Method getUuidMethod = volume.getClass().getMethod("getUuid"); | ||||
|                 String uuid = (String) getUuidMethod.invoke(volume); | ||||
| 
 | ||||
|                 if (uuid != null && uuid.equalsIgnoreCase(type)) { | ||||
|                     Method getPathMethod = volume.getClass().getMethod("getPath"); | ||||
|                     String path = (String) getPathMethod.invoke(volume); | ||||
|                     return new File(path, split[1]); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return null; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,14 +1,16 @@ | ||||
| package awais.instagrabber.utils; | ||||
| 
 | ||||
| import android.app.Application; | ||||
| import android.media.MediaRecorder; | ||||
| import android.os.Handler; | ||||
| import android.os.Message; | ||||
| import android.os.ParcelFileDescriptor; | ||||
| import android.util.Log; | ||||
| import android.webkit.MimeTypeMap; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.documentfile.provider.DocumentFile; | ||||
| 
 | ||||
| import java.io.File; | ||||
| import java.text.SimpleDateFormat; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Date; | ||||
| @ -27,20 +29,20 @@ public class VoiceRecorder { | ||||
|     private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat(FILE_FORMAT, Locale.US); | ||||
| 
 | ||||
|     private final List<Float> waveform = new ArrayList<>(); | ||||
|     private final File recordingsDir; | ||||
|     private final DocumentFile recordingsDir; | ||||
|     private final VoiceRecorderCallback callback; | ||||
| 
 | ||||
|     private MediaRecorder recorder; | ||||
|     private File audioTempFile; | ||||
|     private DocumentFile audioTempFile; | ||||
|     private MaxAmpHandler maxAmpHandler; | ||||
|     private boolean stopped; | ||||
| 
 | ||||
|     public VoiceRecorder(@NonNull final File recordingsDir, final VoiceRecorderCallback callback) { | ||||
|     public VoiceRecorder(@NonNull final DocumentFile recordingsDir, final VoiceRecorderCallback callback) { | ||||
|         this.recordingsDir = recordingsDir; | ||||
|         this.callback = callback; | ||||
|     } | ||||
| 
 | ||||
|     public void startRecording() { | ||||
|     public void startRecording(final Application application) { | ||||
|         stopped = false; | ||||
|         try { | ||||
|             recorder = new MediaRecorder(); | ||||
| @ -48,7 +50,8 @@ public class VoiceRecorder { | ||||
|             recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); | ||||
|             deleteTempAudioFile(); | ||||
|             audioTempFile = getAudioRecordFile(); | ||||
|             recorder.setOutputFile(audioTempFile.getAbsolutePath()); | ||||
|             final ParcelFileDescriptor parcelFileDescriptor = application.getContentResolver().openFileDescriptor(audioTempFile.getUri(), "rwt"); | ||||
|             recorder.setOutputFile(parcelFileDescriptor.getFileDescriptor()); | ||||
|             recorder.setAudioEncoder(MediaRecorder.AudioEncoder.HE_AAC); | ||||
|             recorder.setAudioEncodingBitRate(AUDIO_BIT_RATE); | ||||
|             recorder.setAudioSamplingRate(AUDIO_SAMPLE_RATE); | ||||
| @ -140,9 +143,9 @@ public class VoiceRecorder { | ||||
|     // } | ||||
| 
 | ||||
|     @NonNull | ||||
|     private File getAudioRecordFile() { | ||||
|     private DocumentFile getAudioRecordFile() { | ||||
|         final String name = String.format("%s-%s.%s", FILE_PREFIX, SIMPLE_DATE_FORMAT.format(new Date()), EXTENSION); | ||||
|         return new File(recordingsDir, name); | ||||
|         return recordingsDir.createFile(MIME_TYPE, name); | ||||
|     } | ||||
| 
 | ||||
|     private void deleteTempAudioFile() { | ||||
| @ -160,11 +163,11 @@ public class VoiceRecorder { | ||||
| 
 | ||||
|     public static class VoiceRecordingResult { | ||||
|         private final String mimeType; | ||||
|         private final File file; | ||||
|         private final DocumentFile file; | ||||
|         private final List<Float> waveform; | ||||
|         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.file = file; | ||||
|             this.waveform = waveform; | ||||
| @ -174,7 +177,7 @@ public class VoiceRecorder { | ||||
|             return mimeType; | ||||
|         } | ||||
| 
 | ||||
|         public File getFile() { | ||||
|         public DocumentFile getFile() { | ||||
|             return file; | ||||
|         } | ||||
| 
 | ||||
|  | ||||
| @ -2,18 +2,17 @@ package awais.instagrabber.viewmodels; | ||||
| 
 | ||||
| import android.app.Application; | ||||
| import android.content.ContentResolver; | ||||
| import android.media.MediaScannerConnection; | ||||
| import android.net.Uri; | ||||
| import android.util.Log; | ||||
| 
 | ||||
| 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 androidx.lifecycle.Transformations; | ||||
| 
 | ||||
| import java.io.File; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.Objects; | ||||
| @ -38,6 +37,7 @@ import awais.instagrabber.utils.DownloadUtils; | ||||
| import awais.instagrabber.utils.MediaController; | ||||
| import awais.instagrabber.utils.MediaUtils; | ||||
| import awais.instagrabber.utils.TextUtils; | ||||
| import awais.instagrabber.utils.Utils; | ||||
| import awais.instagrabber.utils.VoiceRecorder; | ||||
| 
 | ||||
| import static awais.instagrabber.utils.Utils.settingsHelper; | ||||
| @ -47,7 +47,7 @@ public class DirectThreadViewModel extends AndroidViewModel { | ||||
|     // private static final String ERROR_INVALID_THREAD = "Invalid thread"; | ||||
| 
 | ||||
|     private final ContentResolver contentResolver; | ||||
|     private final File recordingsDir; | ||||
|     private final DocumentFile recordingsDir; | ||||
|     private final Application application; | ||||
|     private final long viewerId; | ||||
|     private final String threadId; | ||||
| @ -166,37 +166,32 @@ public class DirectThreadViewModel extends AndroidViewModel { | ||||
|             @Override | ||||
|             public void onComplete(final VoiceRecorder.VoiceRecordingResult result) { | ||||
|                 Log.d(TAG, "onComplete: recording complete. Scanning file..."); | ||||
|                 MediaScannerConnection.scanFile( | ||||
|                         application, | ||||
|                         new String[]{result.getFile().getAbsolutePath()}, | ||||
|                         new String[]{result.getMimeType()}, | ||||
|                         (path, uri) -> { | ||||
|                             if (uri == null) { | ||||
|                                 final String msg = "Scan failed!"; | ||||
|                                 Log.e(TAG, msg); | ||||
|                                 data.postValue(Resource.error(msg, null)); | ||||
|                                 return; | ||||
|                             } | ||||
|                             Log.d(TAG, "onComplete: scan complete"); | ||||
|                             MediaUtils.getVoiceInfo(contentResolver, uri, new MediaUtils.OnInfoLoadListener<MediaUtils.VideoInfo>() { | ||||
|                                 @Override | ||||
|                                 public void onLoad(@Nullable final MediaUtils.VideoInfo videoInfo) { | ||||
|                                     if (videoInfo == null) return; | ||||
|                                     threadManager.sendVoice(data, | ||||
|                                                             uri, | ||||
|                                                             result.getWaveform(), | ||||
|                                                             result.getSamplingFreq(), | ||||
|                                                             videoInfo == null ? 0 : videoInfo.duration, | ||||
|                                                             videoInfo == null ? 0 : videoInfo.size); | ||||
|                                 } | ||||
| 
 | ||||
|                                 @Override | ||||
|                                 public void onFailure(final Throwable t) { | ||||
|                                     data.postValue(Resource.error(t.getMessage(), null)); | ||||
|                                 } | ||||
|                             }); | ||||
|                 Utils.scanDocumentFile(application, result.getFile(), (path, uri) -> { | ||||
|                     if (uri == null) { | ||||
|                         final String msg = "Scan failed!"; | ||||
|                         Log.e(TAG, msg); | ||||
|                         data.postValue(Resource.error(msg, null)); | ||||
|                         return; | ||||
|                     } | ||||
|                     Log.d(TAG, "onComplete: scan complete"); | ||||
|                     MediaUtils.getVoiceInfo(contentResolver, uri, new MediaUtils.OnInfoLoadListener<MediaUtils.VideoInfo>() { | ||||
|                         @Override | ||||
|                         public void onLoad(@Nullable final MediaUtils.VideoInfo videoInfo) { | ||||
|                             if (videoInfo == null) return; | ||||
|                             threadManager.sendVoice(data, | ||||
|                                                     uri, | ||||
|                                                     result.getWaveform(), | ||||
|                                                     result.getSamplingFreq(), | ||||
|                                                     videoInfo == null ? 0 : videoInfo.duration, | ||||
|                                                     videoInfo == null ? 0 : videoInfo.size); | ||||
|                         } | ||||
|                 ); | ||||
| 
 | ||||
|                         @Override | ||||
|                         public void onFailure(final Throwable t) { | ||||
|                             data.postValue(Resource.error(t.getMessage(), null)); | ||||
|                         } | ||||
|                     }); | ||||
|                 }); | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
| @ -204,7 +199,7 @@ public class DirectThreadViewModel extends AndroidViewModel { | ||||
| 
 | ||||
|             } | ||||
|         }); | ||||
|         voiceRecorder.startRecording(); | ||||
|         voiceRecorder.startRecording(application); | ||||
|         return data; | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -5,6 +5,7 @@ import android.graphics.RectF; | ||||
| import android.net.Uri; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.documentfile.provider.DocumentFile; | ||||
| import androidx.lifecycle.AndroidViewModel; | ||||
| import androidx.lifecycle.LiveData; | ||||
| import androidx.lifecycle.MutableLiveData; | ||||
| @ -26,6 +27,7 @@ import awais.instagrabber.models.SavedImageEditState; | ||||
| import awais.instagrabber.utils.AppExecutors; | ||||
| import awais.instagrabber.utils.DownloadUtils; | ||||
| import awais.instagrabber.utils.SerializablePair; | ||||
| import awais.instagrabber.utils.Utils; | ||||
| import jp.co.cyberagent.android.gpuimage.GPUImage; | ||||
| import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter; | ||||
| import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilterGroup; | ||||
| @ -35,6 +37,7 @@ public class ImageEditViewModel extends AndroidViewModel { | ||||
|     private static final String RESULT = "result"; | ||||
|     private static final String FILE_FORMAT = "yyyyMMddHHmmssSSS"; | ||||
|     private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat(FILE_FORMAT, Locale.US); | ||||
|     private static final String MIME_TYPE = Utils.mimeTypeMap.getMimeTypeFromExtension("jpg"); | ||||
| 
 | ||||
|     private Uri originalUri; | ||||
|     private SavedImageEditState savedImageEditState; | ||||
| @ -48,18 +51,18 @@ public class ImageEditViewModel extends AndroidViewModel { | ||||
|     private final MutableLiveData<Boolean> isCropped = new MutableLiveData<>(false); | ||||
|     private final MutableLiveData<Boolean> isTuned = 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 Filter<? extends GPUImageFilter> appliedFilter; | ||||
|     private final File destinationFile; | ||||
|     private final DocumentFile destinationFile; | ||||
| 
 | ||||
|     public ImageEditViewModel(final Application application) { | ||||
|         super(application); | ||||
|         sessionId = SIMPLE_DATE_FORMAT.format(new Date()); | ||||
|         outputDir = DownloadUtils.getImageEditDir(sessionId); | ||||
|         destinationFile = new File(outputDir, RESULT + ".jpg"); | ||||
|         destinationUri = Uri.fromFile(destinationFile); | ||||
|         cropDestinationUri = Uri.fromFile(new File(outputDir, CROP + ".jpg")); | ||||
|         destinationFile = outputDir.createFile(MIME_TYPE, RESULT + ".jpg"); | ||||
|         destinationUri = destinationFile.getUri(); | ||||
|         cropDestinationUri = outputDir.createFile(MIME_TYPE, CROP + ".jpg").getUri(); | ||||
|     } | ||||
| 
 | ||||
|     public String getSessionId() { | ||||
| @ -159,16 +162,15 @@ public class ImageEditViewModel extends AndroidViewModel { | ||||
|         delete(outputDir); | ||||
|     } | ||||
| 
 | ||||
|     private void delete(@NonNull final File file) { | ||||
|     private void delete(@NonNull final DocumentFile file) { | ||||
|         if (file.isDirectory()) { | ||||
|             final File[] files = file.listFiles(); | ||||
|             final DocumentFile[] files = file.listFiles(); | ||||
|             if (files != null) { | ||||
|                 for (File f : files) { | ||||
|                 for (DocumentFile f : files) { | ||||
|                     delete(f); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         //noinspection ResultOfMethodCallIgnored | ||||
|         file.delete(); | ||||
|     } | ||||
| 
 | ||||
| @ -206,9 +208,9 @@ public class ImageEditViewModel extends AndroidViewModel { | ||||
|         return new SerializablePair<>(type, propertyValueMap); | ||||
|     } | ||||
| 
 | ||||
|     public File getDestinationFile() { | ||||
|         return destinationFile; | ||||
|     } | ||||
|     // public File getDestinationFile() { | ||||
|     //     return destinationFile; | ||||
|     // } | ||||
| 
 | ||||
|     public enum Tab { | ||||
|         RESULT, | ||||
|  | ||||
| @ -8,7 +8,6 @@ import android.content.Intent; | ||||
| import android.graphics.Bitmap; | ||||
| import android.graphics.BitmapFactory; | ||||
| import android.media.MediaMetadataRetriever; | ||||
| import android.media.MediaScannerConnection; | ||||
| import android.net.Uri; | ||||
| import android.os.Build; | ||||
| import android.os.Handler; | ||||
| @ -18,7 +17,7 @@ import android.util.Log; | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.core.app.NotificationCompat; | ||||
| import androidx.core.app.NotificationManagerCompat; | ||||
| import androidx.core.content.FileProvider; | ||||
| import androidx.documentfile.provider.DocumentFile; | ||||
| import androidx.work.Data; | ||||
| import androidx.work.ForegroundInfo; | ||||
| import androidx.work.Worker; | ||||
| @ -30,10 +29,8 @@ import com.google.gson.JsonSyntaxException; | ||||
| import org.apache.commons.imaging.formats.jpeg.iptc.JpegIptcRewriter; | ||||
| 
 | ||||
| import java.io.BufferedInputStream; | ||||
| import java.io.File; | ||||
| import java.io.FileInputStream; | ||||
| import java.io.FileOutputStream; | ||||
| import java.io.InputStream; | ||||
| import java.io.OutputStream; | ||||
| import java.net.URL; | ||||
| import java.net.URLConnection; | ||||
| import java.util.Collection; | ||||
| @ -44,6 +41,7 @@ import java.util.Map; | ||||
| import java.util.Scanner; | ||||
| import java.util.Set; | ||||
| import java.util.concurrent.ExecutionException; | ||||
| import java.util.stream.Collectors; | ||||
| 
 | ||||
| import awais.instagrabber.BuildConfig; | ||||
| import awais.instagrabber.R; | ||||
| @ -52,10 +50,11 @@ import awais.instagrabber.utils.Constants; | ||||
| import awais.instagrabber.utils.DownloadUtils; | ||||
| import awais.instagrabber.utils.TextUtils; | ||||
| import awais.instagrabber.utils.Utils; | ||||
| //import awaisomereport.LogCollector; | ||||
| 
 | ||||
| import static awais.instagrabber.utils.Constants.DOWNLOAD_CHANNEL_ID; | ||||
| import static awais.instagrabber.utils.Constants.NOTIF_GROUP_NAME; | ||||
| 
 | ||||
| //import awaisomereport.LogCollector; | ||||
| //import static awais.instagrabber.utils.Utils.logCollector; | ||||
| 
 | ||||
| public class DownloadWorker extends Worker { | ||||
| @ -85,8 +84,21 @@ public class DownloadWorker extends Worker { | ||||
|                                           .build()); | ||||
|         } | ||||
|         final String downloadRequestString; | ||||
|         final File requestFile = new File(downloadRequestFilePath); | ||||
|         try (Scanner scanner = new Scanner(requestFile)) { | ||||
|         // final File requestFile = new File(downloadRequestFilePath); | ||||
|         final Uri requestFile = Uri.parse(downloadRequestFilePath); | ||||
|         if (requestFile == null) { | ||||
|             return Result.failure(new Data.Builder() | ||||
|                                           .putString("error", "requestFile is null") | ||||
|                                           .build()); | ||||
|         } | ||||
|         final Context context = getApplicationContext(); | ||||
|         final ContentResolver contentResolver = context.getContentResolver(); | ||||
|         if (contentResolver == null) { | ||||
|             return Result.failure(new Data.Builder() | ||||
|                                           .putString("error", "contentResolver is null") | ||||
|                                           .build()); | ||||
|         } | ||||
|         try (Scanner scanner = new Scanner(contentResolver.openInputStream(requestFile))) { | ||||
|             downloadRequestString = scanner.useDelimiter("\\A").next(); | ||||
|         } catch (Exception e) { | ||||
|             Log.e(TAG, "doWork: ", e); | ||||
| @ -116,7 +128,7 @@ public class DownloadWorker extends Worker { | ||||
|         final Map<String, String> urlToFilePathMap = downloadRequest.getUrlToFilePathMap(); | ||||
|         download(urlToFilePathMap); | ||||
|         new Handler(Looper.getMainLooper()).postDelayed(() -> showSummary(urlToFilePathMap), 500); | ||||
|         final boolean deleted = requestFile.delete(); | ||||
|         final boolean deleted = DocumentFile.fromSingleUri(context, requestFile).delete(); | ||||
|         if (!deleted) { | ||||
|             Log.w(TAG, "doWork: requestFile not deleted!"); | ||||
|         } | ||||
| @ -131,7 +143,9 @@ public class DownloadWorker extends Worker { | ||||
|         for (final Map.Entry<String, String> urlAndFilePath : entries) { | ||||
|             final String url = urlAndFilePath.getKey(); | ||||
|             updateDownloadProgress(notificationId, count, total, 0); | ||||
|             download(notificationId, count, total, url, urlAndFilePath.getValue()); | ||||
|             final String uriString = urlAndFilePath.getValue(); | ||||
|             final DocumentFile file = DocumentFile.fromSingleUri(getApplicationContext(), Uri.parse(uriString)); | ||||
|             download(notificationId, count, total, url, file); | ||||
|             count++; | ||||
|         } | ||||
|     } | ||||
| @ -144,17 +158,24 @@ public class DownloadWorker extends Worker { | ||||
|                           final int position, | ||||
|                           final int total, | ||||
|                           final String url, | ||||
|                           final String filePath) { | ||||
|         final boolean isJpg = filePath.endsWith("jpg"); | ||||
|                           final DocumentFile filePath) { | ||||
|         final Context context = getApplicationContext(); | ||||
|         if (context == null) return; | ||||
|         final ContentResolver contentResolver = context.getContentResolver(); | ||||
|         if (contentResolver == null) return; | ||||
|         final String filePathType = filePath.getType(); | ||||
|         if (filePathType == null) return; | ||||
|         // final String extension = Utils.mimeTypeMap.getExtensionFromMimeType(filePathType); | ||||
|         final boolean isJpg = filePathType.startsWith("image"); // extension.endsWith("jpg"); | ||||
|         // using temp file approach to remove IPTC so that download progress can be reported | ||||
|         final File outFile = isJpg ? DownloadUtils.getTempFile() : new File(filePath); | ||||
|         final DocumentFile outFile = isJpg ? DownloadUtils.getTempFile(null, "jpg") : filePath; | ||||
|         try { | ||||
|             final URLConnection urlConnection = new URL(url).openConnection(); | ||||
|             final long fileSize = Build.VERSION.SDK_INT >= 24 ? urlConnection.getContentLengthLong() : | ||||
|                                   urlConnection.getContentLength(); | ||||
|             float totalRead = 0; | ||||
|             try (final BufferedInputStream bis = new BufferedInputStream(urlConnection.getInputStream()); | ||||
|                  final FileOutputStream fos = new FileOutputStream(outFile)) { | ||||
|                  final OutputStream fos = contentResolver.openOutputStream(outFile.getUri())) { | ||||
|                 final byte[] buffer = new byte[0x2000]; | ||||
|                 int count; | ||||
|                 while ((count = bis.read(buffer, 0, 0x2000)) != -1) { | ||||
| @ -167,18 +188,17 @@ public class DownloadWorker extends Worker { | ||||
|                 } | ||||
|                 fos.flush(); | ||||
|             } catch (final Exception e) { | ||||
|                 Log.e(TAG, "Error while writing data from url: " + url + " to file: " + outFile.getAbsolutePath(), e); | ||||
|                 Log.e(TAG, "Error while writing data from url: " + url + " to file: " + outFile.getName(), e); | ||||
|             } | ||||
|             if (isJpg) { | ||||
|                 final File finalFile = new File(filePath); | ||||
|                 try (FileInputStream fis = new FileInputStream(outFile); | ||||
|                      FileOutputStream fos = new FileOutputStream(finalFile)) { | ||||
|                 try (final InputStream fis = contentResolver.openInputStream(outFile.getUri()); | ||||
|                      final OutputStream fos = contentResolver.openOutputStream(filePath.getUri())) { | ||||
|                     final JpegIptcRewriter jpegIptcRewriter = new JpegIptcRewriter(); | ||||
|                     jpegIptcRewriter.removeIPTC(fis, fos); | ||||
|                 } catch (Exception e) { | ||||
|                     Log.e(TAG, "Error while removing iptc: url: " + url | ||||
|                             + ", tempFile: " + outFile.getAbsolutePath() | ||||
|                             + ", finalFile: " + finalFile.getAbsolutePath(), e); | ||||
|                             + ", tempFile: " + outFile | ||||
|                             + ", finalFile: " + filePath, e); | ||||
|                 } | ||||
|                 final boolean deleted = outFile.delete(); | ||||
|                 if (!deleted) { | ||||
| @ -243,57 +263,56 @@ public class DownloadWorker extends Worker { | ||||
| 
 | ||||
|     private void showSummary(final Map<String, String> urlToFilePathMap) { | ||||
|         final Context context = getApplicationContext(); | ||||
|         final Collection<String> filePaths = urlToFilePathMap.values(); | ||||
|         final Collection<DocumentFile> filePaths = urlToFilePathMap.values() | ||||
|                                                                    .stream() | ||||
|                                                                    .map(s -> DocumentFile.fromSingleUri(context, Uri.parse(s))) | ||||
|                                                                    .collect(Collectors.toList()); | ||||
|         final List<NotificationCompat.Builder> notifications = new LinkedList<>(); | ||||
|         final List<Integer> notificationIds = new LinkedList<>(); | ||||
|         int count = 1; | ||||
|         for (final String filePath : filePaths) { | ||||
|             final File file = new File(filePath); | ||||
|             context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file))); | ||||
|             MediaScannerConnection.scanFile(context, new String[]{file.getAbsolutePath()}, null, null); | ||||
|             final Uri uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file); | ||||
|         for (final DocumentFile filePath : 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); | ||||
|             final ContentResolver contentResolver = context.getContentResolver(); | ||||
|             Bitmap bitmap = null; | ||||
|             final String mimeType = Utils.getMimeType(uri, contentResolver); | ||||
|             final String mimeType = filePath.getType(); // Utils.getMimeType(uri, contentResolver); | ||||
|             if (!TextUtils.isEmpty(mimeType)) { | ||||
|                 if (mimeType.startsWith("image")) { | ||||
|                     try (final InputStream inputStream = contentResolver.openInputStream(uri)) { | ||||
|                     try (final InputStream inputStream = contentResolver.openInputStream(filePath.getUri())) { | ||||
|                         bitmap = BitmapFactory.decodeStream(inputStream); | ||||
|                     } catch (final Exception e) { | ||||
| //                        if (logCollector != null) | ||||
| //                            logCollector.appendException(e, LogCollector.LogFile.ASYNC_DOWNLOADER, "onPostExecute::bitmap_1"); | ||||
|                         if (BuildConfig.DEBUG) Log.e(TAG, "", e); | ||||
|                     } | ||||
|                 } else if (mimeType.startsWith("video")) { | ||||
|                     final MediaMetadataRetriever retriever = new MediaMetadataRetriever(); | ||||
|                     try { | ||||
|                         try { | ||||
|                             retriever.setDataSource(context, uri); | ||||
|                             retriever.setDataSource(context, filePath.getUri()); | ||||
|                         } catch (final Exception e) { | ||||
|                             retriever.setDataSource(file.getAbsolutePath()); | ||||
|                             // retriever.setDataSource(file.getAbsolutePath()); | ||||
|                             Log.e(TAG, "showSummary: ", e); | ||||
|                         } | ||||
|                         bitmap = retriever.getFrameAtTime(); | ||||
|                         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) | ||||
|                             try { | ||||
|                                 retriever.close(); | ||||
|                             } catch (final Exception e) { | ||||
| //                                if (logCollector != null) | ||||
| //                                    logCollector.appendException(e, LogCollector.LogFile.ASYNC_DOWNLOADER, "onPostExecute::bitmap_2"); | ||||
|                                 Log.e(TAG, "showSummary: ", e); | ||||
|                             } | ||||
|                     } catch (final Exception e) { | ||||
|                         if (BuildConfig.DEBUG) Log.e(TAG, "", e); | ||||
| //                        if (logCollector != null) | ||||
| //                            logCollector.appendException(e, LogCollector.LogFile.ASYNC_DOWNLOADER, "onPostExecute::bitmap_3"); | ||||
|                         Log.e(TAG, "", e); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             final String downloadComplete = context.getString(R.string.downloader_complete); | ||||
|             final Intent intent = new Intent(Intent.ACTION_VIEW, uri) | ||||
|             final Intent intent = new Intent(Intent.ACTION_VIEW, filePath.getUri()) | ||||
|                     .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | ||||
|                                       | Intent.FLAG_FROM_BACKGROUND | ||||
|                                       | Intent.FLAG_GRANT_READ_URI_PERMISSION | ||||
|                                       | Intent.FLAG_GRANT_WRITE_URI_PERMISSION) | ||||
|                     .putExtra(Intent.EXTRA_STREAM, uri); | ||||
|                     .putExtra(Intent.EXTRA_STREAM, filePath.getUri()); | ||||
|             final PendingIntent pendingIntent = PendingIntent.getActivity( | ||||
|                     context, | ||||
|                     DOWNLOAD_NOTIFICATION_INTENT_REQUEST_CODE, | ||||
| @ -357,16 +376,22 @@ public class DownloadWorker extends Worker { | ||||
|         public static class Builder { | ||||
|             private Map<String, String> urlToFilePathMap; | ||||
| 
 | ||||
|             public Builder setUrlToFilePathMap(final Map<String, String> urlToFilePathMap) { | ||||
|                 this.urlToFilePathMap = urlToFilePathMap; | ||||
|             public Builder setUrlToFilePathMap(final Map<String, DocumentFile> urlToFilePathMap) { | ||||
|                 if (urlToFilePathMap == null) { | ||||
|                     return this; | ||||
|                 } | ||||
|                 this.urlToFilePathMap = urlToFilePathMap.entrySet() | ||||
|                                                         .stream() | ||||
|                                                         .collect(Collectors.toMap(Map.Entry::getKey, | ||||
|                                                                                   o -> o.getValue().getUri().toString())); | ||||
|                 return this; | ||||
|             } | ||||
| 
 | ||||
|             public Builder addUrl(@NonNull final String url, @NonNull final String filePath) { | ||||
|             public Builder addUrl(@NonNull final String url, @NonNull final DocumentFile filePath) { | ||||
|                 if (urlToFilePathMap == null) { | ||||
|                     urlToFilePathMap = new HashMap<>(); | ||||
|                 } | ||||
|                 urlToFilePathMap.put(url, filePath); | ||||
|                 urlToFilePathMap.put(url, filePath.getUri().toString()); | ||||
|                 return this; | ||||
|             } | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user