aboutsummaryrefslogtreecommitdiff
path: root/androidApp/src/main/java/mx/trackermap/TrackerMap/android
diff options
context:
space:
mode:
Diffstat (limited to 'androidApp/src/main/java/mx/trackermap/TrackerMap/android')
-rw-r--r--androidApp/src/main/java/mx/trackermap/TrackerMap/android/details/DetailsActivity.kt8
-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/map/MapFragment.kt2
-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/java/mx/trackermap/TrackerMap/android/units/UnitsActivity.kt14
7 files changed, 273 insertions, 9 deletions
diff --git a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/details/DetailsActivity.kt b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/details/DetailsActivity.kt
index 56d5c4b..9633332 100644
--- a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/details/DetailsActivity.kt
+++ b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/details/DetailsActivity.kt
@@ -5,6 +5,7 @@ import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Log
import androidx.activity.result.contract.ActivityResultContracts
+import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import com.google.android.material.tabs.TabLayout
@@ -108,6 +109,13 @@ class DetailsActivity : AppCompatActivity() {
}
ActivityCompat.shouldShowRequestPermissionRationale(this, permission) -> {
Log.d("DetailsActivity", "shouldShowRequestPermissionRationale")
+ AlertDialog.Builder(this)
+ .setTitle(R.string.write_rationale_title)
+ .setMessage(R.string.write_rationale_msg)
+ .setPositiveButton(R.string.shared_ok) { _, _ ->
+ launcher.launch(permission)
+ }
+ .create().show()
}
else -> {
Log.d("DetailsActivity", "Requesting $permission permission")
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/map/MapFragment.kt b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/map/MapFragment.kt
index 37956c1..c483d52 100644
--- a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/map/MapFragment.kt
+++ b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/map/MapFragment.kt
@@ -240,7 +240,7 @@ open class MapFragment : GlobeMapFragment() {
/* Draw polyline for report */
if (isReport && markers.isNotEmpty()) {
val vector = VectorObject()
- vector.addAreal(points)
+ vector.addLinear(points)
objects.add(mapControl.addVector(
vector,
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 004bcaa..abfe1bb 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 {
@@ -23,5 +35,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/java/mx/trackermap/TrackerMap/android/units/UnitsActivity.kt b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/units/UnitsActivity.kt
index 9fcef7f..58147e4 100644
--- a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/units/UnitsActivity.kt
+++ b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/units/UnitsActivity.kt
@@ -66,6 +66,10 @@ class UnitsActivity : AppCompatActivity() {
}
private fun setupViews() {
+ supportFragmentManager.commit {
+ add(R.id.displayContainer, mapFragment)
+ add(R.id.displayContainer, devicesFragment)
+ }
TooltipCompat.setTooltipText(binding.userButton, getString(R.string.open_profile))
TooltipCompat.setTooltipText(binding.mapLayerToggle, getString(R.string.switch_layer))
}
@@ -148,9 +152,15 @@ class UnitsActivity : AppCompatActivity() {
UnitsViewModel.UnitsDisplayMode.MAP -> mapFragment
else -> mapFragment
}
+ val oldFragment =
+ when (displayMode) {
+ UnitsViewModel.UnitsDisplayMode.LIST -> mapFragment
+ UnitsViewModel.UnitsDisplayMode.MAP -> devicesFragment
+ else -> devicesFragment
+ }
supportFragmentManager.commit {
- replace(R.id.displayContainer, newFragment)
- setTransition(TRANSIT_FRAGMENT_FADE)
+ show(newFragment)
+ hide(oldFragment)
}
}
unitsViewModel.selectedUnit.observe(this) {