From ecc818705b9b523dbd1a85f017d14ba0d017849b Mon Sep 17 00:00:00 2001 From: Iván Ávalos Date: Thu, 20 Jan 2022 01:26:29 -0600 Subject: Properly implemented save and share report actions --- androidApp/src/google/AndroidManifest.xml | 15 ++- .../src/google/java/ManagerMessagingService.kt | 19 ++-- .../android/details/reports/UnitReportsFragment.kt | 63 +++++++++-- .../details/reports/UnitReportsViewModel.kt | 4 + .../TrackerMap/android/shared/FileCache.kt | 120 +++++++++++++++++++++ .../trackermap/TrackerMap/android/shared/Utils.kt | 71 ++++++++++++ .../src/main/res/layout/unit_details_reports.xml | 31 ++++-- androidApp/src/main/res/values-es-rMX/strings.xml | 5 +- androidApp/src/main/res/values/strings.xml | 5 +- androidApp/src/main/res/xml/filepaths.xml | 4 + 10 files changed, 312 insertions(+), 25 deletions(-) create mode 100644 androidApp/src/main/java/mx/trackermap/TrackerMap/android/shared/FileCache.kt create mode 100644 androidApp/src/main/res/xml/filepaths.xml diff --git a/androidApp/src/google/AndroidManifest.xml b/androidApp/src/google/AndroidManifest.xml index f7aea6c..ce72a8b 100644 --- a/androidApp/src/google/AndroidManifest.xml +++ b/androidApp/src/google/AndroidManifest.xml @@ -7,7 +7,10 @@ + tools:ignore="GoogleAppIndexingWarning" + android:allowBackup="false" + android:icon="@mipmap/ic_launcher" + android:fullBackupContent="false"> + + + + diff --git a/androidApp/src/google/java/ManagerMessagingService.kt b/androidApp/src/google/java/ManagerMessagingService.kt index 07ee427..9ac4636 100644 --- a/androidApp/src/google/java/ManagerMessagingService.kt +++ b/androidApp/src/google/java/ManagerMessagingService.kt @@ -16,14 +16,13 @@ package mx.trackermap.TrackerMap.android import android.annotation.SuppressLint -import android.app.NotificationManager import android.app.PendingIntent import android.content.Intent import android.os.Build -import androidx.core.app.NotificationCompat import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage import kotlinx.coroutines.DelicateCoroutinesApi +import mx.trackermap.TrackerMap.android.shared.Utils import mx.trackermap.TrackerMap.android.units.UnitsActivity import kotlin.time.ExperimentalTime @@ -39,14 +38,14 @@ class ManagerMessagingService : FirebaseMessagingService() { } else { PendingIntent.FLAG_ONE_SHOT } - val pendingIntent = PendingIntent.getActivity(this, 0, Intent(this, UnitsActivity::class.java), flags) - val builder = NotificationCompat.Builder(this, getString(R.string.notification_channel_id)) - .setSmallIcon(R.drawable.icon_notify) - .setContentTitle(getString(R.string.app_name)) - .setContentText(remoteMessage.notification?.body) - .setAutoCancel(true) - .setContentIntent(pendingIntent) - (getSystemService(NOTIFICATION_SERVICE) as NotificationManager).notify(remoteMessage.hashCode(), builder.build()) + val intent = Intent(this, UnitsActivity::class.java) + val pendingIntent = PendingIntent.getActivity(this, 0, intent, flags) + Utils.showNotification( + this, + remoteMessage.hashCode(), + remoteMessage.notification?.body, + pendingIntent + ) } override fun onNewToken(token: String) { diff --git a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/details/reports/UnitReportsFragment.kt b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/details/reports/UnitReportsFragment.kt index 51d2f6d..08d7076 100644 --- a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/details/reports/UnitReportsFragment.kt +++ b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/details/reports/UnitReportsFragment.kt @@ -22,12 +22,14 @@ import mx.trackermap.TrackerMap.android.R import mx.trackermap.TrackerMap.android.databinding.UnitDetailsReportsBinding import mx.trackermap.TrackerMap.android.details.UnitDetailsAdapter import mx.trackermap.TrackerMap.android.map.MapWrapperFragment +import mx.trackermap.TrackerMap.android.shared.FileCache +import mx.trackermap.TrackerMap.android.shared.Utils import mx.trackermap.TrackerMap.client.models.EventInformation import mx.trackermap.TrackerMap.controllers.ReportController import mx.trackermap.TrackerMap.utils.Formatter import mx.trackermap.TrackerMap.utils.ReportDates import org.koin.androidx.viewmodel.ext.android.viewModel -import java.lang.Exception +import java.io.* import kotlin.math.max import kotlin.time.ExperimentalTime @@ -41,6 +43,13 @@ class UnitReportsFragment : Fragment() { private val unitReportsViewModel: UnitReportsViewModel by viewModel() private lateinit var mapFragment: MapWrapperFragment + private var reportFile: ReportController.Report.XlsxReport? = null + private var exportAction: UnitReportsViewModel.ExportAction? = null + + private companion object { + const val XLSX_MIME_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -104,6 +113,11 @@ class UnitReportsFragment : Fragment() { showPeriodPopUp(it) } binding.exportButton.setOnClickListener { + exportAction = UnitReportsViewModel.ExportAction.ACTION_SAVE + unitReportsViewModel.fetchReportXlsx() + } + binding.shareButton.setOnClickListener { + exportAction = UnitReportsViewModel.ExportAction.ACTION_SHARE unitReportsViewModel.fetchReportXlsx() } unitReportsViewModel.setReportPeriod(ReportDates.ReportPeriod.TODAY) @@ -131,7 +145,12 @@ class UnitReportsFragment : Fragment() { showMap(true) } is ReportController.Report.XlsxReport -> { - downloadFile("report.xlsx") + reportFile = report + when (exportAction) { + UnitReportsViewModel.ExportAction.ACTION_SHARE -> shareFile() + UnitReportsViewModel.ExportAction.ACTION_SAVE -> saveFile() + else -> {} + } } is ReportController.Report.LoadingReport -> loading() } @@ -280,12 +299,12 @@ class UnitReportsFragment : Fragment() { } } - private fun downloadFile(filename: String) { + private fun saveFile(filename: String = "reports.xlsx") { val filesDir = requireContext().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS) filesDir?.let { val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) - type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + type = XLSX_MIME_TYPE putExtra(Intent.EXTRA_TITLE, filename) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { putExtra(DocumentsContract.EXTRA_INITIAL_URI, filesDir) @@ -295,10 +314,19 @@ class UnitReportsFragment : Fragment() { } } + private fun shareFile(filename: String = "reports.xlsx") { + context?.let { context -> + reportFile?.data?.let { data -> + val cacheFile = Utils.saveReportToCache(context, data, filename) + Utils.shareFile(context, cacheFile, XLSX_MIME_TYPE) + } + } + } + private val createDocumentResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { if (it.resultCode == Activity.RESULT_OK) { - (unitReportsViewModel.report.value as? ReportController.Report.XlsxReport)?.let { report -> + reportFile?.let { report -> it?.data?.data?.also { uri -> Log.d("UnitReportsFragment", "Downloading file into ${uri.path}") val outputStream = context?.contentResolver?.openOutputStream(uri) @@ -308,7 +336,23 @@ class UnitReportsFragment : Fragment() { outputStream.flush() outputStream.close() Log.d("UnitReportsFragment", "Wrote file into ${uri.path}") - } catch (e: Exception) { + + activity?.let { context -> + // Copy file to cache so we can open it + val fc = FileCache(context) + fc.removeAll() + fc.cacheThis(listOf(uri)) + + val dir = File(context.cacheDir, "reports_tmp") + val files = dir.listFiles() + if (files?.isNotEmpty() == true) { + val intent = + Utils.openFileIntent(context, files[0], XLSX_MIME_TYPE) + openFileResult.launch(intent) + } + } + + } catch (e: IOException) { e.printStackTrace() } } @@ -316,4 +360,11 @@ class UnitReportsFragment : Fragment() { } } } + + private val openFileResult = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + if (it.resultCode == Activity.RESULT_OK) { + Log.d("UnitReportsFragment", "Opening file!") + } + } } \ No newline at end of file diff --git a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/details/reports/UnitReportsViewModel.kt b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/details/reports/UnitReportsViewModel.kt index 7d1e028..0bdf61b 100644 --- a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/details/reports/UnitReportsViewModel.kt +++ b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/details/reports/UnitReportsViewModel.kt @@ -17,6 +17,10 @@ class UnitReportsViewModel( savedStateHandle: SavedStateHandle ) : ViewModel(), KoinComponent { + enum class ExportAction { + ACTION_SAVE, ACTION_SHARE + } + private val geofencesController: GeofencesController by inject() private val reportController: ReportController by inject() diff --git a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/shared/FileCache.kt b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/shared/FileCache.kt new file mode 100644 index 0000000..16777de --- /dev/null +++ b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/shared/FileCache.kt @@ -0,0 +1,120 @@ +package mx.trackermap.TrackerMap.android.shared + +import android.content.Context +import android.net.Uri +import android.provider.OpenableColumns +import android.webkit.MimeTypeMap +import java.io.BufferedInputStream +import java.io.BufferedOutputStream +import java.io.File +import java.io.FileOutputStream + +// Source: https://techenum.com/android-content-uri-how-to-get-the-file-uri/ +class FileCache( + private val context: Context +) { + + // content resolver + private val contentResolver = context.contentResolver + + // to get the type of file + private val mimeTypeMap = MimeTypeMap.getSingleton() + + private val mCacheLocation = File(context.cacheDir, "reports_tmp") + + fun cacheThis(uris: List) { + uris.forEach { uri -> copyFromSource(uri) } + } + + /** + * Copies the actual data from provided content provider. + */ + private fun copyFromSource(uri: Uri) { + + val fileExtension: String = getFileExtension(uri) ?: kotlin.run { + throw RuntimeException("Extension is null for $uri") + } + val fileName = queryName(uri) ?: getFileName(fileExtension) + + val inputStream = contentResolver.openInputStream(uri) ?: kotlin.run { + throw RuntimeException("Cannot open for reading $uri") + } + val bufferedInputStream = BufferedInputStream(inputStream) + + // the file which will be the new cached file + val outputFile = File(mCacheLocation, fileName) + if (!outputFile.exists()) { + outputFile.parentFile?.mkdirs() + } else { + outputFile.delete() + } + val bufferedOutputStream = BufferedOutputStream(FileOutputStream(outputFile)) + + // this will hold the content for each iteration + val buffer = ByteArray(DEFAULT_BUFFER_SIZE) + + var readBytes: Int // will be -1 if reached the end of file + + while (true) { + readBytes = bufferedInputStream.read(buffer) + + // check if the read was failure + if (readBytes == -1) { + bufferedOutputStream.flush() + break + } + + bufferedOutputStream.write(buffer) + bufferedOutputStream.flush() + } + + // close everything + inputStream.close() + bufferedInputStream.close() + bufferedOutputStream.close() + + } + + private fun getFileExtension(uri: Uri): String? { + return mimeTypeMap.getExtensionFromMimeType(contentResolver.getType(uri)) + } + + /** + * Tries to get actual name of the file being copied. + * This might be required in some of the cases where you might want to know the file name too. + * + * @param uri + * + */ + private fun queryName(uri: Uri): String? { + val returnCursor = contentResolver.query(uri, null, null, null, null) ?: return null + val nameIndex: Int = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) + returnCursor.moveToFirst() + val name: String = returnCursor.getString(nameIndex) + returnCursor.close() + return name + } + + private fun getFileName(fileExtension: String): String { + return "${System.currentTimeMillis()}.$fileExtension" + } + + /** + * Remove everything that we have cached. + * You might want to invoke this method before quiting the application. + */ + fun removeAll() { + mCacheLocation.deleteRecursively() + } + + companion object { + + // base buffer size + private const val BASE_BUFFER_SIZE = 1024 + + // if you want to modify size use binary multiplier 2, 4, 6, 8 + private const val DEFAULT_BUFFER_SIZE = BASE_BUFFER_SIZE * 4 + + } + +} \ No newline at end of file diff --git a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/shared/Utils.kt b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/shared/Utils.kt index 56a9167..fe18997 100644 --- a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/shared/Utils.kt +++ b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/shared/Utils.kt @@ -1,10 +1,22 @@ package mx.trackermap.TrackerMap.android.shared +import android.app.NotificationManager +import android.app.PendingIntent import android.content.Context +import android.content.Intent +import android.util.Log import android.view.View import androidx.appcompat.widget.PopupMenu +import androidx.core.app.NotificationCompat +import androidx.core.app.ShareCompat +import androidx.core.content.FileProvider +import com.google.firebase.messaging.FirebaseMessagingService import mx.trackermap.TrackerMap.android.R import mx.trackermap.TrackerMap.client.models.MapLayer +import java.io.BufferedOutputStream +import java.io.File +import java.io.FileOutputStream +import java.io.IOException class Utils { companion object { @@ -22,5 +34,64 @@ class Utils { } popOver.show() } + + fun openFileIntent(context: Context, file: File, mime: String): Intent { + Log.d("Utils", "Filename is ${file.name}") + val uri = FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", file) + val intent = Intent() + intent.action = Intent.ACTION_VIEW + intent.setDataAndType(uri, mime) + intent.putExtra(Intent.EXTRA_STREAM, uri) + return intent + } + + fun shareFile(context: Context, file: File, mime: String) { + Log.d("Utils", "Filename is ${file.name}") + val uri = FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", file) + ShareCompat.IntentBuilder(context) + .setType(mime) + .setStream(uri) + .setChooserTitle("Share this with your friends!") + .startChooser() + } + + fun saveReportToCache( + context: Context, + data: ByteArray, + filename: String + ): File { + val reportsDir = File(context.cacheDir, "reports_tmp") + reportsDir.deleteRecursively() + val cacheFile = File(reportsDir, filename) + if (!cacheFile.exists()) { + cacheFile.parentFile?.mkdirs() + } else { + cacheFile.delete() + } + + val output = BufferedOutputStream(FileOutputStream(cacheFile)) + + try { + output.write(data) + output.flush() + output.close() + } catch (e: IOException) { + e.printStackTrace() + } + + return cacheFile + } + + fun showNotification(context: Context, id: Int, body: String?, pendingIntent: PendingIntent) { + val builder = NotificationCompat.Builder(context, context.getString(R.string.notification_channel_id)) + .setSmallIcon(R.drawable.icon_notify) + .setContentTitle(context.getString(R.string.app_name)) + .setContentText(body) + .setAutoCancel(true) + .setContentIntent(pendingIntent) + (context.getSystemService(FirebaseMessagingService.NOTIFICATION_SERVICE) + as NotificationManager) + .notify(id, builder.build()) + } } } \ No newline at end of file diff --git a/androidApp/src/main/res/layout/unit_details_reports.xml b/androidApp/src/main/res/layout/unit_details_reports.xml index f53ce51..903c2c3 100644 --- a/androidApp/src/main/res/layout/unit_details_reports.xml +++ b/androidApp/src/main/res/layout/unit_details_reports.xml @@ -106,12 +106,6 @@ android:textColor="@color/colorPrimaryDark" app:backgroundTint="@color/darkBackground" /> - - + + + + + + + + Paradas Periodo Seleccionar - Exportar + Guardar + Compartir + El reporte ha sido descargado + Abrir con Hoy diff --git a/androidApp/src/main/res/values/strings.xml b/androidApp/src/main/res/values/strings.xml index babb741..3e2b37a 100644 --- a/androidApp/src/main/res/values/strings.xml +++ b/androidApp/src/main/res/values/strings.xml @@ -101,7 +101,10 @@ Stops Period Select - Export + Save + Share + Report has been downloaded + Open file with Today diff --git a/androidApp/src/main/res/xml/filepaths.xml b/androidApp/src/main/res/xml/filepaths.xml new file mode 100644 index 0000000..f894ef4 --- /dev/null +++ b/androidApp/src/main/res/xml/filepaths.xml @@ -0,0 +1,4 @@ + + + + -- cgit v1.2.3