Skip to content

Commit

Permalink
Merge pull request #3416 from FaribaKhandani/feature/savefiles
Browse files Browse the repository at this point in the history
feature/2331/saveFiles
  • Loading branch information
mahibi authored Nov 7, 2023
2 parents eb9d4f8 + dd9eb35 commit 946eb84
Show file tree
Hide file tree
Showing 7 changed files with 300 additions and 2 deletions.
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

0 comments on commit 946eb84

Please sign in to comment.