Socket connection now works

- Pairing a new device works

(I did a lot since the last commit)
This commit is contained in:
2021-11-14 01:42:21 +01:00
parent 28e85ea730
commit 19b7c05d75
72 changed files with 5861 additions and 23719 deletions

122
android/.idea/codeStyles/Project.xml generated Normal file
View File

@@ -0,0 +1,122 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>

View File

@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="1.8" />
<bytecodeTargetLevel target="11" />
</component>
</project>

View File

@@ -4,7 +4,7 @@
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="PLATFORM" />
<option name="testRunner" value="GRADLE" />
<option name="disableWrapperSourceDistributionNotification" value="true" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
@@ -16,7 +16,6 @@
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
<option name="useQualifiedModuleNames" value="true" />
</GradleProjectSettings>
</option>
</component>

13
android/.idea/misc.xml generated
View File

@@ -1,6 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="DesignSurface">
<option name="filePathToZoomLevelMap">
<map>
<entry key="app/src/main/res/layout/activity_main.xml" value="0.22291666666666668" />
<entry key="app/src/main/res/layout/activity_setup.xml" value="0.3578125" />
<entry key="app/src/main/res/layout/content_setup.xml" value="0.3578125" />
<entry key="app/src/main/res/layout/input_dialog.xml" value="0.19791666666666666" />
<entry key="app/src/main/res/layout/setup.xml" value="0.33" />
</map>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View File

