aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIván Ávalos <avalos@disroot.org>2022-01-20 01:26:29 -0600
committerIván Ávalos <avalos@disroot.org>2022-01-20 01:26:29 -0600
commitecc818705b9b523dbd1a85f017d14ba0d017849b (patch)
tree556f1c62fb22ad0bc85720985a0fcc1137989e96
parent5472556161c2f781e7a0dabe48301f3b665641b1 (diff)
downloadetbsa-trackermap-mobile-ecc818705b9b523dbd1a85f017d14ba0d017849b.tar.gz
etbsa-trackermap-mobile-ecc818705b9b523dbd1a85f017d14ba0d017849b.tar.bz2
etbsa-trackermap-mobile-ecc818705b9b523dbd1a85f017d14ba0d017849b.zip
Properly implemented save and share report actions
-rw-r--r--androidApp/src/google/AndroidManifest.xml15
-rw-r--r--androidApp/src/google/java/ManagerMessagingService.kt19
-rw-r--r--androidApp/src/main/java/mx/trackermap/TrackerMap/android/details/reports/UnitReportsFragment.kt63
-rw-r--r--androidApp/src/main/java/mx/trackermap/TrackerMap/android/details/reports/UnitReportsViewModel.kt4
-rw-r--r--androidApp/src/main/java/mx/trackermap/TrackerMap/android/shared/FileCache.kt120
-rw-r--r--androidApp/src/main/java/mx/trackermap/TrackerMap/android/shared/Utils.kt71
-rw-r--r--androidApp/src/main/res/layout/unit_details_reports.xml31
-rw-r--r--androidApp/src/main/res/values-es-rMX/strings.xml5
-rw-r--r--androidApp/src/main/res/values/strings.xml5
-rw-r--r--androidApp/src/main/res/xml/filepaths.xml4
10 files changed, 312 insertions, 25 deletions
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 @@
<application
android:name=".GoogleMainApplication"
tools:replace="android:name"
- tools:ignore="GoogleAppIndexingWarning">
+ tools:ignore="GoogleAppIndexingWarning"
+ android:allowBackup="false"
+ android:icon="@mipmap/ic_launcher"
+ android:fullBackupContent="false">
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
@@ -23,6 +26,16 @@
</intent-filter>
</service>
+ <provider
+ android:authorities="${applicationId}.fileprovider"
+ android:name="androidx.core.content.FileProvider"
+ android:exported="false"
+ android:grantUriPermissions="true">
+ <meta-data
+ android:name="android.support.FILE_PROVIDER_PATHS"
+ android:resource="@xml/filepaths" />
+ </provider>
+
</application>
</manifest>
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<Uri>) {
+ 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" />
- <com.google.android.material.button.MaterialButton
- android:id="@+id/exportButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/export_report" />
-
</LinearLayout>
<com.addisonelliott.segmentedbutton.SegmentedButtonGroup
@@ -159,6 +153,31 @@
</com.addisonelliott.segmentedbutton.SegmentedButtonGroup>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_marginTop="@dimen/margin">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/exportButton"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/export_report"
+ android:layout_weight="1"
+ android:layout_marginEnd="@dimen/fields_spacing"
+ style="?android:buttonStyleSmall"/>
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/shareButton"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/share_report"
+ style="?android:buttonStyleSmall"/>
+
+ </LinearLayout>
+
</LinearLayout>
<include
diff --git a/androidApp/src/main/res/values-es-rMX/strings.xml b/androidApp/src/main/res/values-es-rMX/strings.xml
index 739023b..7284b6c 100644
--- a/androidApp/src/main/res/values-es-rMX/strings.xml
+++ b/androidApp/src/main/res/values-es-rMX/strings.xml
@@ -88,7 +88,10 @@
<string name="stops">Paradas</string>
<string name="period">Periodo</string>
<string name="select_period">Seleccionar</string>
- <string name="export_report">Exportar</string>
+ <string name="export_report">Guardar</string>
+ <string name="share_report">Compartir</string>
+ <string name="export_stored">El reporte ha sido descargado</string>
+ <string name="export_open_file_with">Abrir con</string>
<!-- Report periods -->
<string name="period_today">Hoy</string>
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 @@
<string name="stops">Stops</string>
<string name="period">Period</string>
<string name="select_period">Select</string>
- <string name="export_report">Export</string>
+ <string name="export_report">Save</string>
+ <string name="share_report">Share</string>
+ <string name="export_stored">Report has been downloaded</string>
+ <string name="export_open_file_with">Open file with</string>
<!-- Report periods -->
<string name="period_today">Today</string>
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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<paths>
+ <cache-path name="/" path="." />
+</paths>