commit 80f49044f61c896e29b5b7b0fd2cb5708089c3e7 Author: andonyth Date: Thu Dec 5 03:13:06 2024 -0300 Initial Commit SIMTAK 1.0 Stable Version Developed to use with Armatak addon for ARMA 3 Copyright ©2024 Valmo Trindade All rights reserved. Take a look on the main project: https://github.com/valmojr/armatak diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties 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/README.md b/README.md new file mode 100644 index 0000000..d32954f --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +## SIMTAK 1.0 Stable Version + +Developed to use with Armatak addon for ARMA 3 +Copyright ©2024 Valmo Trindade +All rights reserved. + +Take a look on the main project: \ 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..8feea25 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file 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