diff --git a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java index 8f2a75e37..df069dc3d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java @@ -19,11 +19,13 @@ import android.app.ActivityManager; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; +import android.os.Build; import android.os.Bundle; import android.preference.PreferenceManager; import android.util.Log; @@ -37,9 +39,11 @@ import com.keylesspalace.tusky.db.AccountEntity; import com.keylesspalace.tusky.db.AccountManager; import com.keylesspalace.tusky.di.Injectable; import com.keylesspalace.tusky.interfaces.AccountSelectionListener; +import com.keylesspalace.tusky.interfaces.PermissionRequester; import com.keylesspalace.tusky.util.ThemeUtils; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import javax.inject.Inject; @@ -48,6 +52,8 @@ import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; import retrofit2.Call; public abstract class BaseActivity extends AppCompatActivity implements Injectable { @@ -59,7 +65,9 @@ public abstract class BaseActivity extends AppCompatActivity implements Injectab @Inject public AccountManager accountManager; - protected static final int PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE = 1; + protected static final int BUILD_VERSION_ANY = -1; + private static final int REQUESTER_NONE = Integer.MAX_VALUE; + private HashMap requesters; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { @@ -98,6 +106,7 @@ public abstract class BaseActivity extends AppCompatActivity implements Injectab } callList = new ArrayList<>(); + requesters = new HashMap<>(); } @Override @@ -222,4 +231,38 @@ public abstract class BaseActivity extends AppCompatActivity implements Injectab .setAdapter(adapter, (dialogInterface, index) -> listener.onAccountSelected(accounts.get(index))) .show(); } + + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + if (requesters.containsKey(requestCode)) { + PermissionRequester requester = requesters.remove(requestCode); + requester.onRequestPermissionsResult(permissions, grantResults); + } + } + + public void requestPermissions(String[] permissions, int minimumBuildVersion, PermissionRequester requester) { + if (minimumBuildVersion == BUILD_VERSION_ANY || Build.VERSION.SDK_INT >= minimumBuildVersion) { + ArrayList permissionsToRequest = new ArrayList<>(); + for(String permission: permissions) { + if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) { + permissionsToRequest.add(permission); + } + } + if (permissionsToRequest.isEmpty()) { + int[] permissionsAlreadyGranted = new int[permissions.length]; + for (int i = 0; i < permissionsAlreadyGranted.length; ++i) + permissionsAlreadyGranted[i] = PackageManager.PERMISSION_GRANTED; + requester.onRequestPermissionsResult(permissions, permissionsAlreadyGranted); + return; + } + + int newKey = requester == null ? REQUESTER_NONE : requesters.size(); + if (newKey != REQUESTER_NONE) { + requesters.put(newKey, requester); + } + String[] permissionsCopy = new String[permissionsToRequest.size()]; + permissionsToRequest.toArray(permissionsCopy); + ActivityCompat.requestPermissions(this, permissionsCopy, newKey); + } + } } diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt b/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt index a733b1032..411ea10cc 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt @@ -31,8 +31,6 @@ import android.net.Uri import android.os.Build import android.os.Bundle import android.os.Environment -import androidx.core.app.ActivityCompat -import androidx.core.content.ContextCompat import androidx.core.content.FileProvider import androidx.viewpager.widget.ViewPager import android.util.Log @@ -142,7 +140,7 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener toolbar.setNavigationOnClickListener { supportFinishAfterTransition() } toolbar.setOnMenuItemClickListener { item: MenuItem -> when (item.itemId) { - R.id.action_download -> downloadMedia() + R.id.action_download -> requestDownloadMedia() R.id.action_open_status -> onOpenStatus() R.id.action_share_media -> shareMedia() R.id.action_copy_media_link -> copyLink() @@ -188,36 +186,25 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener .start() } - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { - when (requestCode) { - PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE -> { - if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - downloadMedia() - } else { - showErrorDialog(toolbar, R.string.error_media_download_permission, R.string.action_retry) { downloadMedia() } - } - } - } + private fun downloadMedia() { + val url = attachments!![viewPager.currentItem].attachment.url + val filename = Uri.parse(url).lastPathSegment + Toast.makeText(applicationContext, resources.getString(R.string.download_image, filename), Toast.LENGTH_SHORT).show() + + val downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager + val request = DownloadManager.Request(Uri.parse(url)) + request.setDestinationInExternalPublicDir(Environment.DIRECTORY_PICTURES, + getString(R.string.app_name) + "/" + filename) + downloadManager.enqueue(request) } - private fun downloadMedia() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && - ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) - != PackageManager.PERMISSION_GRANTED) { - ActivityCompat.requestPermissions(this, - arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), - PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE) - } else { - val url = attachments!![viewPager.currentItem].attachment.url - val filename = Uri.parse(url).lastPathSegment - val toastText = String.format(resources.getString(R.string.download_image), filename) - Toast.makeText(applicationContext, toastText, Toast.LENGTH_SHORT).show() - - val downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager - val request = DownloadManager.Request(Uri.parse(url)) - request.setDestinationInExternalPublicDir(Environment.DIRECTORY_PICTURES, - getString(R.string.app_name) + "/" + filename) - downloadManager.enqueue(request) + private fun requestDownloadMedia() { + requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), Build.VERSION_CODES.M) { _, grantResults -> + if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + downloadMedia() + } else { + showErrorDialog(toolbar, R.string.error_media_download_permission, R.string.action_retry) { requestDownloadMedia() } + } } } diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java index 88d613c5a..be6755717 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java @@ -15,15 +15,22 @@ package com.keylesspalace.tusky.fragment; +import android.Manifest; +import android.app.DownloadManager; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; import android.text.Spanned; import android.view.Menu; import android.view.MenuItem; import android.view.View; +import android.widget.Toast; import com.keylesspalace.tusky.BaseActivity; import com.keylesspalace.tusky.BottomSheetActivity; @@ -69,6 +76,7 @@ public abstract class SFragment extends BaseFragment implements Injectable { protected abstract void onReblog(final boolean reblog, final int position); private BottomSheetActivity bottomSheetActivity; + private Status pendingDownloadStatus; @Inject public MastodonApi mastodonApi; @@ -158,6 +166,8 @@ public abstract class SFragment extends BaseFragment implements Injectable { // Give a different menu depending on whether this is the user's own toot or not. if (loggedInAccountId == null || !loggedInAccountId.equals(accountId)) { popup.inflate(R.menu.status_more); + Menu menu = popup.getMenu(); + menu.findItem(R.id.status_download_media).setVisible(!status.getAttachments().isEmpty()); } else { popup.inflate(R.menu.status_more_for_user); Menu menu = popup.getMenu(); @@ -236,6 +246,10 @@ public abstract class SFragment extends BaseFragment implements Injectable { showOpenAsDialog(statusUrl, item.getTitle()); return true; } + case R.id.status_download_media: { + requestDownloadAllMedia(status); + return true; + } case R.id.status_mute: { timelineCases.mute(accountId); return true; @@ -342,4 +356,31 @@ public abstract class SFragment extends BaseFragment implements Injectable { BaseActivity activity = (BaseActivity)getActivity(); activity.showAccountChooserDialog(dialogTitle, false, account -> openAsAccount(statusUrl, account)); } + + private void downloadAllMedia(Status status) { + pendingDownloadStatus = null; + Toast.makeText(getContext(), R.string.downloading_media, Toast.LENGTH_SHORT).show(); + for(Attachment attachment: status.getAttachments()) { + String url = attachment.getUrl(); + Uri uri = Uri.parse(url); + String filename = uri.getLastPathSegment(); + + DownloadManager downloadManager = (DownloadManager)getActivity().getSystemService(Context.DOWNLOAD_SERVICE); + DownloadManager.Request request = new DownloadManager.Request(uri); + request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename); + downloadManager.enqueue(request); + } + } + + private void requestDownloadAllMedia(Status status) { + pendingDownloadStatus = status; + String[] permissions = new String[]{ Manifest.permission.WRITE_EXTERNAL_STORAGE }; + ((BaseActivity)getActivity()).requestPermissions(permissions, Build.VERSION_CODES.M, (permissions1, grantResults) -> { + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + downloadAllMedia(status); + } else { + Toast.makeText(getContext(), R.string.error_media_download_permission, Toast.LENGTH_SHORT).show(); + } + }); + } } diff --git a/app/src/main/java/com/keylesspalace/tusky/interfaces/PermissionRequester.java b/app/src/main/java/com/keylesspalace/tusky/interfaces/PermissionRequester.java new file mode 100644 index 000000000..ca83e085b --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/interfaces/PermissionRequester.java @@ -0,0 +1,5 @@ +package com.keylesspalace.tusky.interfaces; + +public interface PermissionRequester { + void onRequestPermissionsResult(String[] permissions, int[] grantResults); +} \ No newline at end of file diff --git a/app/src/main/res/menu/status_more.xml b/app/src/main/res/menu/status_more.xml index e9f78fc0c..525ce90a3 100644 --- a/app/src/main/res/menu/status_more.xml +++ b/app/src/main/res/menu/status_more.xml @@ -18,6 +18,9 @@ + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c70f15adb..19731fbd6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -133,6 +133,8 @@ Copy the link Open as %s Share as … + Download media + Downloading media Share toot URL to… Share toot to…