Browse Source
Merge pull request #934 from ammargitham/support-android-11
Merge pull request #934 from ammargitham/support-android-11
Migrate to SAF (Storage Access Framework)renovate/org.robolectric-robolectric-4.x
Austin Huang
3 years ago
committed by
GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 1500 additions and 610 deletions
-
2app/build.gradle
-
4app/src/main/AndroidManifest.xml
-
42app/src/main/java/awais/instagrabber/activities/CameraActivity.kt
-
115app/src/main/java/awais/instagrabber/activities/DirectorySelectActivity.java
-
13app/src/main/java/awais/instagrabber/activities/MainActivity.kt
-
9app/src/main/java/awais/instagrabber/dialogs/ConfirmDialogFragment.java
-
142app/src/main/java/awais/instagrabber/dialogs/CreateBackupDialogFragment.java
-
34app/src/main/java/awais/instagrabber/dialogs/ProfilePicDialogFragment.java
-
164app/src/main/java/awais/instagrabber/dialogs/RestoreBackupDialogFragment.java
-
30app/src/main/java/awais/instagrabber/fragments/CollectionPostsFragment.java
-
17app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java
-
29app/src/main/java/awais/instagrabber/fragments/SavedViewerFragment.java
-
30app/src/main/java/awais/instagrabber/fragments/TopicPostsFragment.java
-
14app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java
-
5app/src/main/java/awais/instagrabber/fragments/imageedit/ImageEditFragment.java
-
238app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java
-
1app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.kt
-
12app/src/main/java/awais/instagrabber/services/DeleteImageIntentService.java
-
7app/src/main/java/awais/instagrabber/utils/BitmapUtils.kt
-
1app/src/main/java/awais/instagrabber/utils/Constants.kt
-
24app/src/main/java/awais/instagrabber/utils/DirectoryChooser.java
-
40app/src/main/java/awais/instagrabber/utils/DirectoryUtils.java
-
428app/src/main/java/awais/instagrabber/utils/DownloadUtils.java
-
40app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java
-
12app/src/main/java/awais/instagrabber/utils/MediaUploader.kt
-
69app/src/main/java/awais/instagrabber/utils/MediaUtils.java
-
6app/src/main/java/awais/instagrabber/utils/PermissionUtils.java
-
3app/src/main/java/awais/instagrabber/utils/SettingsHelper.java
-
103app/src/main/java/awais/instagrabber/utils/Utils.java
-
37app/src/main/java/awais/instagrabber/utils/VoiceRecorder.java
-
39app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.kt
-
113app/src/main/java/awais/instagrabber/viewmodels/DirectorySelectActivityViewModel.java
-
30app/src/main/java/awais/instagrabber/viewmodels/ImageEditViewModel.java
-
157app/src/main/java/awais/instagrabber/workers/DownloadWorker.kt
-
86app/src/main/res/layout/activity_directory_select.xml
-
2app/src/main/res/layout/dialog_create_backup.xml
-
2app/src/main/res/layout/dialog_restore_backup.xml
-
10app/src/main/res/values/strings.xml
@ -0,0 +1,115 @@ |
|||
package awais.instagrabber.activities; |
|||
|
|||
import android.content.Intent; |
|||
import android.net.Uri; |
|||
import android.os.Build; |
|||
import android.os.Bundle; |
|||
import android.provider.DocumentsContract; |
|||
import android.util.Log; |
|||
import android.view.View; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
import androidx.lifecycle.ViewModelProvider; |
|||
|
|||
import java.io.IOException; |
|||
import java.io.PrintWriter; |
|||
import java.io.StringWriter; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.databinding.ActivityDirectorySelectBinding; |
|||
import awais.instagrabber.dialogs.ConfirmDialogFragment; |
|||
import awais.instagrabber.utils.AppExecutors; |
|||
import awais.instagrabber.viewmodels.DirectorySelectActivityViewModel; |
|||
|
|||
public class DirectorySelectActivity extends BaseLanguageActivity { |
|||
private static final String TAG = DirectorySelectActivity.class.getSimpleName(); |
|||
public static final int SELECT_DIR_REQUEST_CODE = 0x01; |
|||
private static final int ERROR_REQUEST_CODE = 0x02; |
|||
|
|||
private Uri initialUri; |
|||
private ActivityDirectorySelectBinding binding; |
|||
private DirectorySelectActivityViewModel viewModel; |
|||
|
|||
@Override |
|||
protected void onCreate(@Nullable final Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
binding = ActivityDirectorySelectBinding.inflate(getLayoutInflater()); |
|||
setContentView(binding.getRoot()); |
|||
viewModel = new ViewModelProvider(this).get(DirectorySelectActivityViewModel.class); |
|||
setupObservers(); |
|||
binding.selectDir.setOnClickListener(v -> openDirectoryChooser()); |
|||
AppExecutors.INSTANCE.getMainThread().execute(() -> viewModel.setInitialUri(getIntent())); |
|||
} |
|||
|
|||
private void setupObservers() { |
|||
viewModel.getMessage().observe(this, message -> binding.message.setText(message)); |
|||
viewModel.getPrevUri().observe(this, prevUri -> { |
|||
if (prevUri == null) { |
|||
binding.prevUri.setVisibility(View.GONE); |
|||
binding.message2.setVisibility(View.GONE); |
|||
return; |
|||
} |
|||
binding.prevUri.setText(prevUri); |
|||
binding.prevUri.setVisibility(View.VISIBLE); |
|||
binding.message2.setVisibility(View.VISIBLE); |
|||
}); |
|||
viewModel.getDirSuccess().observe(this, success -> binding.selectDir.setVisibility(success ? View.GONE : View.VISIBLE)); |
|||
viewModel.isLoading().observe(this, loading -> { |
|||
binding.message.setVisibility(loading ? View.GONE : View.VISIBLE); |
|||
binding.loadingIndicator.setVisibility(loading ? View.VISIBLE : View.GONE); |
|||
}); |
|||
} |
|||
|
|||
private void openDirectoryChooser() { |
|||
final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); |
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && initialUri != null) { |
|||
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, initialUri); |
|||
} |
|||
startActivityForResult(intent, SELECT_DIR_REQUEST_CODE); |
|||
} |
|||
|
|||
@Override |
|||
protected void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) { |
|||
super.onActivityResult(requestCode, resultCode, data); |
|||
if (requestCode != SELECT_DIR_REQUEST_CODE) return; |
|||
if (resultCode != RESULT_OK) { |
|||
showErrorDialog(getString(R.string.select_a_folder)); |
|||
return; |
|||
} |
|||
if (data == null || data.getData() == null) { |
|||
showErrorDialog(getString(R.string.select_a_folder)); |
|||
return; |
|||
} |
|||
AppExecutors.INSTANCE.getMainThread().execute(() -> { |
|||
try { |
|||
viewModel.setupSelectedDir(data); |
|||
final Intent intent = new Intent(this, MainActivity.class); |
|||
startActivity(intent); |
|||
finish(); |
|||
} catch (Exception e) { |
|||
// Should not come to this point. |
|||
// If it does, we have to show this error to the user so that they can report it. |
|||
try (final StringWriter sw = new StringWriter(); |
|||
final PrintWriter pw = new PrintWriter(sw)) { |
|||
e.printStackTrace(pw); |
|||
showErrorDialog("Please report this error to the developers:\n\n" + sw.toString()); |
|||
} catch (IOException ioException) { |
|||
Log.e(TAG, "onActivityResult: ", ioException); |
|||
} |
|||
} |
|||
}, 500); |
|||
} |
|||
|
|||
private void showErrorDialog(@NonNull final String message) { |
|||
final ConfirmDialogFragment dialogFragment = ConfirmDialogFragment.newInstance( |
|||
ERROR_REQUEST_CODE, |
|||
R.string.error, |
|||
message, |
|||
R.string.ok, |
|||
0, |
|||
0 |
|||
); |
|||
dialogFragment.show(getSupportFragmentManager(), ConfirmDialogFragment.class.getSimpleName()); |
|||
} |
|||
} |
@ -0,0 +1,113 @@ |
|||
package awais.instagrabber.viewmodels; |
|||
|
|||
import android.app.Application; |
|||
import android.content.Intent; |
|||
import android.content.UriPermission; |
|||
import android.net.Uri; |
|||
import android.os.Parcelable; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
import androidx.documentfile.provider.DocumentFile; |
|||
import androidx.lifecycle.AndroidViewModel; |
|||
import androidx.lifecycle.LiveData; |
|||
import androidx.lifecycle.MutableLiveData; |
|||
|
|||
import java.io.UnsupportedEncodingException; |
|||
import java.net.URLDecoder; |
|||
import java.nio.charset.StandardCharsets; |
|||
import java.util.List; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.DownloadUtils; |
|||
import awais.instagrabber.utils.TextUtils; |
|||
import awais.instagrabber.utils.Utils; |
|||
|
|||
import static awais.instagrabber.fragments.settings.PreferenceKeys.FOLDER_PATH; |
|||
|
|||
public class DirectorySelectActivityViewModel extends AndroidViewModel { |
|||
private static final String TAG = DirectorySelectActivityViewModel.class.getSimpleName(); |
|||
|
|||
private final MutableLiveData<String> message = new MutableLiveData<>(); |
|||
private final MutableLiveData<String> prevUri = new MutableLiveData<>(); |
|||
private final MutableLiveData<Boolean> loading = new MutableLiveData<>(false); |
|||
private final MutableLiveData<Boolean> dirSuccess = new MutableLiveData<>(false); |
|||
|
|||
public DirectorySelectActivityViewModel(final Application application) { |
|||
super(application); |
|||
} |
|||
|
|||
public LiveData<String> getMessage() { |
|||
return message; |
|||
} |
|||
|
|||
public LiveData<String> getPrevUri() { |
|||
return prevUri; |
|||
} |
|||
|
|||
public LiveData<Boolean> isLoading() { |
|||
return loading; |
|||
} |
|||
|
|||
public LiveData<Boolean> getDirSuccess() { |
|||
return dirSuccess; |
|||
} |
|||
|
|||
public void setInitialUri(final Intent intent) { |
|||
if (intent == null) { |
|||
setMessage(null); |
|||
return; |
|||
} |
|||
final Parcelable initialUriParcelable = intent.getParcelableExtra(Constants.EXTRA_INITIAL_URI); |
|||
if (!(initialUriParcelable instanceof Uri)) { |
|||
setMessage(null); |
|||
return; |
|||
} |
|||
setMessage((Uri) initialUriParcelable); |
|||
} |
|||
|
|||
private void setMessage(@Nullable final Uri initialUri) { |
|||
if (initialUri == null) { |
|||
final String prevVersionFolderPath = Utils.settingsHelper.getString(FOLDER_PATH); |
|||
if (TextUtils.isEmpty(prevVersionFolderPath)) { |
|||
// default message |
|||
message.postValue(getApplication().getString(R.string.dir_select_default_message)); |
|||
prevUri.postValue(null); |
|||
return; |
|||
} |
|||
message.postValue(getApplication().getString(R.string.dir_select_reselect_message)); |
|||
prevUri.postValue(prevVersionFolderPath); |
|||
return; |
|||
} |
|||
final List<UriPermission> existingPermissions = getApplication().getContentResolver().getPersistedUriPermissions(); |
|||
final boolean anyMatch = existingPermissions.stream().anyMatch(uriPermission -> uriPermission.getUri().equals(initialUri)); |
|||
final DocumentFile documentFile = DocumentFile.fromSingleUri(getApplication(), initialUri); |
|||
String path; |
|||
try { |
|||
path = URLDecoder.decode(initialUri.toString(), StandardCharsets.UTF_8.toString()); |
|||
} catch (UnsupportedEncodingException e) { |
|||
path = initialUri.toString(); |
|||
} |
|||
if (!anyMatch) { |
|||
message.postValue(getApplication().getString(R.string.dir_select_permission_revoked_message)); |
|||
prevUri.postValue(path); |
|||
return; |
|||
} |
|||
if (documentFile == null || !documentFile.exists() || documentFile.lastModified() == 0) { |
|||
message.postValue(getApplication().getString(R.string.dir_select_folder_not_exist)); |
|||
prevUri.postValue(path); |
|||
} |
|||
} |
|||
|
|||
public void setupSelectedDir(@NonNull final Intent data) throws DownloadUtils.ReselectDocumentTreeException { |
|||
loading.postValue(true); |
|||
try { |
|||
Utils.setupSelectedDir(getApplication(), data); |
|||
message.postValue(getApplication().getString(R.string.dir_select_success_message)); |
|||
dirSuccess.postValue(true); |
|||
} finally { |
|||
loading.postValue(false); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,86 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" |
|||
xmlns:app="http://schemas.android.com/apk/res-auto" |
|||
xmlns:tools="http://schemas.android.com/tools" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
android:padding="16dp"> |
|||
|
|||
<androidx.appcompat.widget.AppCompatTextView |
|||
android:id="@+id/title" |
|||
android:layout_width="0dp" |
|||
android:layout_height="wrap_content" |
|||
android:text="@string/app_name" |
|||
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline4" |
|||
app:layout_constraintEnd_toEndOf="parent" |
|||
app:layout_constraintStart_toStartOf="parent" |
|||
app:layout_constraintTop_toTopOf="parent" /> |
|||
|
|||
<androidx.appcompat.widget.AppCompatTextView |
|||
android:id="@+id/message" |
|||
android:layout_width="0dp" |
|||
android:layout_height="wrap_content" |
|||
android:layout_marginBottom="8dp" |
|||
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6" |
|||
app:layout_constraintBottom_toTopOf="@id/prev_uri" |
|||
app:layout_constraintEnd_toEndOf="parent" |
|||
app:layout_constraintStart_toStartOf="parent" |
|||
app:layout_constraintTop_toTopOf="parent" |
|||
app:layout_constraintVertical_chainStyle="packed" |
|||
app:layout_goneMarginBottom="0dp" |
|||
tools:text="@string/dir_select_permission_revoked_message" |
|||
tools:visibility="visible" /> |
|||
|
|||
<androidx.appcompat.widget.AppCompatTextView |
|||
android:id="@+id/prev_uri" |
|||
android:layout_width="0dp" |
|||
android:layout_height="wrap_content" |
|||
android:layout_marginBottom="8dp" |
|||
android:fontFamily="monospace" |
|||
android:padding="8dp" |
|||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1" |
|||
android:textColor="@color/blue_500" |
|||
android:visibility="gone" |
|||
app:layout_constraintBottom_toTopOf="@id/message2" |
|||
app:layout_constraintEnd_toEndOf="parent" |
|||
app:layout_constraintStart_toStartOf="parent" |
|||
app:layout_constraintTop_toBottomOf="@id/message" |
|||
app:layout_goneMarginBottom="0dp" |
|||
tools:text="content://something/something/content/content" |
|||
tools:visibility="visible" /> |
|||
|
|||
<androidx.appcompat.widget.AppCompatTextView |
|||
android:id="@+id/message2" |
|||
android:layout_width="0dp" |
|||
android:layout_height="wrap_content" |
|||
android:text="@string/dir_select_message2" |
|||
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6" |
|||
android:visibility="gone" |
|||
app:layout_constraintBottom_toBottomOf="parent" |
|||
app:layout_constraintEnd_toEndOf="parent" |
|||
app:layout_constraintStart_toStartOf="parent" |
|||
app:layout_constraintTop_toBottomOf="@id/prev_uri" |
|||
tools:visibility="visible" /> |
|||
|
|||
<com.google.android.material.progressindicator.CircularProgressIndicator |
|||
android:id="@+id/loading_indicator" |
|||
android:layout_width="0dp" |
|||
android:layout_height="wrap_content" |
|||
android:indeterminate="true" |
|||
android:visibility="gone" |
|||
app:layout_constraintBottom_toBottomOf="@id/select_dir" |
|||
app:layout_constraintEnd_toEndOf="parent" |
|||
app:layout_constraintStart_toStartOf="parent" |
|||
app:layout_constraintTop_toBottomOf="@id/title" /> |
|||
|
|||
<com.google.android.material.button.MaterialButton |
|||
android:id="@+id/select_dir" |
|||
style="@style/Widget.MaterialComponents.Button.OutlinedButton" |
|||
android:layout_width="0dp" |
|||
android:layout_height="wrap_content" |
|||
android:text="@string/select_folder" |
|||
app:layout_constraintBottom_toBottomOf="parent" |
|||
app:layout_constraintEnd_toEndOf="parent" |
|||
app:layout_constraintStart_toStartOf="parent" /> |
|||
|
|||
</androidx.constraintlayout.widget.ConstraintLayout> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue