Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/2331/saveFiles #3416

Merged
merged 1 commit into from
Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,16 @@

package com.nextcloud.talk.activities

import android.annotation.SuppressLint
import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup.MarginLayoutParams
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.FileProvider
import androidx.core.view.ViewCompat
Expand All @@ -41,20 +44,28 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
import androidx.work.Data
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkInfo
import androidx.work.WorkManager
import com.google.android.material.snackbar.Snackbar
import com.nextcloud.talk.BuildConfig
import com.nextcloud.talk.R
import com.nextcloud.talk.databinding.ActivityFullScreenImageBinding
import com.nextcloud.talk.jobs.SaveFileToStorageWorker
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.BitmapShrinker
import com.nextcloud.talk.utils.Mimetype.IMAGE_PREFIX_GENERIC
import pl.droidsonroids.gif.GifDrawable
import java.io.File
import java.util.concurrent.ExecutionException

class FullScreenImageActivity : AppCompatActivity() {
lateinit var binding: ActivityFullScreenImageBinding
private lateinit var windowInsetsController: WindowInsetsControllerCompat
private lateinit var path: String
private var showFullscreen = false
lateinit var viewThemeUtils: ViewThemeUtils

override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_preview, menu)
Expand All @@ -67,6 +78,7 @@ class FullScreenImageActivity : AppCompatActivity() {
onBackPressedDispatcher.onBackPressed()
true
}

R.id.share -> {
val shareUri = FileProvider.getUriForFile(
this,
Expand All @@ -84,12 +96,34 @@ class FullScreenImageActivity : AppCompatActivity() {

true
}

R.id.save -> {
showWarningDialog()
true
}

else -> {
super.onOptionsItemSelected(item)
}
}
}

private fun showWarningDialog() {
val builder = AlertDialog.Builder(this)
builder.setTitle(R.string.nc_dialog_save_to_storage_title)
builder.setMessage(R.string.nc_dialog_save_to_storage_content)
builder.setPositiveButton(R.string.nc_dialog_save_to_storage_yes) { dialog: DialogInterface, which: Int ->
val fileName = intent.getStringExtra("FILE_NAME").toString()
saveImageToStorage(fileName)
dialog.dismiss()
}
builder.setNegativeButton(R.string.nc_dialog_save_to_storage_no) { dialog: DialogInterface, which: Int ->
dialog.dismiss()
}
val dialog = builder.create()
dialog.show()
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

Expand Down Expand Up @@ -188,6 +222,38 @@ class FullScreenImageActivity : AppCompatActivity() {
}
}

@SuppressLint("LongLogTag")
private fun saveImageToStorage(
fileName: String
) {
val sourceFilePath = applicationContext.cacheDir.path

val workers = WorkManager.getInstance(this).getWorkInfosByTag(fileName)
try {
for (workInfo in workers.get()) {
if (workInfo.state == WorkInfo.State.RUNNING || workInfo.state == WorkInfo.State.ENQUEUED) {
return
}
}
} catch (e: ExecutionException) {
Log.e(TAG, "Error when checking if worker already exists", e)
} catch (e: InterruptedException) {
Log.e(TAG, "Error when checking if worker already exists", e)
}

val data: Data = Data.Builder()
.putString(SaveFileToStorageWorker.KEY_FILE_NAME, fileName)
.putString(SaveFileToStorageWorker.KEY_SOURCE_FILE_PATH, "$sourceFilePath/$fileName")
.build()

val saveWorker: OneTimeWorkRequest = OneTimeWorkRequest.Builder(SaveFileToStorageWorker::class.java)
.setInputData(data)
.addTag(fileName)
.build()

WorkManager.getInstance().enqueue(saveWorker)
}

