diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..b589d56
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
new file mode 100644
index 0000000..b268ef3
--- /dev/null
+++ b/.idea/deploymentTargetSelector.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..0897082
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
new file mode 100644
index 0000000..fdf8d99
--- /dev/null
+++ b/.idea/kotlinc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/migrations.xml b/.idea/migrations.xml
new file mode 100644
index 0000000..f8051a6
--- /dev/null
+++ b/.idea/migrations.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..8978d23
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/other.xml b/.idea/other.xml
new file mode 100644
index 0000000..104e542
--- /dev/null
+++ b/.idea/other.xml
@@ -0,0 +1,329 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
new file mode 100644
index 0000000..79f2468
--- /dev/null
+++ b/app/build.gradle.kts
@@ -0,0 +1,55 @@
+plugins {
+ alias(libs.plugins.android.application)
+ alias(libs.plugins.jetbrains.kotlin.android)
+}
+
+android {
+ namespace = "com.armatak.simtak"
+ compileSdk = 34
+
+ defaultConfig {
+ applicationId = "com.armatak.simtak"
+ minSdk = 29
+ targetSdk = 34
+ versionCode = 1
+ versionName = "1.0"
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
+ viewBinding {
+ enable = true
+ }
+}
+
+dependencies {
+
+ implementation(libs.androidx.core.ktx)
+ implementation(libs.androidx.appcompat)
+ implementation(libs.material)
+ implementation(libs.androidx.activity)
+ implementation(libs.androidx.constraintlayout)
+
+ implementation(libs.okhttp)
+ //Vision - Qr/BarCodeScanner
+ implementation(libs.play.services.vision)
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.androidx.junit)
+ androidTestImplementation(libs.androidx.espresso.core)
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/com/armatak/simtak/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/armatak/simtak/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..63212c5
--- /dev/null
+++ b/app/src/androidTest/java/com/armatak/simtak/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.armatak.simtak
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.armatak.simtak", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/debug/AndroidManifest.xml b/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000..d16c003
--- /dev/null
+++ b/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..5a04310
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/armatak/simtak/HomeActivity.kt b/app/src/main/java/com/armatak/simtak/HomeActivity.kt
new file mode 100644
index 0000000..6c86320
--- /dev/null
+++ b/app/src/main/java/com/armatak/simtak/HomeActivity.kt
@@ -0,0 +1,182 @@
+package com.armatak.simtak
+
+import android.Manifest
+import android.app.Dialog
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.net.Uri
+import android.os.Build
+import android.os.Bundle
+import android.provider.Settings
+import android.view.Window
+import android.widget.Button
+import android.widget.TextView
+import android.widget.Toast
+import androidx.activity.enableEdgeToEdge
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.ContextCompat
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import com.armatak.simtak.databinding.ActivityHomeBinding
+import com.armatak.simtak.trackerLog.TrackerLogActivity
+
+class HomeActivity : AppCompatActivity() {
+ private lateinit var binding: ActivityHomeBinding
+ private var requestCamera: ActivityResultLauncher? = null
+ private var requestPermissionLauncher: ActivityResultLauncher? = null
+ private var requested = false
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityHomeBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+ enableEdgeToEdge()
+ ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, insets ->
+ val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
+ v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
+ insets
+ }
+
+ requestCamera = registerForActivityResult(ActivityResultContracts.RequestPermission()) {
+ if (it) {
+ initScan()
+ } else {
+ Toast.makeText(
+ this,
+ "Sem permissão para acessar a camera. Permita o acesso para continuar",
+ Toast.LENGTH_LONG
+ ).show()
+ }
+ }
+
+ requestPermissionLauncher = registerForActivityResult(
+ ActivityResultContracts.RequestPermission(),
+ ) { isGranted: Boolean ->
+ if (isGranted) {
+ Toast.makeText(baseContext, "Notificações Habilitadas", Toast.LENGTH_SHORT).show()
+ requestCamera?.launch(Manifest.permission.CAMERA)
+ } else {
+ Toast.makeText(
+ baseContext,
+ "Por favor confirme a permissão",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+
+ initUI()
+
+ }
+
+ private fun validateUrl() {
+ val etServerAddressLayout = binding.etServerAddressLayout
+ val etServerAddress = binding.etServerAddress
+ val serverAddress = etServerAddress.text.toString()
+
+ val regexPatterns = listOf(
+ "^ws://\\b(?:(?:2(?:[0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9])\\.){3}(?:(?:2([0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9]))\\b:[0-9]+\$",
+ "^ws://([A-Za-z0-9]+(\\.[A-Za-z0-9]+)+)\$"
+ )
+
+ val allowed = regexPatterns.any { patterns ->
+ Regex(patterns).matches(serverAddress)
+ }
+
+ if(allowed) {
+ requested = false
+ val intent = Intent(this, TrackerLogActivity::class.java)
+ intent.putExtra("webSocketUrl", serverAddress)
+ startActivity(intent)
+ } else {
+ requested = false
+ etServerAddressLayout.error = "Server Address need to be a valid URL"
+ }
+ }
+
+ private fun initScan() {
+ startActivity(Intent(this, ScannerActivity::class.java))
+ }
+
+
+ private fun initUI() {
+ binding.btnScanQrCode.setOnClickListener {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ if (ContextCompat.checkSelfPermission(
+ this,
+ Manifest.permission.POST_NOTIFICATIONS
+ ) ==
+ PackageManager.PERMISSION_GRANTED
+ ) {
+ requestCamera?.launch(Manifest.permission.CAMERA)
+ } else if (shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)) {
+ requestNotificationByDialog()
+ } else {
+ requestPermissionLauncher?.launch(Manifest.permission.POST_NOTIFICATIONS)
+ }
+ }
+ }
+
+ binding.btnConnectToServer.setOnClickListener {
+ val serverAddress = binding.etServerAddress.text.toString()
+ if (!requested && serverAddress.isNotBlank()){
+ validateUrl()
+ } else {
+ binding.etServerAddressLayout.error = "This Input cannot be blank"
+ }
+ }
+ configureFooterLinks()
+ }
+
+
+ private fun requestNotificationByDialog() {
+ val dialog = Dialog(this)
+ dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
+ dialog.setContentView(R.layout.dialog_alert)
+ dialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
+
+ val tvTitle: TextView = dialog.findViewById(R.id.title)
+ val tvMessage: TextView = dialog.findViewById(R.id.message)
+ val btnCancel: Button = dialog.findViewById(R.id.btnCancel)
+ val btnAccept: Button = dialog.findViewById(R.id.btnAccept)
+
+ btnCancel.text = getString(R.string.cancel)
+ btnCancel.setOnClickListener {
+ dialog.dismiss()
+ }
+ tvTitle.text = getString(R.string.allowNotificationsPermission)
+ tvMessage.text = getString(R.string.needNotificationsPermissionMessage)
+ btnAccept.text = getString(R.string.goToSettings)
+ btnAccept.setOnClickListener {
+ val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
+ intent.putExtra(Settings.EXTRA_APP_PACKAGE, this.packageName)
+ startActivity(intent)
+ dialog.dismiss()
+ }
+ dialog.show()
+ }
+
+ private fun configureFooterLinks() {
+ binding.btnGithubProject.setOnClickListener {
+ openLink(getString(R.string.githubProjectUrl))
+ }
+ binding.btnWiki.setOnClickListener {
+ openLink(getString(R.string.wikiUrl))
+ }
+ binding.btnDiscord.setOnClickListener {
+ openLink(getString(R.string.discordUrl))
+ }
+ binding.btnSteamProject.setOnClickListener {
+ openLink(getString(R.string.steamUrl))
+ }
+ }
+
+ private fun openLink(url: String) {
+ val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
+ startActivity(intent)
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/armatak/simtak/LocationSimulationService.kt b/app/src/main/java/com/armatak/simtak/LocationSimulationService.kt
new file mode 100644
index 0000000..937e891
--- /dev/null
+++ b/app/src/main/java/com/armatak/simtak/LocationSimulationService.kt
@@ -0,0 +1,237 @@
+package com.armatak.simtak
+
+import android.app.NotificationManager
+import android.app.Service
+import android.content.Intent
+import android.location.Location
+import android.location.LocationManager
+import android.location.provider.ProviderProperties
+import android.os.Binder
+import android.os.Build
+import android.os.Handler
+import android.os.IBinder
+import android.os.Looper
+import android.os.SystemClock
+import android.util.Log
+import com.armatak.simtak.core.Util.createNotificationChannel
+import com.armatak.simtak.core.Util.getActualTime
+import com.armatak.simtak.core.Util.getMockLocationStoppedNotification
+import com.armatak.simtak.core.Util.getNeedReconnectNotification
+import com.armatak.simtak.core.Util.getRunningNotification
+import com.armatak.simtak.core.Util.getServiceDestroyedNotification
+import com.armatak.simtak.core.Util.getStartedServiceNotification
+import com.armatak.simtak.trackerLog.data.models.ConnectionStatus
+import com.armatak.simtak.trackerLog.data.models.LogModel
+import com.armatak.simtak.trackerLog.data.models.LogTypes
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+import org.json.JSONObject
+
+private const val TAG = "LocationSimulationService"
+
+class LocationSimulationService : Service() {
+ private val binder = LocationMockBinder()
+ private lateinit var webSocketClient: WebSocketClient
+
+ private var logTrackerMutableList = mutableListOf()
+ private var lastLineId = 0
+
+ private var connectionAttemps = 0
+
+ private val _connectionStatus = MutableStateFlow(ConnectionStatus.InitialValue)
+ private val connectionStatus : StateFlow = _connectionStatus
+ private val _logTracker = MutableStateFlow(emptyList())
+ private val logTracker : StateFlow> = _logTracker
+
+ init {
+ Log.d(TAG, "$TAG, initialized")
+ }
+
+ private val socketListener = object : WebSocketClient.SocketListener {
+ override fun onMessage(message: String) {
+ try {
+ if (message[0] == '{') {
+ val jsonObject = JSONObject(message)
+ val latitude = jsonObject.getDouble("latitude")
+ val longitude = jsonObject.getDouble("longitude")
+ val bearing = jsonObject.getDouble("bearing")
+ simulateLocation(latitude, longitude, bearing)
+ addEntryToLog(message, LogTypes.Normal)
+ } else {
+ Log.e(TAG, "Non an JsonObject, text: $message")
+ addEntryToLog("Non an JsonObject, text: $message", LogTypes.Warning)
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, e.localizedMessage, e)
+ addEntryToLog(e.localizedMessage, LogTypes.Error)
+ }
+ }
+
+ }
+
+ fun addEntryToLog(message: String?, type: LogTypes) {
+ lastLineId += 1
+ logTrackerMutableList.add(
+ LogModel(
+ lastLineId,
+ getActualTime(),
+ message?:"null",
+ type
+ )
+ )
+ val newList = logTrackerMutableList.toList()
+ _logTracker.value = newList
+ }
+
+ fun getLog(): StateFlow> {
+ return logTracker
+ }
+ fun getConnectionStatus(): StateFlow {
+ return connectionStatus
+ }
+
+ fun connectToServer(url: String){
+ CoroutineScope(Dispatchers.IO).launch {
+ try {
+ connectionAttemps ++
+ webSocketClient = WebSocketClient.getInstance()
+ addEntryToLog("Server Address: $url", LogTypes.NetworkOperation)
+ webSocketClient.setSocketUrl(url)
+ webSocketClient.setListener(socketListener)
+ webSocketClient.connect()
+ _connectionStatus.value = ConnectionStatus.Connected
+ addEntryToLog("Connection Server Success", LogTypes.NetworkOperation)
+ } catch (e: Exception) {
+ Log.e(TAG, e.localizedMessage, e)
+ when (e.localizedMessage){
+ "Expected URL scheme 'http' or 'https' but no scheme was found for test u..." -> {
+ addEntryToLog("Expected ws:// or wss:// scheme, this is only for debug", LogTypes.Warning)
+ _connectionStatus.value = ConnectionStatus.Disconnected
+ }
+ else -> {
+ if (connectionAttemps < 6){
+ addEntryToLog("Attemp: $connectionAttemps \nError:${e.localizedMessage}", LogTypes.Warning)
+ _connectionStatus.value = ConnectionStatus.OnReconnect
+ Handler(Looper.getMainLooper()).postDelayed({
+ connectToServer(url)
+ }, 1500)
+ } else {
+ addEntryToLog("Exceed Connection Attemps", LogTypes.Error)
+ connectionAttemps = -1
+ _connectionStatus.value = ConnectionStatus.Awaiting
+ }
+ }
+ }
+ }
+ }
+ }
+
+ override fun onBind(intent: Intent): IBinder {
+ return binder
+ }
+
+
+ private fun simulateLocation(latitude: Double, longitude: Double, bearing: Double) {
+ val locationManager = baseContext.getSystemService(LOCATION_SERVICE) as LocationManager
+ // Create a Location Object
+ val location = Location(LocationManager.GPS_PROVIDER)
+ location.latitude = latitude
+ location.longitude = longitude
+ location.accuracy = 3f
+ location.altitude = 0.0
+ location.time = System.currentTimeMillis()
+ location.bearing = bearing.toFloat()
+ location.setBearingAccuracyDegrees(0.1F)
+ location.setVerticalAccuracyMeters(0.1F)
+ location.setSpeedAccuracyMetersPerSecond(0.01F)
+ location.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()
+ var powerUsage = 3
+ var accuracy = 5
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S){
+ powerUsage = ProviderProperties.POWER_USAGE_LOW
+ accuracy = ProviderProperties.ACCURACY_COARSE
+ }
+ //Create Test Provider
+ locationManager.addTestProvider(
+ LocationManager.GPS_PROVIDER,
+ false,
+ false,
+ false,
+ false,
+ false,
+ true,
+ true,
+ powerUsage,
+ accuracy
+ )
+ // Enable Mock Provider
+ locationManager.setTestProviderEnabled(LocationManager.GPS_PROVIDER, true)
+
+ // Mock Location on System
+ locationManager.setTestProviderLocation(LocationManager.GPS_PROVIDER, location)
+ }
+
+ fun stopByActivity() {
+ webSocketClient.disconnect()
+ _connectionStatus.value = ConnectionStatus.Disconnected
+ addEntryToLog("Connection Stopped", LogTypes.NetworkOperation)
+ val locationManager = baseContext.getSystemService(LOCATION_SERVICE) as LocationManager
+ locationManager.removeTestProvider(LocationManager.GPS_PROVIDER)
+ addEntryToLog("MockLocation Stopped", LogTypes.Warning)
+ this.stopSelf()
+ }
+
+ override fun onCreate() {
+ super.onCreate()
+ val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
+ createNotificationChannel(manager)
+ CoroutineScope(Dispatchers.Default).launch{
+ notifyStatusByPushNotification()
+ }
+ }
+
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ val notification = getStartedServiceNotification(this)
+ startForeground(5142, notification)
+ return START_STICKY
+ }
+
+ private suspend fun notifyStatusByPushNotification(){
+ getConnectionStatus().collectLatest { status ->
+ when(status){
+ ConnectionStatus.Awaiting -> {
+ val notification = getNeedReconnectNotification(this)
+ val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
+ manager.notify(5142, notification)
+ }
+ ConnectionStatus.Connected -> {
+ val notification = getRunningNotification(this)
+ val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
+ manager.notify(5142, notification)
+ }
+ ConnectionStatus.Disconnected -> {
+ val notification = getMockLocationStoppedNotification(this)
+ val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
+ manager.notify(5142, notification)
+ }
+ else -> {}
+ }
+ }
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ val notification = getServiceDestroyedNotification(this)
+ val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
+ manager.notify(5142, notification)
+ Log.e(TAG, "Service destroyed")
+ }
+
+ inner class LocationMockBinder: Binder(){
+ fun getService() = this@LocationSimulationService
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/armatak/simtak/ScannerActivity.kt b/app/src/main/java/com/armatak/simtak/ScannerActivity.kt
new file mode 100644
index 0000000..f0f085c
--- /dev/null
+++ b/app/src/main/java/com/armatak/simtak/ScannerActivity.kt
@@ -0,0 +1,114 @@
+package com.armatak.simtak
+
+import android.annotation.SuppressLint
+import android.content.Intent
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import android.view.SurfaceHolder
+import android.widget.Toast
+import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.lifecycleScope
+import com.armatak.simtak.databinding.ActivityScannerBinding
+import com.armatak.simtak.trackerLog.TrackerLogActivity
+import com.google.android.gms.vision.CameraSource
+import com.google.android.gms.vision.Detector
+import com.google.android.gms.vision.barcode.Barcode
+import com.google.android.gms.vision.barcode.BarcodeDetector
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import java.io.IOException
+
+class ScannerActivity : AppCompatActivity() {
+
+ private lateinit var binding: ActivityScannerBinding
+
+ private var tentativa = false
+ private lateinit var barcodeDetector: BarcodeDetector
+ private lateinit var cameraSource: CameraSource
+ var intentData = ""
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityScannerBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+ initScanBarcode()
+ }
+
+ private fun initScanBarcode() {
+ barcodeDetector = BarcodeDetector.Builder(this)
+ .setBarcodeFormats(Barcode.QR_CODE)
+ .build()
+ cameraSource = CameraSource.Builder(this, barcodeDetector)
+ .setRequestedPreviewSize(1080, 1080)
+ .setAutoFocusEnabled(true)
+ .setFacing(CameraSource.CAMERA_FACING_BACK)
+ .build()
+ binding.surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
+ @SuppressLint("MissingPermission")
+ override fun surfaceCreated(holder: SurfaceHolder) {
+ try {
+ cameraSource.start(binding.surfaceView.holder)
+ } catch (e: IOException) {
+ e.printStackTrace()
+ }
+ }
+
+ override fun surfaceChanged(
+ holder: SurfaceHolder,
+ format: Int,
+ width: Int,
+ height: Int
+ ) {
+
+ }
+
+ override fun surfaceDestroyed(holder: SurfaceHolder) {
+ cameraSource.stop()
+ }
+ })
+ barcodeDetector.setProcessor(object : Detector.Processor {
+ override fun release() {
+ Toast.makeText(applicationContext, "Scanner was stopped", Toast.LENGTH_SHORT).show()
+ }
+
+ override fun receiveDetections(detections: Detector.Detections) {
+ val barcodes = detections.detectedItems
+ if (barcodes.size() != 0) {
+ Thread.sleep(300)
+
+ intentData = barcodes.valueAt(0).displayValue
+
+
+ if (!tentativa) {
+ initTrackerActivity(intentData)
+ tentativa = true
+ }
+ }
+ }
+
+ })
+ }
+
+ private fun initTrackerActivity(url: String?) {
+ lifecycleScope.launch(Dispatchers.IO) {
+ if (url != null) {
+ val intent = Intent(this@ScannerActivity, TrackerLogActivity::class.java)
+ intent.putExtra("webSocketUrl", url)
+ startActivity(intent)
+ Handler(Looper.getMainLooper()).postDelayed(
+ { tentativa = false },
+ 2000
+ )
+ } else {
+ withContext(Dispatchers.Main) {
+ Toast.makeText(baseContext, "Try Again, Scan Error", Toast.LENGTH_SHORT).show()
+ }
+ Handler(Looper.getMainLooper()).postDelayed(
+ { tentativa = false },
+ 1500
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/armatak/simtak/WebSocketService.kt b/app/src/main/java/com/armatak/simtak/WebSocketService.kt
new file mode 100644
index 0000000..465b650
--- /dev/null
+++ b/app/src/main/java/com/armatak/simtak/WebSocketService.kt
@@ -0,0 +1,121 @@
+package com.armatak.simtak
+
+import android.util.Log
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import okhttp3.Response
+import okhttp3.WebSocketListener
+
+class WebSocketClient {
+ private lateinit var webSocket: okhttp3.WebSocket
+ private var socketListener: SocketListener? = null
+ private var socketUrl = ""
+ private var shouldReconnect = true
+ private var client: OkHttpClient? = null
+
+ companion object {
+ private lateinit var instance: WebSocketClient
+
+ @JvmStatic
+ @Synchronized
+ //This function gives singleton instance of WebSocket.
+ fun getInstance(): WebSocketClient {
+ synchronized(WebSocketClient::class) {
+ if (!::instance.isInitialized) {
+ instance = WebSocketClient()
+ }
+ }
+ return instance
+ }
+ }
+
+ fun setListener(listener: SocketListener) {
+ this.socketListener = listener
+ }
+
+ fun setSocketUrl(socketUrl: String) {
+ this.socketUrl = socketUrl
+ }
+
+ private fun initWebSocket() {
+ Log.e("socketCheck", "initWebSocket() socketurl = $socketUrl")
+ client = OkHttpClient()
+ val request = Request.Builder().url(url = socketUrl).build()
+ webSocket = client!!.newWebSocket(request, webSocketListener)
+ //this must me done else memory leak will be caused
+ client!!.dispatcher.executorService.shutdown()
+ }
+
+ fun connect() {
+ Log.e("socketCheck", "connect()")
+ shouldReconnect = true
+ initWebSocket()
+ }
+
+ fun reconnect() {
+ Log.e("socketCheck", "reconnect()")
+ initWebSocket()
+ }
+
+ //send
+
+ @Suppress("unused")
+ fun sendMessage(message: String) {
+ Log.e("socketCheck", "sendMessage($message)")
+ if (::webSocket.isInitialized) webSocket.send(message)
+ }
+
+
+ //We can close socket by two way:
+
+ //1. websocket.webSocket.close(1000, "Dont need connection")
+ //This attempts to initiate a graceful shutdown of this web socket.
+ //Any already-enqueued messages will be transmitted before the close message is sent but
+ //subsequent calls to send will return false and their messages will not be enqueued.
+
+ //2. websocket.cancel()
+ //This immediately and violently release resources held by this web socket,
+ //discarding any enqueued messages.
+
+ //Both does nothing if the web socket has already been closed or canceled.
+ fun disconnect() {
+ if (::webSocket.isInitialized) webSocket.close(1000, "Do not need connection anymore.")
+ shouldReconnect = false
+ }
+
+ interface SocketListener {
+ fun onMessage(message: String)
+ }
+
+
+ private val webSocketListener = object : WebSocketListener() {
+ //called when connection succeeded
+ //we are sending a message just after the socket is opened
+ override fun onOpen(webSocket: okhttp3.WebSocket, response: Response) {
+ Log.e("socketCheck", "onOpen()")
+ }
+
+ //called when text message received
+ override fun onMessage(webSocket: okhttp3.WebSocket, text: String) {
+ socketListener?.onMessage(text)
+ }
+
+ //called when binary message received
+ override fun onClosing(webSocket: okhttp3.WebSocket, code: Int, reason: String) {
+ Log.e("socketCheck", "onClosing()")
+ }
+
+ override fun onClosed(webSocket: okhttp3.WebSocket, code: Int, reason: String) {
+ //called when no more messages and the connection should be released
+ Log.e("socketCheck", "onClosed()")
+ if (shouldReconnect) reconnect()
+ }
+
+ override fun onFailure(
+ webSocket: okhttp3.WebSocket, t: Throwable, response: Response?
+ ) {
+ Log.e("socketCheck", "onFailure()")
+ if (shouldReconnect) reconnect()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/armatak/simtak/core/Util.kt b/app/src/main/java/com/armatak/simtak/core/Util.kt
new file mode 100644
index 0000000..7dd0d7a
--- /dev/null
+++ b/app/src/main/java/com/armatak/simtak/core/Util.kt
@@ -0,0 +1,65 @@
+package com.armatak.simtak.core
+
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.content.Context
+import androidx.core.app.NotificationCompat
+import com.armatak.simtak.R
+import java.time.LocalDateTime
+import java.time.format.DateTimeFormatter
+
+object Util {
+ const val CHANNEL_ID = "websocketChannel"
+ fun getActualTime(): String {
+ val now = LocalDateTime.now()
+ val formatter = DateTimeFormatter.ofPattern("HH:mm:ss:SSS")
+ return now.format(formatter)
+ }
+
+ fun createNotificationChannel(manager: NotificationManager) {
+ val websocketChannel = NotificationChannel(
+ CHANNEL_ID, "websocketChannelName",
+ NotificationManager.IMPORTANCE_HIGH
+ )
+ websocketChannel.description = "websocketChannelDescription"
+ websocketChannel.enableVibration(true)
+ manager.createNotificationChannel(websocketChannel)
+ }
+
+ fun getStartedServiceNotification(context: Context): Notification {
+ return NotificationCompat.Builder(context, CHANNEL_ID)
+ .setContentTitle("Service Started")
+ .setContentText("Service is ready to Start Tracking")
+ .setSmallIcon(R.drawable.appicon_simtak)
+ .build()
+ }
+ fun getRunningNotification(context: Context): Notification {
+ return NotificationCompat.Builder(context, CHANNEL_ID)
+ .setContentTitle("Mocking Location")
+ .setContentText("Service is running")
+ .setSmallIcon(R.drawable.appicon_simtak)
+ .build()
+ }
+
+ fun getNeedReconnectNotification(context: Context): Notification{
+ return NotificationCompat.Builder(context, CHANNEL_ID)
+ .setContentTitle("Need restart server connection")
+ .setContentText("Connection attempts failed. Check your network/server and try again")
+ .setSmallIcon(R.drawable.appicon_simtak)
+ .build()
+ }
+ fun getMockLocationStoppedNotification(context: Context): Notification{
+ return NotificationCompat.Builder(context, CHANNEL_ID)
+ .setContentTitle("Mock Location Stopped")
+ .setContentText("Disconnect from server")
+ .setSmallIcon(R.drawable.appicon_simtak)
+ .build()
+ }
+ fun getServiceDestroyedNotification(context: Context): Notification{
+ return NotificationCompat.Builder(context, CHANNEL_ID)
+ .setContentTitle("Service Destroyed")
+ .setSmallIcon(R.drawable.appicon_simtak)
+ .build()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/armatak/simtak/trackerLog/TrackerLogActivity.kt b/app/src/main/java/com/armatak/simtak/trackerLog/TrackerLogActivity.kt
new file mode 100644
index 0000000..4b14091
--- /dev/null
+++ b/app/src/main/java/com/armatak/simtak/trackerLog/TrackerLogActivity.kt
@@ -0,0 +1,277 @@
+package com.armatak.simtak.trackerLog
+
+import android.Manifest
+import android.app.Dialog
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.os.Build
+import android.os.Bundle
+import android.os.IBinder
+import android.view.Window
+import android.widget.Button
+import android.widget.TextView
+import android.widget.Toast
+import androidx.activity.enableEdgeToEdge
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.lifecycle.lifecycleScope
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.armatak.simtak.LocationSimulationService
+import com.armatak.simtak.R
+import com.armatak.simtak.databinding.ActivityTrackerLogBinding
+import com.armatak.simtak.trackerLog.data.adapters.AdapterLogTracker
+import com.armatak.simtak.trackerLog.data.models.ConnectionStatus
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+
+class TrackerLogActivity : AppCompatActivity() {
+ private var connectedToServer: Boolean = false
+ private lateinit var binding: ActivityTrackerLogBinding
+ private var url: String = ""
+ private lateinit var locationPermissionRequest : ActivityResultLauncher>
+
+ private var serviceStarted = false
+
+ private var rvAdapter = AdapterLogTracker()
+ private var lastThreeElementsAreVisible = false
+
+ private lateinit var mService: LocationSimulationService
+ private var mBound = false
+ private val mConnection = object : ServiceConnection{
+ override fun onServiceConnected(className: ComponentName?, binder : IBinder?) {
+ val service = binder as LocationSimulationService.LocationMockBinder
+ mService = service.getService()
+ mBound = true
+ initCollectors()
+ }
+
+ override fun onServiceDisconnected(name: ComponentName?) {
+ mBound = false
+ }
+
+ }
+
+ private fun initCollectors() {
+ lifecycleScope.launch {
+ if (mBound){
+ mService.getLog().collect { logModelList ->
+ rvAdapter.submitList(logModelList)
+ if (lastThreeElementsAreVisible){
+ binding.rvLogTracker.smoothScrollToPosition(rvAdapter.itemCount - 1)
+ }
+ }
+ }
+ }
+ lifecycleScope.launch {
+ if (mBound){
+ mService.getConnectionStatus().collectLatest {
+ when (it){
+ ConnectionStatus.Connected -> {
+ binding.txtServerConnectionStatus.text = getString(R.string.serverConnectionStatusPropertyFormat, "Connected")
+ }
+ ConnectionStatus.Disconnected -> {
+ binding.txtServerConnectionStatus.text = getString(R.string.serverConnectionStatusPropertyFormat, "Disconnected")
+ }
+ ConnectionStatus.OnReconnect -> {
+ binding.txtServerConnectionStatus.text = getString(R.string.serverConnectionStatusPropertyFormat, "OnReconnect")
+ }
+ ConnectionStatus.Awaiting -> {
+ binding.txtServerConnectionStatus.text = getString(R.string.serverConnectionStatusPropertyFormat, "Awaiting")
+ showConnectionErrorDialog()
+ }
+ ConnectionStatus.InitialValue -> {
+ binding.txtServerConnectionStatus.text = getString(R.string.serverConnectionStatusPropertyFormat, "Not Initialized")
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private fun showConnectionErrorDialog() {
+ val dialog = Dialog(this)
+ dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
+ dialog.setContentView(R.layout.dialog_alert)
+ dialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
+
+ val tvTitle: TextView = dialog.findViewById(R.id.title)
+ val tvMessage: TextView = dialog.findViewById(R.id.message)
+ val btnCancel: Button = dialog.findViewById(R.id.btnCancel)
+ val btnAccept: Button = dialog.findViewById(R.id.btnAccept)
+
+ btnCancel.text = getString(R.string.cancel)
+ btnCancel.setOnClickListener {
+ dialog.dismiss()
+ }
+ tvTitle.text = getString(R.string.serverConnectionProblems)
+ tvMessage.text = getString(R.string.errorDescriptionFormat, "Connection attempts failed. Check your network/server and try again")
+ btnAccept.text = getString(R.string.tryAgain)
+ btnAccept.setOnClickListener {
+ if(mBound) mService.connectToServer(url)
+ dialog.dismiss()
+ }
+ dialog.show()
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityTrackerLogBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+ enableEdgeToEdge()
+ ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, insets ->
+ val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
+ v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
+ insets
+ }
+
+ url = intent.getStringExtra("webSocketUrl") ?: ""
+
+ locationPermissionRequest = registerForActivityResult(
+ ActivityResultContracts.RequestMultiplePermissions()
+ ) { permissions ->
+ when {
+ permissions.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false) -> {
+ if(!serviceStarted){
+ Intent(this, LocationSimulationService::class.java).also {
+ startService(it)
+ bindService(it, mConnection, Context.BIND_AUTO_CREATE)
+ }
+ serviceStarted = true
+ }
+ if(mBound && !connectedToServer){
+ mService.connectToServer(url)
+ connectedToServer = true
+ }
+ }
+
+ permissions.getOrDefault(Manifest.permission.ACCESS_COARSE_LOCATION, false) -> {
+ if(!serviceStarted){
+ Intent(this, LocationSimulationService::class.java).also {
+ startService(it)
+ bindService(it, mConnection, Context.BIND_AUTO_CREATE)
+ }
+ serviceStarted = true
+ }
+ if(mBound && !connectedToServer){
+ mService.connectToServer(url)
+ connectedToServer = true
+ }
+ }
+ else -> {
+ Toast.makeText(baseContext, "Problems with permission", Toast.LENGTH_SHORT)
+ .show()
+ }
+ }
+ }
+
+ }
+
+ override fun onStart() {
+ super.onStart()
+ initUI()
+ }
+
+ private fun initUI() {
+
+ binding.txtServerAddress.text = getString(R.string.serverAddressPropertyFormat, url)
+
+ binding.btnBack.setOnClickListener {
+ if (!connectedToServer){
+ onBackPressedDispatcher.onBackPressed()
+ }
+ }
+
+ binding.startService.setOnClickListener {
+ val connectionStatus = if (mBound){
+ mService.getConnectionStatus().value
+ } else {
+ ConnectionStatus.InitialValue
+ }
+ when (connectionStatus){
+ ConnectionStatus.Awaiting -> requestLocationPermission()
+ ConnectionStatus.InitialValue -> requestLocationPermission()
+ ConnectionStatus.Disconnected -> requestLocationPermission()
+ else -> {
+ Toast.makeText(this, "SIMTAK Service is already running", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+ if (!serviceStarted){
+ binding.startService.performClick()
+ }
+ binding.stopService.setOnClickListener {
+ val connectionStatus = if (mBound){
+ mService.getConnectionStatus().value
+ } else {
+ ConnectionStatus.InitialValue
+ }
+ when (connectionStatus){
+ ConnectionStatus.Awaiting -> secureStopService()
+ ConnectionStatus.Connected -> secureStopService()
+ ConnectionStatus.OnReconnect -> secureStopService()
+ else -> {
+ Toast.makeText(this, "SIMTAK Service is already stopped", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+ setUpLogTrackerRV()
+ }
+
+ private fun setUpLogTrackerRV() {
+ val rv = binding.rvLogTracker
+ rv.adapter = rvAdapter
+ rv.layoutManager = LinearLayoutManager(this)
+ rv.addOnScrollListener(object : RecyclerView.OnScrollListener(){
+ override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
+ super.onScrolled(recyclerView, dx, dy)
+ val layoutManager = recyclerView.layoutManager as LinearLayoutManager?
+ layoutManager?.let {
+ val totalItemCount = it.itemCount
+ val lastVisibleItemPosition = it.findLastVisibleItemPosition()
+
+ lastThreeElementsAreVisible = totalItemCount - lastVisibleItemPosition <= 3
+ }
+ }
+ })
+ }
+
+ private fun secureStopService(){
+ if (mBound){
+ mService.stopByActivity()
+ connectedToServer = false
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ if (rvAdapter.itemCount -1 > 0){
+ binding.rvLogTracker.smoothScrollToPosition(rvAdapter.itemCount - 1)
+ }
+ }
+
+ private fun requestLocationPermission() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ locationPermissionRequest.launch(
+ arrayOf(
+ Manifest.permission.FOREGROUND_SERVICE_LOCATION,
+ Manifest.permission.ACCESS_FINE_LOCATION,
+ Manifest.permission.ACCESS_COARSE_LOCATION
+ )
+ )
+ }
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ mService.stopByActivity()
+ unbindService(mConnection)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/armatak/simtak/trackerLog/data/adapters/AdapterLogTracker.kt b/app/src/main/java/com/armatak/simtak/trackerLog/data/adapters/AdapterLogTracker.kt
new file mode 100644
index 0000000..3c95b0a
--- /dev/null
+++ b/app/src/main/java/com/armatak/simtak/trackerLog/data/adapters/AdapterLogTracker.kt
@@ -0,0 +1,39 @@
+package com.armatak.simtak.trackerLog.data.adapters
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import com.armatak.simtak.R
+import com.armatak.simtak.trackerLog.data.models.LogModel
+import com.armatak.simtak.trackerLog.data.viewHolder.ViewHolderLogTracker
+
+class AdapterLogTracker: ListAdapter(DIFF_CALLBACK) {
+ companion object{
+ private val DIFF_CALLBACK = object : DiffUtil.ItemCallback(){
+ override fun areItemsTheSame(oldItem: LogModel, newItem: LogModel): Boolean {
+ return oldItem.idLine == newItem.idLine
+ }
+
+ override fun areContentsTheSame(oldItem: LogModel, newItem: LogModel): Boolean {
+ return oldItem == newItem
+ }
+
+ }
+ }
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolderLogTracker {
+ val layoutInflater = LayoutInflater.from(parent.context)
+ return ViewHolderLogTracker(
+ layoutInflater.inflate(
+ R.layout.item_log_tracker,
+ parent,
+ false
+ )
+ )
+ }
+
+ override fun onBindViewHolder(holder: ViewHolderLogTracker, position: Int) {
+ val item = getItem(position)
+ holder.render(item)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/armatak/simtak/trackerLog/data/models/ConnectionStatus.kt b/app/src/main/java/com/armatak/simtak/trackerLog/data/models/ConnectionStatus.kt
new file mode 100644
index 0000000..ad4527c
--- /dev/null
+++ b/app/src/main/java/com/armatak/simtak/trackerLog/data/models/ConnectionStatus.kt
@@ -0,0 +1,9 @@
+package com.armatak.simtak.trackerLog.data.models
+
+sealed interface ConnectionStatus {
+ data object Connected : ConnectionStatus
+ data object Disconnected : ConnectionStatus
+ data object OnReconnect : ConnectionStatus
+ data object Awaiting : ConnectionStatus
+ data object InitialValue : ConnectionStatus
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/armatak/simtak/trackerLog/data/models/LogModel.kt b/app/src/main/java/com/armatak/simtak/trackerLog/data/models/LogModel.kt
new file mode 100644
index 0000000..0f0f20c
--- /dev/null
+++ b/app/src/main/java/com/armatak/simtak/trackerLog/data/models/LogModel.kt
@@ -0,0 +1,15 @@
+package com.armatak.simtak.trackerLog.data.models
+
+data class LogModel(
+ val idLine: Int,
+ val time: String,
+ val body: String,
+ val type: LogTypes
+)
+
+sealed interface LogTypes {
+ data object Error : LogTypes
+ data object Warning : LogTypes
+ data object Normal : LogTypes
+ data object NetworkOperation : LogTypes
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/armatak/simtak/trackerLog/data/viewHolder/ViewHolderLogTracker.kt b/app/src/main/java/com/armatak/simtak/trackerLog/data/viewHolder/ViewHolderLogTracker.kt
new file mode 100644
index 0000000..88882ef
--- /dev/null
+++ b/app/src/main/java/com/armatak/simtak/trackerLog/data/viewHolder/ViewHolderLogTracker.kt
@@ -0,0 +1,45 @@
+package com.armatak.simtak.trackerLog.data.viewHolder
+
+import android.view.View
+import androidx.recyclerview.widget.RecyclerView.ViewHolder
+import com.armatak.simtak.R
+import com.armatak.simtak.databinding.ItemLogTrackerBinding
+import com.armatak.simtak.trackerLog.data.models.LogModel
+import com.armatak.simtak.trackerLog.data.models.LogTypes
+
+class ViewHolderLogTracker(view: View) : ViewHolder(view) {
+ private val binding = ItemLogTrackerBinding.bind(view)
+
+ fun render(item: LogModel) {
+ val tvLogBody = binding.tvLogBody
+ val tvLogType = binding.tvLogType
+ binding.tvLogTime.text = item.time
+ tvLogBody.text = item.body
+
+ tvLogType.apply {
+ when (item.type) {
+ LogTypes.Error -> {
+ tvLogBody.setTextColor(context.getColor(R.color.errorColor))
+ setTextColor(context.getColor(R.color.errorColor))
+ text = context.getString(R.string.error)
+ }
+
+ LogTypes.NetworkOperation -> {
+ tvLogBody.setTextColor(context.getColor(R.color.networkColor))
+ setTextColor(context.getColor(R.color.networkColor))
+ text = context.getString(R.string.networkOperation)
+ }
+ LogTypes.Normal -> {
+ tvLogBody.setTextColor(context.getColor(R.color.darkGrey))
+ setTextColor(context.getColor(R.color.darkGrey))
+ text = context.getString(R.string.normal)
+ }
+ LogTypes.Warning -> {
+ tvLogBody.setTextColor(context.getColor(R.color.warningColor))
+ setTextColor(context.getColor(R.color.warningColor))
+ text = context.getString(R.string.warning)
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/aim_qrcode.xml b/app/src/main/res/drawable/aim_qrcode.xml
new file mode 100644
index 0000000..d16b161
--- /dev/null
+++ b/app/src/main/res/drawable/aim_qrcode.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/appicon_simtak.xml b/app/src/main/res/drawable/appicon_simtak.xml
new file mode 100644
index 0000000..d5ed791
--- /dev/null
+++ b/app/src/main/res/drawable/appicon_simtak.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/baseline_arrow_back_24.xml b/app/src/main/res/drawable/baseline_arrow_back_24.xml
new file mode 100644
index 0000000..1762766
--- /dev/null
+++ b/app/src/main/res/drawable/baseline_arrow_back_24.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/baseline_power_24.xml b/app/src/main/res/drawable/baseline_power_24.xml
new file mode 100644
index 0000000..06aff76
--- /dev/null
+++ b/app/src/main/res/drawable/baseline_power_24.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_discord.xml b/app/src/main/res/drawable/ic_discord.xml
new file mode 100644
index 0000000..d48d5d3
--- /dev/null
+++ b/app/src/main/res/drawable/ic_discord.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_github.xml b/app/src/main/res/drawable/ic_github.xml
new file mode 100644
index 0000000..5f76617
--- /dev/null
+++ b/app/src/main/res/drawable/ic_github.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_plug_connection.xml b/app/src/main/res/drawable/ic_plug_connection.xml
new file mode 100644
index 0000000..ee508fb
--- /dev/null
+++ b/app/src/main/res/drawable/ic_plug_connection.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_steam.xml b/app/src/main/res/drawable/ic_steam.xml
new file mode 100644
index 0000000..1fcf40a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_steam.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_wiki_book.xml b/app/src/main/res/drawable/ic_wiki_book.xml
new file mode 100644
index 0000000..1ffed89
--- /dev/null
+++ b/app/src/main/res/drawable/ic_wiki_book.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/font/archivo_black.ttf b/app/src/main/res/font/archivo_black.ttf
new file mode 100644
index 0000000..16f2d6c
Binary files /dev/null and b/app/src/main/res/font/archivo_black.ttf differ
diff --git a/app/src/main/res/font/roboto.ttf b/app/src/main/res/font/roboto.ttf
new file mode 100644
index 0000000..440843a
Binary files /dev/null and b/app/src/main/res/font/roboto.ttf differ
diff --git a/app/src/main/res/font/roboto_medium.ttf b/app/src/main/res/font/roboto_medium.ttf
new file mode 100644
index 0000000..3e87dbd
Binary files /dev/null and b/app/src/main/res/font/roboto_medium.ttf differ
diff --git a/app/src/main/res/layout/activity_home.xml b/app/src/main/res/layout/activity_home.xml
new file mode 100644
index 0000000..c25ecaf
--- /dev/null
+++ b/app/src/main/res/layout/activity_home.xml
@@ -0,0 +1,212 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_scanner.xml b/app/src/main/res/layout/activity_scanner.xml
new file mode 100644
index 0000000..34bb481
--- /dev/null
+++ b/app/src/main/res/layout/activity_scanner.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_tracker_log.xml b/app/src/main/res/layout/activity_tracker_log.xml
new file mode 100644
index 0000000..af8cf84
--- /dev/null
+++ b/app/src/main/res/layout/activity_tracker_log.xml
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_alert.xml b/app/src/main/res/layout/dialog_alert.xml
new file mode 100644
index 0000000..472d858
--- /dev/null
+++ b/app/src/main/res/layout/dialog_alert.xml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_log_tracker.xml b/app/src/main/res/layout/item_log_tracker.xml
new file mode 100644
index 0000000..6140792
--- /dev/null
+++ b/app/src/main/res/layout/item_log_tracker.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..c209e78
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b2dfe3d
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..4f0f1d6
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..62b611d
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..948a307
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..1b9a695
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..28d4b77
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9287f50
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..aa7d642
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9126ae3
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
new file mode 100644
index 0000000..01e6357
--- /dev/null
+++ b/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,7 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..a8c4a5a
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,13 @@
+
+
+ #FF000000
+ #FFFFFFFF
+ #FF262626
+ #FF828282
+ #FFdddddd
+ #FFCE4444
+ #1367BA
+ #FF9800
+ #6E3B23
+ #00000000
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..0c13b2e
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,40 @@
+
+ SIMTAK
+
+ Allow permission for notifications
+ For trust app working, need allow on system settings
+ Go to system settings
+ OK
+ Cancel
+ Try Again
+ Server Address
+ Scan the QR code in your briefing tab to get on the TAK Session
+ Welcome!, Insert the provided Server Address or Scan the QR code in your briefing tab to get on the TAK Session
+ SIMTAK - Simulation for Team Awareness Kit
+ Or
+ SCAN QR CODE
+ Connect to Provider Server Address Button
+ SIMTAK are part of the ARMATAK software bundle. \nARMATAK is currently in the development stage and is subject to unexpected bugs. Please use with caution.
+ Give us a Star on Github!
+ https://github.com/valmojr/armatak
+ https://github.com/valmojr/armatak/wiki
+ https://discord.gg/svK64PCycU
+ https://steamcommunity.com/sharedfiles/filedetails/?id=3301306282
+ Wiki
+ Join our Discord Group
+ Steam
+ Start Tracking
+ Stop Tracking
+ Server Address:
+ Server Address: %s
+ Connection Status:
+ Connection Status: %s
+ Back Button
+ Tracker Log
+ Oops, server has problem!
+ Error Description: \n%s
+ Normal
+ Network Operation
+ Warning
+ Error
+
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..565b4b0
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000..fa0f996
--- /dev/null
+++ b/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..0a6d946
--- /dev/null
+++ b/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/test/java/com/armatak/simtak/ExampleUnitTest.kt b/app/src/test/java/com/armatak/simtak/ExampleUnitTest.kt
new file mode 100644
index 0000000..14d9cef
--- /dev/null
+++ b/app/src/test/java/com/armatak/simtak/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.armatak.simtak
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..f6a8457
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,8 @@
+
+
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+plugins {
+ alias(libs.plugins.android.application) apply false
+ alias(libs.plugins.jetbrains.kotlin.android) apply false
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..20e2a01
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,23 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. For more details, visit
+# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
new file mode 100644
index 0000000..d68645c
--- /dev/null
+++ b/gradle/libs.versions.toml
@@ -0,0 +1,32 @@
+[versions]
+agp = "8.3.0"
+googleServices = "4.4.2"
+kotlin = "1.9.0"
+coreKtx = "1.12.0"
+junit = "4.13.2"
+junitVersion = "1.2.1"
+espressoCore = "3.6.1"
+appcompat = "1.7.0"
+material = "1.12.0"
+activity = "1.9.3"
+constraintlayout = "2.2.0"
+okhttp = "4.12.0"
+playServicesVision = "20.1.3"
+
+[libraries]
+androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
+google-services = { module = "com.google.gms:google-services", version.ref = "googleServices" }
+junit = { group = "junit", name = "junit", version.ref = "junit" }
+androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
+androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
+androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
+material = { group = "com.google.android.material", name = "material", version.ref = "material" }
+androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
+androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
+okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
+play-services-vision = { module = "com.google.android.gms:play-services-vision", version.ref = "playServicesVision" }
+
+[plugins]
+android-application = { id = "com.android.application", version.ref = "agp" }
+jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
+
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..e708b1c
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..ac304e6
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Thu Nov 28 01:11:21 BRT 2024
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..4f906e0
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..107acd3
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 0000000..b23052c
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,24 @@
+pluginManagement {
+ repositories {
+ google {
+ content {
+ includeGroupByRegex("com\\.android.*")
+ includeGroupByRegex("com\\.google.*")
+ includeGroupByRegex("androidx.*")
+ }
+ }
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+rootProject.name = "SIMTAK"
+include(":app")
+
\ No newline at end of file