@@ -21,6 +21,7 @@ android {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
debuggable false
}
}
compileOptions {
@@ -30,10 +31,17 @@ android {
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
viewBinding true
}
}
dependencies {
implementation ('io.socket:socket.io-client:2.0.1') {
// excluding org.json which is provided by Android
exclude group: 'org.json', module: 'json'
}
implementation fileTree(dir: 'libs', include: ['lineage-sdk.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.1.0'
@@ -46,6 +54,6 @@ dependencies {
implementation 'com.rabbitmq:amqp-client:5.9.0'
implementation "com.squareup.okhttp3:okhttp:4.9.0"
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

Binary file not shown.

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="de.nicolasklier.livebeat">
package="de.nicolasklier.livebeat" >
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
@@ -19,18 +19,26 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:usesCleartextTraffic="true"
android:theme="@style/Theme.Livebeat">
android:theme="@style/Theme.Livebeat"
android:usesCleartextTraffic="true" >
<activity
android:name=".SetupActivity"
android:exported="false"
android:label="@string/title_activity_setup"
android:theme="@style/Theme.Livebeat.NoActionBar" />
<service android:name=".TrackerService" />
<receiver android:name=".BootReceiver">
<intent-filter >
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<receiver android:name=".BootReceiver" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:theme="@style/Theme.Livebeat.NoActionBar">
android:theme="@style/Theme.Livebeat.NoActionBar" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View File

@@ -10,16 +10,13 @@ import android.content.pm.PackageManager
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.provider.Settings
import android.telephony.TelephonyManager
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.Button
import android.widget.TextView
import androidx.annotation.ColorInt
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
@@ -27,11 +24,9 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.snackbar.Snackbar
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import java.util.logging.Logger
@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
class MainActivity : AppCompatActivity() {
@@ -46,6 +41,15 @@ class MainActivity : AppCompatActivity() {
@SuppressLint("HardwareIds")
fun checkIfPhoneIsRegistered() {
val pref = this.getPreferences(Context.MODE_PRIVATE) ?: return
val accessToken = pref.getString("accessToken", "");
// App is not setup
if (accessToken == "") {
val intent = Intent(baseContext, SetupActivity::class.java);
startActivity(intent);
}
if (TOKEN == "") return;
Thread(Runnable {
val androidId = Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID)
@@ -95,8 +99,6 @@ class MainActivity : AppCompatActivity() {
setContentView(R.layout.activity_main)
setSupportActionBar(findViewById(R.id.toolbar))
val process = Runtime.getRuntime().exec("su")
// Check authorization
val backendChecks = Thread(Runnable {
val username = findViewById<TextView>(R.id.username).text
@@ -142,7 +144,7 @@ class MainActivity : AppCompatActivity() {
USER = jsonToUser.fromJson(userInfoResponseBody)
// Only start service if authentication went good.
startService(Intent(this, TrackerService::class.java))
// startService(Intent(this, TrackerService::class.java))
Snackbar.make(findViewById<FloatingActionButton>(R.id.fab), "Login succeeded", Snackbar.LENGTH_SHORT)
.setBackgroundTint(Color.GREEN)
@@ -174,14 +176,6 @@ class MainActivity : AppCompatActivity() {
val statusRabbit = intent.getBooleanExtra("statusRabbit", false)
val statusHttp = intent.getIntExtra("statusHttp", 404)
if (statusRabbit) {
findViewById<TextView>(R.id.rabbitStatus).text = "connected"
findViewById<TextView>(R.id.rabbitStatus).setTextColor(Color.GREEN)
} else {
findViewById<TextView>(R.id.rabbitStatus).text = "disconnected"
findViewById<TextView>(R.id.rabbitStatus).setTextColor(Color.RED)
}
/*if (statusHttp == 200) {
findViewById<TextView>(R.id.httpStatus).text = "ONLINE (no login)"
findViewById<TextView>(R.id.httpStatus).setTextColor(Color.CYAN)
@@ -203,6 +197,14 @@ class MainActivity : AppCompatActivity() {
}
}
@ColorInt
private fun getAccentColor(): Int {
val attr = intArrayOf(android.R.attr.colorAccent)
val typedArray = obtainStyledAttributes(android.R.style.Theme_DeviceDefault, attr)
return typedArray.getColor(0, Color.BLACK)
.also { typedArray.recycle() }
}
private fun checkPerms() {
if (ActivityCompat.checkSelfPermission(
this,

View File

@@ -22,12 +22,21 @@ class Phone(
val architecture: String
) {}
class PhoneRegistration (
val phone: Phone,
val token: String
) {}
class PhoneSubmitPairCode (
val phoneId: String,
val code: String
) {}
class User(
val name: String,
val type: String,
val lastLogin: String,
val twoFASecret: String?,
val brokerToken: String,
val createdAt: String
)

View File

@@ -0,0 +1,195 @@
package de.nicolasklier.livebeat
import android.content.Context
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.util.Log
import android.view.View
import android.widget.*
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.ui.AppBarConfiguration
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import de.nicolasklier.livebeat.databinding.ActivitySetupBinding
import de.nicolasklier.livebeat.dialogs.ErrorDialog
import de.nicolasklier.livebeat.dialogs.InputDialog
import io.socket.client.IO
import io.socket.client.Socket
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject
import java.lang.Exception
class SetupActivity : AppCompatActivity() {
private lateinit var appBarConfiguration: AppBarConfiguration
private lateinit var binding: ActivitySetupBinding
private var host = ""
private var username = ""
private var password = ""
private var token = ""
private lateinit var socket: Socket;
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivitySetupBinding.inflate(layoutInflater)
setContentView(binding.root)
findViewById<Button>(R.id.btnLogin).setOnClickListener {
kotlin.run {
host = findViewById<EditText>(R.id.inputServerIp).text.toString()
username = findViewById<EditText>(R.id.inputUsername).text.toString()
password = findViewById<EditText>(R.id.inputPassword).text.toString()
findViewById<LinearLayout>(R.id.connectLayout).visibility = View.VISIBLE;
tryConnect()
}
}
}
private fun updateStatus(text: String) {
runOnUiThread {
findViewById<TextView>(R.id.connectStatus).text = text;
}
}
private fun throwError(text: String) {
val dialog = ErrorDialog(text)
dialog.show(supportFragmentManager, "");
runOnUiThread {
findViewById<LinearLayout>(R.id.connectLayout).visibility = View.GONE;
}
}
private fun tryConnect() {
updateStatus("Connect to backend");
// Connection values
val options = IO.Options.builder()
.setTransports(arrayOf("websocket"))
.build();
try {
socket = IO.socket("http://$host:8040", options)
socket.connect()
} catch (e: Exception) {
updateStatus("Failed to connect");
Log.e("Socket.io", "Unable to connect to socket: ${e.message}")
return;
}
socket.on("connect") { args ->
run {
updateStatus("Connected")
login();
}
}
}
private fun login() {
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
val jsonToLogin = moshi.adapter(Login::class.java)
val client = OkHttpClient()
val req = Request.Builder()
.url("${MainActivity.API_URL}/user/login")
.post(
("{ \"username\": \"" + username + "\"," +
"\"password\": \"" + password + "\" }").toRequestBody()
)
.header("Content-Type", "application/json")
.build()
val loginResponse = client.newCall(req).execute()
val responseBody = loginResponse.body!!.string()
if (loginResponse.code == 200) {
token = jsonToLogin.fromJson(responseBody)!!.token
requestAccess()
} else {
throwError("Username or password is wrong.")
}
}
private fun requestAccess() {
var phoneId = "";
val androidId = Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID)
val phone = Phone(
androidId,
Build.MODEL,
Build.PRODUCT,
Build.VERSION.BASE_OS + " " + Build.VERSION.RELEASE + " " + Build.VERSION.CODENAME,
System.getProperty("os.arch")
)
val phoneRegistration = PhoneRegistration(
phone,
token
)
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
val phoneToJson = moshi.adapter(PhoneRegistration::class.java)
val json = phoneToJson.toJson(phoneRegistration)
socket.emit("requestAccess", json)
updateStatus("Await new phone id")
// We received a response from the backend containing the phone id.
socket.on("requestAccess") { args ->
run {
phoneId = (args[0] as JSONObject).getString("phoneId");
updateStatus("Await user to enter pair code")
fun promptUser() {
val dialog = InputDialog("Pair code" , "Look into your device overview to get the pair code.") { choice, code ->
if (choice == InputDialog.UserChoice.CANCEL) {
updateStatus("Process has been canceled by user.")
return@InputDialog;
}
updateStatus("Validate code $code for $phoneId")
val submitCode = PhoneSubmitPairCode(
phoneId,
code
);
val submitCodeToJson = moshi.adapter(PhoneSubmitPairCode::class.java)
val submitCodeJson = submitCodeToJson.toJson(submitCode)
socket.emit("submitPairCode", submitCodeJson);
}
dialog.show(supportFragmentManager, "");
}
// The backend only calls this event again if the code is incorrect. Otherwise `accessGranted` is called.
socket.on("submitPairCode") { args ->
run {
if ((args[0] as String) == "") {
updateStatus("Code has been incorrect")
// Prompt user to enter code as long as it's invalid
promptUser()
} else {
// If response is not empty, we received a token (let's just hope that)
updateStatus("Code has been correct")
val sharedPref = this.getPreferences(Context.MODE_PRIVATE) ?: return@on
with (sharedPref.edit()) {
putString("accessToken", args[0] as String)
commit()
}
// Return to previous activity.
this.finish()
}
}
}
promptUser()
}
}
}
}

View File

@@ -16,19 +16,27 @@ import android.provider.Settings
import android.util.Log
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationCompat.VISIBILITY_PRIVATE
import androidx.core.app.NotificationCompat.VISIBILITY_SECRET
import com.rabbitmq.client.Channel
import com.rabbitmq.client.Connection
import com.rabbitmq.client.ConnectionFactory
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import io.socket.client.IO
import io.socket.client.Socket
import io.socket.emitter.Emitter
import okhttp3.EventListener
import okhttp3.OkHttpClient
import okhttp3.Request
import java.io.IOException
import java.lang.Exception
import java.util.concurrent.TimeoutException
class TrackerService : Service() {
var isSocketConnected = false;
val conn = arrayOfNulls<Connection>(1)
val channel = arrayOfNulls<Channel>(1)
@@ -36,35 +44,47 @@ class TrackerService : Service() {
return null
}
private fun connectWithBroker() {
private fun connectSocket() {
// This thread only connects to RabbitMQ
val connectionThread = Thread(Runnable {
val client = OkHttpClient()
val socket: Socket;
// Connection values
val options = IO.Options.builder()
.setTransports(arrayOf("websocket"))
.build();
val factory = ConnectionFactory()
factory.username = MainActivity.USER!!.name
factory.password = MainActivity.USER!!.brokerToken
factory.virtualHost = "/"
factory.host = "192.168.178.26"
factory.port = 5672
factory.isAutomaticRecoveryEnabled = true
try {
conn[0] = factory.newConnection()
channel[0] = conn[0]?.createChannel()
socket = IO.socket("http://192.168.178.26:8040", options)
socket.connect()
} catch (e: Exception) {
Log.e("Socket.io", "Unable to connect to socket: ${e.message}")
return@Runnable
}
val intent = Intent("de.nicolasklier.livebeat")
val bundle = Bundle()
bundle.putBoolean("statusRabbit", true)
intent.putExtras(bundle)
this.sendBroadcast(intent)
socket.on("test") { args ->
run {
Log.i("Socket.io", args[0].toString());
}
}
channel[0]?.queueDeclare("tracker-" + factory.username, true, false, false, null)
//channel[0]?.basicPublish("", "Tracker", null, "Test message".toByteArray())
Log.i("RabbitMQ", "run: Published test message")
} catch (e: IOException) {
e.printStackTrace()
} catch (e: TimeoutException) {
e.printStackTrace()
socket.on("connect") { args ->
run {
isSocketConnected = true;
}
}
socket.on("disconnect") { args ->
run {
isSocketConnected = false;
}
}
if (socket.connected()) {
socket.emit("test", "This is a message from my phone!")
Log.i("Socket.io", "Published test message")
} else {
Log.e("Socket.io", "Cannot send test message because I'm not connected with the Socket.io server.")
}
})
connectionThread.start()
@@ -105,13 +125,15 @@ class TrackerService : Service() {
}
}
connectWithBroker()
startForeground()
connectSocket()
start()
return super.onStartCommand(intent, flags, startId)
}
private fun startForeground() {
val noticicationIntent = Intent(this, MainActivity::class.java)
private fun start() {
val noticicationIntent = Intent(this, MainActivity::class.java).apply {
action = "Stop"
}
val pendingIntent = PendingIntent.getActivity(this, 0, noticicationIntent, 0)
val chan = NotificationChannel(
NOTIF_CHANNEL_ID,
@@ -121,17 +143,24 @@ class TrackerService : Service() {
chan.lockscreenVisibility = Notification.VISIBILITY_SECRET
val manager = (getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager)
manager.createNotificationChannel(chan)
startForeground(NOTIF_ID, NotificationCompat.Builder(this, NOTIF_CHANNEL_ID)
.setOngoing(true)
.setContentTitle("Livebeat")
.setContentText("Tracker is running")
.setContentIntent(pendingIntent)
.setCategory(Notification.CATEGORY_SERVICE)
.setPriority(NotificationManager.IMPORTANCE_LOW)
.setChannelId(NOTIF_CHANNEL_ID)
.setColorized(true)
.setColor(Color.BLACK)
.build())
val notification = NotificationCompat.Builder(this, NOTIF_CHANNEL_ID)
.setOngoing(true)
.setContentTitle("Livebeat")
.setContentText("Tracker is running")
.setContentIntent(pendingIntent)
.setCategory(Notification.CATEGORY_SERVICE)
.setPriority(NotificationManager.IMPORTANCE_LOW)
.setSmallIcon(R.mipmap.ic_launcher)
.setChannelId(NOTIF_CHANNEL_ID)
.setColorized(true)
.setShowWhen(false)
.setVisibility(VISIBILITY_SECRET)
.setColor(Color.BLACK)
.addAction(R.drawable.ic_launcher_background, "Stop", pendingIntent)
.build()
manager.notify(0, notification)
}
companion object {

View File

@@ -0,0 +1,23 @@
package de.nicolasklier.livebeat.dialogs
import android.app.AlertDialog
import android.app.Dialog
import android.os.Bundle
import androidx.fragment.app.DialogFragment
class ErrorDialog(val message: String) : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
val builder = AlertDialog.Builder(it)
builder.setMessage(message)
.setPositiveButton("Ok"
) { dialog, _ ->
dialog.cancel()
}
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
}

View File

@@ -0,0 +1,43 @@
package de.nicolasklier.livebeat.dialogs
import android.app.AlertDialog
import android.app.Dialog
import android.os.Bundle
import android.widget.EditText
import androidx.fragment.app.DialogFragment
import de.nicolasklier.livebeat.R
import de.nicolasklier.livebeat.User
class InputDialog(val title: String, val message: String, val callback: (UserChoice, String) -> Unit) : DialogFragment() {
enum class UserChoice {
CANCEL,
SUBMIT
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
val inflater = requireActivity().layoutInflater;
val view = inflater.inflate(R.layout.input_dialog, null);
val builder = AlertDialog.Builder(it)
builder
.setMessage(message)
.setTitle(title)
.setCancelable(false)
.setView(view)
.setNegativeButton("Cancel"
) { dialog, _ ->
callback(UserChoice.CANCEL, "")
dialog.cancel()
}
.setPositiveButton("Submit"
) { dialog, _ ->
callback(UserChoice.SUBMIT, view.findViewById<EditText>(R.id.input).text.toString());
}
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
}

View File

@@ -64,36 +64,6 @@
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<TextView
android:id="@+id/rabbitText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|center_vertical"
android:layout_weight="0"
android:text="RabbitMQ"
android:textColor="@color/white" />
<TextView
android:id="@+id/rabbitStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:layout_weight="0"
android:fontFamily="sans-serif-black"
android:text="disconnected"
android:textAlignment="center"
android:textAllCaps="true"
android:textColor="@color/orange"
android:textSize="14sp" />
</LinearLayout>
<Space
android:layout_width="match_parent"
android:layout_height="120dp" />

View File

@@ -0,0 +1,119 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".SetupActivity">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="176dp"
android:layout_marginTop="51dp"
android:layout_marginEnd="177dp"
android:text="Connect"
android:textColor="@color/white"
android:textSize="34sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="@+id/linearLayout2"
android:layout_width="331dp"
android:layout_height="wrap_content"
android:layout_marginTop="200dp"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<EditText
android:id="@+id/inputServerIp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:hint="Server IP"
android:inputType="textPersonName"
android:text="192.168.178.26" />
<EditText
android:id="@+id/inputUsername"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:hint="Username"
android:inputType="textPersonName"
android:text="admin"
android:textColor="@color/white" />
<EditText
android:id="@+id/inputPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:hint="Password"
android:inputType="textPassword"
android:text="$1KDaNCDlyXAOg"
android:textColor="@color/white" />
</LinearLayout>
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="174dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="179dp"
android:text="You'll need to login to a Livebeat server instance."
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.489"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
<Button
android:id="@+id/btnLogin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="161dp"
android:layout_marginTop="85dp"
android:layout_marginEnd="162dp"
android:text="Login"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/linearLayout2" />
<LinearLayout
android:id="@+id/connectLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="invisible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btnLogin">
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="181dp"
android:layout_marginTop="39dp"
android:layout_marginEnd="182dp"
android:progressTint="@color/white"
android:visibility="visible" />
<TextView
android:id="@+id/connectStatus"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:textAlignment="center" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<EditText
android:id="@+id/input"
android:inputType="number"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:layout_marginBottom="16dp"
android:fontFamily="sans-serif"/>
</LinearLayout>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph"
app:startDestination="@id/FirstFragment">
<fragment
android:id="@+id/FirstFragment"
android:name="de.nicolasklier.livebeat.FirstFragment"
android:label="@string/first_fragment_label"
tools:layout="@layout/fragment_first" >
<action
android:id="@+id/action_FirstFragment_to_SecondFragment"
app:destination="@id/SecondFragment" />
</fragment>
<fragment
android:id="@+id/SecondFragment"
android:name="de.nicolasklier.livebeat.SecondFragment"
android:label="@string/second_fragment_label"
tools:layout="@layout/fragment_second" >
<action
android:id="@+id/action_SecondFragment_to_FirstFragment"
app:destination="@id/FirstFragment" />
</fragment>
</navigation>

View File

@@ -0,0 +1,3 @@
<resources>
<dimen name="fab_margin">48dp</dimen>
</resources>

View File

@@ -0,0 +1,3 @@
<resources>
<dimen name="fab_margin">200dp</dimen>
</resources>

View File

@@ -0,0 +1,3 @@
<resources>
<dimen name="fab_margin">48dp</dimen>
</resources>

View File

@@ -9,4 +9,5 @@
<string name="hello_first_fragment">Hello first fragment</string>
<string name="hello_second_fragment">Hello second fragment. Arg: %1$s</string>
<string name="title_activity_setup">Setup</string>
</resources>

View File

@@ -1,6 +1,6 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.Livebeat" parent="Theme.AppCompat.Light">
<style name="Theme.Livebeat" parent="Theme.MaterialComponents.DayNight">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/black</item>
<item name="colorPrimaryVariant">@color/black2</item>

View File

@@ -7,7 +7,7 @@ buildscript {
}
dependencies {
classpath "com.android.tools.build:gradle:4.1.0"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.20"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files