companion object {
private const val TAG = "FullScreenImageActivity"
private const val HUNDRED_MB = 100 * 1024 * 1024
Expand Down
80 changes: 78 additions & 2 deletions app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import android.annotation.SuppressLint
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.AssetFileDescriptor
Expand Down Expand Up @@ -156,6 +157,7 @@ import com.nextcloud.talk.events.UserMentionClickEvent
import com.nextcloud.talk.events.WebSocketCommunicationEvent
import com.nextcloud.talk.extensions.loadAvatarOrImagePreview
import com.nextcloud.talk.jobs.DownloadFileToCacheWorker
import com.nextcloud.talk.jobs.SaveFileToStorageWorker
import com.nextcloud.talk.jobs.ShareOperationWorker
import com.nextcloud.talk.jobs.UploadAndShareFilesWorker
import com.nextcloud.talk.location.LocationPickerActivity
Expand Down Expand Up @@ -897,7 +899,7 @@ class ChatActivity :
}
} else {
Log.d(TAG, "Downloaded to cache")
downloadFileToCache(message, true) {
downloadFileToCache(message,true ) {
setUpWaveform(message)
}
}
Expand Down Expand Up @@ -2018,6 +2020,44 @@ class ChatActivity :
}
}

@SuppressLint("LongLogTag")
private fun saveImageToStorage(
message: ChatMessage
) {
message.openWhenDownloaded = false
adapter?.update(message)

val fileName = message.selectedIndividualHashMap!!["name"]
val sourceFilePath = applicationContext.cacheDir.path
val fileId = message.selectedIndividualHashMap!!["id"]

val workers = WorkManager.getInstance(context).getWorkInfosByTag(fileId!!)
try {
for (workInfo in workers.get()) {
if (workInfo.state == WorkInfo.State.RUNNING || workInfo.state == WorkInfo.State.ENQUEUED) {
Log.d(TAG, "SaveFileToStorageWorker for $fileId is already running or scheduled")
return
}
}
} catch (e: ExecutionException) {
Log.e(TAG, "Error when checking if worker already exists", e)
} catch (e: InterruptedException) {
Log.e(TAG, "Error when checking if worker already exists", e)
}

val data: Data = Data.Builder()
.putString(SaveFileToStorageWorker.KEY_FILE_NAME, fileName)
.putString(SaveFileToStorageWorker.KEY_SOURCE_FILE_PATH, "$sourceFilePath/$fileName")
.build()

val saveWorker: OneTimeWorkRequest = OneTimeWorkRequest.Builder(SaveFileToStorageWorker::class.java)
.setInputData(data)
.addTag(fileId)
.build()

WorkManager.getInstance().enqueue(saveWorker)
}

