diff --git a/app/src/main/java/com/readrops/app/fragments/settings/AccountSettingsFragment.java b/app/src/main/java/com/readrops/app/fragments/settings/AccountSettingsFragment.java index 2df601a6..99bce58c 100644 --- a/app/src/main/java/com/readrops/app/fragments/settings/AccountSettingsFragment.java +++ b/app/src/main/java/com/readrops/app/fragments/settings/AccountSettingsFragment.java @@ -8,7 +8,6 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; -import android.os.Environment; import android.provider.Settings; import android.util.Log; @@ -29,6 +28,7 @@ import com.readrops.app.ReadropsApp; import com.readrops.app.activities.AddAccountActivity; import com.readrops.app.activities.ManageFeedsFoldersActivity; import com.readrops.app.activities.NotificationPermissionActivity; +import com.readrops.app.utils.FileUtils; import com.readrops.app.utils.PermissionManager; import com.readrops.app.utils.SharedPreferencesManager; 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.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.observers.DisposableCompletableObserver; import io.reactivex.schedulers.Schedulers; +import kotlin.Unit; import static android.app.Activity.RESULT_OK; import static com.readrops.api.opml.OPMLHelper.OPEN_OPML_FILE_REQUEST; @@ -180,7 +176,7 @@ public class AccountSettingsFragment extends PreferenceFragmentCompat { } // region opml import - + @Override public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { if (requestCode == OPEN_OPML_FILE_REQUEST && resultCode == RESULT_OK && data != null) { @@ -231,37 +227,21 @@ public class AccountSettingsFragment extends PreferenceFragmentCompat { //region opml export private void exportAsOPMLFile() { + String fileName = "subscriptions.opml"; + try { - String filePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(); - File file = new File(filePath, "subscriptions.opml"); + String path = FileUtils.writeDownloadFile(getContext(), fileName, "text/xml", outputStream -> { + 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() - .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()); - } - }); + displayNotification(fileName, path); } catch (Exception e) { Log.e(TAG, 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.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) .setContentTitle(getString(R.string.opml_export)) - .setContentText(file.getName()) + .setContentText(name) .setSmallIcon(R.drawable.ic_notif) .setContentIntent(PendingIntent.getActivity(getContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)) .setAutoCancel(true) diff --git a/app/src/main/java/com/readrops/app/utils/FileUtils.kt b/app/src/main/java/com/readrops/app/utils/FileUtils.kt new file mode 100644 index 00000000..9ae24dff --- /dev/null +++ b/app/src/main/java/com/readrops/app/utils/FileUtils.kt @@ -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 + } +} \ No newline at end of file