diff options
author | Iván Ávalos <avalos@disroot.org> | 2022-01-20 01:53:52 -0600 |
---|---|---|
committer | Iván Ávalos <avalos@disroot.org> | 2022-01-20 01:53:52 -0600 |
commit | e5f4e67b85ff42cb116b99a9abb013067a5636ad (patch) | |
tree | f9524b126dcbe13ee62edf6eb6b254eb3a0740fe /androidApp/src/main/java | |
parent | 1cc7aee5c2ae7d587d9d2e09f0fea3f546ceb27a (diff) | |
parent | 84f65df0a794dee2ee21e80f83ed9e589412acc1 (diff) | |
download | etbsa-trackermap-mobile-e5f4e67b85ff42cb116b99a9abb013067a5636ad.tar.gz etbsa-trackermap-mobile-e5f4e67b85ff42cb116b99a9abb013067a5636ad.tar.bz2 etbsa-trackermap-mobile-e5f4e67b85ff42cb116b99a9abb013067a5636ad.zip |
Merge branch 'main' of https://git.sr.ht/~avalos/trackermap-mobile
Diffstat (limited to 'androidApp/src/main/java')
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) { |