@SuppressLint("SimpleDateFormat")
private fun setVoiceRecordFileName() {
val simpleDateFormat = SimpleDateFormat(FILE_DATE_PATTERN)
Expand Down Expand Up @@ -4101,12 +4141,48 @@ class ChatActivity :
if (file.exists()) {
share(message)
} else {
downloadFileToCache(message, false) {
downloadFileToCache(message, false ) {
share(message)
}
}
}

private fun saveImage(message: ChatMessage){
if (permissionUtil.isFilesPermissionGranted()) {
saveImageToStorage(message)
} else {
UploadAndShareFilesWorker.requestStoragePermission(this@ChatActivity)
}
}

private fun showSaveToStorageWarning(message: ChatMessage) {
val builder = AlertDialog.Builder(this)
builder.setTitle(R.string.nc_dialog_save_to_storage_title)
builder.setMessage(R.string.nc_dialog_save_to_storage_content)
builder.setPositiveButton(R.string.nc_dialog_save_to_storage_yes) { dialog: DialogInterface, _: Int ->
saveImage(message)
dialog.dismiss()
}
builder.setNegativeButton(R.string.nc_dialog_save_to_storage_no) { dialog: DialogInterface, _: Int ->
dialog.dismiss()
}
val dialog = builder.create()
dialog.show()
}

fun checkIfSaveable(message: ChatMessage) {
val filename = message.selectedIndividualHashMap!!["name"]
path = applicationContext.cacheDir.absolutePath + "/" + filename
val file = File(context.cacheDir, filename!!)
if (file.exists()) {
showSaveToStorageWarning(message)
} else {
downloadFileToCache(message ,false) {
showSaveToStorageWarning(message)
}
}
}

fun openInFilesApp(message: ChatMessage) {
val keyID = message.selectedIndividualHashMap!![PreviewMessageViewHolder.KEY_ID]
val link = message.selectedIndividualHashMap!!["link"]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Nextcloud Talk application
*
* @author Andy Scherzinger
* @author Marcel Hibbe
* Copyright (C) 2022 Andy Scherzinger <[email protected]>
* Copyright (C) 2021 Marcel Hibbe <[email protected]>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package com.nextcloud.talk.jobs

import android.content.ContentValues
import android.content.Context
import android.media.MediaScannerConnection
import android.os.Environment
import android.provider.MediaStore
import android.provider.MediaStore.Files.FileColumns
import android.util.Log
import androidx.work.Worker
import androidx.work.WorkerParameters
import autodagger.AutoInjector
import com.nextcloud.talk.application.NextcloudTalkApplication
import java.io.File
import java.io.IOException
import java.io.OutputStream
import java.net.URLConnection

@AutoInjector(NextcloudTalkApplication::class)
class SaveFileToStorageWorker(val context: Context, workerParameters: WorkerParameters) :
Worker(context, workerParameters) {

override fun doWork(): Result {
try {
val sourceFilePath = inputData.getString(KEY_SOURCE_FILE_PATH)
val cacheFile = File(sourceFilePath!!)

val contentResolver = context.contentResolver
val mimeType = URLConnection.guessContentTypeFromName(cacheFile.name)

val values = ContentValues().apply {
put(FileColumns.DISPLAY_NAME, cacheFile.name)
put(FileColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
if (mimeType != null) {
put(FileColumns.MIME_TYPE, URLConnection.guessContentTypeFromName(cacheFile.name))
}
}

val collection = MediaStore.Files.getContentUri("external")
val uri = contentResolver.insert(collection, values)

uri?.let { fileUri ->
try {
val outputStream: OutputStream? = contentResolver.openOutputStream(fileUri)
outputStream.use { output ->
val inputStream = cacheFile.inputStream()
if (output != null) {
inputStream.copyTo(output)
}
}
} catch (e: Exception) {
Log.e(TAG, "Failed to create output stream")
return Result.failure()
}
}

// Notify the media scanner about the new file
MediaScannerConnection.scanFile(context, arrayOf(cacheFile.absolutePath), null, null)

return Result.success()
} catch (e: IOException) {
Log.e(TAG, "Something went wrong when trying to save file to internal storage", e)
return Result.failure()
} catch (e: NullPointerException) {
Log.e(TAG, "Something went wrong when trying to save file to internal storage", e)
return Result.failure()
}
}

companion object {
private val TAG = SaveFileToStorageWorker::class.java.simpleName
const val KEY_FILE_NAME = "KEY_FILE_NAME"
const val KEY_SOURCE_FILE_PATH = "KEY_SOURCE_FILE_PATH"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ class MessageActionsDialog(
initMenuItemOpenNcApp(
ChatMessage.MessageType.SINGLE_NC_ATTACHMENT_MESSAGE == message.getCalculateMessageType()
)
initMenuItemSave(message.getCalculateMessageType() == ChatMessage.MessageType.SINGLE_NC_ATTACHMENT_MESSAGE)
}

override fun onStart() {
Expand Down Expand Up @@ -169,6 +170,8 @@ class MessageActionsDialog(
dialogMessageActionsBinding.emojiMore.installForceSingleEmoji()
}



/*
This method is a hacky workaround to avoid bug #1914
As the bug happens only for the very first time when the popup is opened,
Expand Down Expand Up @@ -352,6 +355,16 @@ class MessageActionsDialog(
dialogMessageActionsBinding.menuOpenInNcApp.visibility = getVisibility(visible)
}

private fun initMenuItemSave (visible: Boolean) {
if (visible){
dialogMessageActionsBinding.menuSaveMessage.setOnClickListener {
chatActivity.checkIfSaveable(message)
dismiss()
}
}
dialogMessageActionsBinding.menuSaveMessage.visibility = getVisibility(visible)
}

private fun getVisibility(visible: Boolean): Int {
return if (visible) {
View.VISIBLE
Expand Down
Loading