mirror of https://github.com/readrops/Readrops.git
Use scope storage with API >= 29 for OPML export
This commit is contained in:
parent
594a0a67fa
commit
76d5e29589
|
@ -8,7 +8,6 @@ import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Environment;
|
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
@ -29,6 +28,7 @@ import com.readrops.app.ReadropsApp;
|
||||||
import com.readrops.app.activities.AddAccountActivity;
|
import com.readrops.app.activities.AddAccountActivity;
|
||||||
import com.readrops.app.activities.ManageFeedsFoldersActivity;
|
import com.readrops.app.activities.ManageFeedsFoldersActivity;
|
||||||
import com.readrops.app.activities.NotificationPermissionActivity;
|
import com.readrops.app.activities.NotificationPermissionActivity;
|
||||||
|
import com.readrops.app.utils.FileUtils;
|
||||||
import com.readrops.app.utils.PermissionManager;
|
import com.readrops.app.utils.PermissionManager;
|
||||||
import com.readrops.app.utils.SharedPreferencesManager;
|
import com.readrops.app.utils.SharedPreferencesManager;
|
||||||
import com.readrops.app.utils.Utils;
|
import com.readrops.app.utils.Utils;
|
||||||
|
@ -36,14 +36,10 @@ import com.readrops.app.viewmodels.AccountViewModel;
|
||||||
import com.readrops.db.entities.account.Account;
|
import com.readrops.db.entities.account.Account;
|
||||||
import com.readrops.db.entities.account.AccountType;
|
import com.readrops.db.entities.account.AccountType;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.observers.DisposableCompletableObserver;
|
import io.reactivex.observers.DisposableCompletableObserver;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
import kotlin.Unit;
|
||||||
|
|
||||||
import static android.app.Activity.RESULT_OK;
|
import static android.app.Activity.RESULT_OK;
|
||||||
import static com.readrops.api.opml.OPMLHelper.OPEN_OPML_FILE_REQUEST;
|
import static com.readrops.api.opml.OPMLHelper.OPEN_OPML_FILE_REQUEST;
|
||||||
|
@ -180,7 +176,7 @@ public class AccountSettingsFragment extends PreferenceFragmentCompat {
|
||||||
}
|
}
|
||||||
|
|
||||||
// region opml import
|
// region opml import
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
|
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
|
||||||
if (requestCode == OPEN_OPML_FILE_REQUEST && resultCode == RESULT_OK && data != null) {
|
if (requestCode == OPEN_OPML_FILE_REQUEST && resultCode == RESULT_OK && data != null) {
|
||||||
|
@ -231,37 +227,21 @@ public class AccountSettingsFragment extends PreferenceFragmentCompat {
|
||||||
//region opml export
|
//region opml export
|
||||||
|
|
||||||
private void exportAsOPMLFile() {
|
private void exportAsOPMLFile() {
|
||||||
|
String fileName = "subscriptions.opml";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String filePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath();
|
String path = FileUtils.writeDownloadFile(getContext(), fileName, "text/xml", outputStream -> {
|
||||||
File file = new File(filePath, "subscriptions.opml");
|
viewModel.getFoldersWithFeeds()
|
||||||
|
.flatMapCompletable(folderListMap -> OPMLParser.write(folderListMap, outputStream))
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.doOnError(e -> Utils.showSnackbar(getView(), e.getMessage()))
|
||||||
|
.subscribe();
|
||||||
|
|
||||||
final OutputStream outputStream = new FileOutputStream(file);
|
return Unit.INSTANCE;
|
||||||
|
});
|
||||||
|
|
||||||
viewModel.getFoldersWithFeeds()
|
displayNotification(fileName, path);
|
||||||
.flatMapCompletable(folderListMap -> OPMLParser.write(folderListMap, outputStream))
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.doAfterTerminate(() -> {
|
|
||||||
try {
|
|
||||||
outputStream.flush();
|
|
||||||
outputStream.close();
|
|
||||||
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(TAG, e.getMessage());
|
|
||||||
Utils.showSnackbar(getView(), e.getMessage());
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.subscribe(new DisposableCompletableObserver() {
|
|
||||||
@Override
|
|
||||||
public void onComplete() {
|
|
||||||
displayNotification(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(Throwable e) {
|
|
||||||
Utils.showSnackbar(getView(), e.getMessage());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, e.getMessage());
|
Log.e(TAG, e.getMessage());
|
||||||
Utils.showSnackbar(getView(), e.getMessage());
|
Utils.showSnackbar(getView(), e.getMessage());
|
||||||
|
@ -269,13 +249,13 @@ public class AccountSettingsFragment extends PreferenceFragmentCompat {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void displayNotification(File file) {
|
private void displayNotification(String name, String absolutePath) {
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||||
intent.setDataAndType(Uri.parse(file.getAbsolutePath()), "text/plain");
|
intent.setDataAndType(Uri.parse(absolutePath), "text/plain");
|
||||||
|
|
||||||
Notification notification = new NotificationCompat.Builder(getContext(), ReadropsApp.OPML_EXPORT_CHANNEL_ID)
|
Notification notification = new NotificationCompat.Builder(getContext(), ReadropsApp.OPML_EXPORT_CHANNEL_ID)
|
||||||
.setContentTitle(getString(R.string.opml_export))
|
.setContentTitle(getString(R.string.opml_export))
|
||||||
.setContentText(file.getName())
|
.setContentText(name)
|
||||||
.setSmallIcon(R.drawable.ic_notif)
|
.setSmallIcon(R.drawable.ic_notif)
|
||||||
.setContentIntent(PendingIntent.getActivity(getContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT))
|
.setContentIntent(PendingIntent.getActivity(getContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT))
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
package com.readrops.app.utils
|
||||||
|
|
||||||
|
import android.content.ContentValues
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Environment
|
||||||
|
import android.provider.MediaStore
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
|
||||||
|
object FileUtils {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun writeDownloadFile(context: Context, fileName: String, mimeType: String, listener: (OutputStream) -> Unit): String {
|
||||||
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
|
||||||
|
writeFileApi29(context, fileName, mimeType, listener)
|
||||||
|
else
|
||||||
|
writeFileApi28(fileName, listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.Q)
|
||||||
|
private fun writeFileApi29(context: Context, fileName: String, mimeType: String, listener: (OutputStream) -> Unit): String {
|
||||||
|
val resolver = context.contentResolver
|
||||||
|
val downloadsUri = MediaStore.Downloads
|
||||||
|
.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
||||||
|
|
||||||
|
val fileDetails = ContentValues().apply {
|
||||||
|
put(MediaStore.Downloads.DISPLAY_NAME, fileName)
|
||||||
|
put(MediaStore.Downloads.IS_PENDING, 1)
|
||||||
|
put(MediaStore.Downloads.MIME_TYPE, mimeType)
|
||||||
|
}
|
||||||
|
|
||||||
|
val contentUri = resolver.insert(downloadsUri, fileDetails)
|
||||||
|
|
||||||
|
resolver.openFileDescriptor(contentUri!!, "w", null).use { pfd ->
|
||||||
|
val outputStream = FileOutputStream(pfd?.fileDescriptor!!)
|
||||||
|
|
||||||
|
try {
|
||||||
|
listener(outputStream)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw e
|
||||||
|
} finally {
|
||||||
|
outputStream.flush()
|
||||||
|
outputStream.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileDetails.clear()
|
||||||
|
fileDetails.put(MediaStore.Downloads.IS_PENDING, 0)
|
||||||
|
resolver.update(contentUri, fileDetails, null, null)
|
||||||
|
|
||||||
|
return contentUri.path!!
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun writeFileApi28(fileName: String, listener: (OutputStream) -> Unit): String {
|
||||||
|
val filePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath
|
||||||
|
val file = File(filePath, fileName)
|
||||||
|
|
||||||
|
val outputStream = FileOutputStream(file)
|
||||||
|
listener(outputStream)
|
||||||
|
|
||||||
|
outputStream.flush()
|
||||||
|
outputStream.close()
|
||||||
|
|
||||||
|
return file.absolutePath
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue