diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000..c68d202
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,4 @@
+@davkutalek @jumaallan
+
+## code changes will send PR to following users
+* @davkutalek @jumaallan
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000..916d456
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,18 @@
+# Scope
+
+_Please explain what your feature does in a short paragraph._ please check the below boxes
+- [ ] I have followed the coding conventions
+- [ ] I have added/updated necessary tests
+- [ ] I have tested the changes added on a physical device
+- [ ] I have run `./codeAnalysis.sh` to make sure all lint/formatting checks have been done.
+
+## Closes/Fixes Issues
+_Declare any issues by typing `fixes #1` or `closes #1` for example so that the automation can kick
+in when this is merged_
+
+## Other testing QA Notes
+_What have you tested specifically and what possible impacts/areas there are that may need retesting
+by others._
+
+Please add a screenshot (if necessary)
+
diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml
new file mode 100644
index 0000000..a176d97
--- /dev/null
+++ b/.github/workflows/build_and_test.yml
@@ -0,0 +1,30 @@
+name: Build and test
+
+on:
+ pull_request:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ build_and_test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Bundle install
+ run: bundle install
+
+ - name: Static check
+ run: bundle exec fastlane staticCheck
+
+ - name: Unit tests
+ run: bundle exec fastlane unitTest
+
+ - name: Upload test report
+ uses: actions/upload-artifact@v3
+ if: failure()
+ with:
+ name: junit_test_report_${{ env.GITHUB_REF }}_${{ env.GITHUB_RUN_ID }}
+ path: multisim/build/reports/tests
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 0000000..7a118b4
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,3 @@
+source "https://rubygems.org"
+
+gem "fastlane"
diff --git a/Gemfile.lock b/Gemfile.lock
new file mode 100644
index 0000000..22e4779
--- /dev/null
+++ b/Gemfile.lock
@@ -0,0 +1,218 @@
+GEM
+ remote: https://rubygems.org/
+ specs:
+ CFPropertyList (3.0.5)
+ rexml
+ addressable (2.8.1)
+ public_suffix (>= 2.0.2, < 6.0)
+ artifactory (3.0.15)
+ atomos (0.1.3)
+ aws-eventstream (1.2.0)
+ aws-partitions (1.642.0)
+ aws-sdk-core (3.158.0)
+ aws-eventstream (~> 1, >= 1.0.2)
+ aws-partitions (~> 1, >= 1.525.0)
+ aws-sigv4 (~> 1.1)
+ jmespath (~> 1, >= 1.6.1)
+ aws-sdk-kms (1.58.0)
+ aws-sdk-core (~> 3, >= 3.127.0)
+ aws-sigv4 (~> 1.1)
+ aws-sdk-s3 (1.114.0)
+ aws-sdk-core (~> 3, >= 3.127.0)
+ aws-sdk-kms (~> 1)
+ aws-sigv4 (~> 1.4)
+ aws-sigv4 (1.5.2)
+ aws-eventstream (~> 1, >= 1.0.2)
+ babosa (1.0.4)
+ claide (1.1.0)
+ colored (1.2)
+ colored2 (3.1.2)
+ commander (4.6.0)
+ highline (~> 2.0.0)
+ declarative (0.0.20)
+ digest-crc (0.6.4)
+ rake (>= 12.0.0, < 14.0.0)
+ domain_name (0.5.20190701)
+ unf (>= 0.0.5, < 1.0.0)
+ dotenv (2.8.1)
+ emoji_regex (3.2.3)
+ excon (0.93.0)
+ faraday (1.10.2)
+ faraday-em_http (~> 1.0)
+ faraday-em_synchrony (~> 1.0)
+ faraday-excon (~> 1.1)
+ faraday-httpclient (~> 1.0)
+ faraday-multipart (~> 1.0)
+ faraday-net_http (~> 1.0)
+ faraday-net_http_persistent (~> 1.0)
+ faraday-patron (~> 1.0)
+ faraday-rack (~> 1.0)
+ faraday-retry (~> 1.0)
+ ruby2_keywords (>= 0.0.4)
+ faraday-cookie_jar (0.0.7)
+ faraday (>= 0.8.0)
+ http-cookie (~> 1.0.0)
+ faraday-em_http (1.0.0)
+ faraday-em_synchrony (1.0.0)
+ faraday-excon (1.1.0)
+ faraday-httpclient (1.0.1)
+ faraday-multipart (1.0.4)
+ multipart-post (~> 2)
+ faraday-net_http (1.0.1)
+ faraday-net_http_persistent (1.2.0)
+ faraday-patron (1.0.0)
+ faraday-rack (1.0.0)
+ faraday-retry (1.0.3)
+ faraday_middleware (1.2.0)
+ faraday (~> 1.0)
+ fastimage (2.2.6)
+ fastlane (2.210.1)
+ CFPropertyList (>= 2.3, < 4.0.0)
+ addressable (>= 2.8, < 3.0.0)
+ artifactory (~> 3.0)
+ aws-sdk-s3 (~> 1.0)
+ babosa (>= 1.0.3, < 2.0.0)
+ bundler (>= 1.12.0, < 3.0.0)
+ colored
+ commander (~> 4.6)
+ dotenv (>= 2.1.1, < 3.0.0)
+ emoji_regex (>= 0.1, < 4.0)
+ excon (>= 0.71.0, < 1.0.0)
+ faraday (~> 1.0)
+ faraday-cookie_jar (~> 0.0.6)
+ faraday_middleware (~> 1.0)
+ fastimage (>= 2.1.0, < 3.0.0)
+ gh_inspector (>= 1.1.2, < 2.0.0)
+ google-apis-androidpublisher_v3 (~> 0.3)
+ google-apis-playcustomapp_v1 (~> 0.1)
+ google-cloud-storage (~> 1.31)
+ highline (~> 2.0)
+ json (< 3.0.0)
+ jwt (>= 2.1.0, < 3)
+ mini_magick (>= 4.9.4, < 5.0.0)
+ multipart-post (~> 2.0.0)
+ naturally (~> 2.2)
+ optparse (~> 0.1.1)
+ plist (>= 3.1.0, < 4.0.0)
+ rubyzip (>= 2.0.0, < 3.0.0)
+ security (= 0.1.3)
+ simctl (~> 1.6.3)
+ terminal-notifier (>= 2.0.0, < 3.0.0)
+ terminal-table (>= 1.4.5, < 2.0.0)
+ tty-screen (>= 0.6.3, < 1.0.0)
+ tty-spinner (>= 0.8.0, < 1.0.0)
+ word_wrap (~> 1.0.0)
+ xcodeproj (>= 1.13.0, < 2.0.0)
+ xcpretty (~> 0.3.0)
+ xcpretty-travis-formatter (>= 0.0.3)
+ gh_inspector (1.1.3)
+ google-apis-androidpublisher_v3 (0.29.0)
+ google-apis-core (>= 0.9.0, < 2.a)
+ google-apis-core (0.9.0)
+ addressable (~> 2.5, >= 2.5.1)
+ googleauth (>= 0.16.2, < 2.a)
+ httpclient (>= 2.8.1, < 3.a)
+ mini_mime (~> 1.0)
+ representable (~> 3.0)
+ retriable (>= 2.0, < 4.a)
+ rexml
+ webrick
+ google-apis-iamcredentials_v1 (0.15.0)
+ google-apis-core (>= 0.9.0, < 2.a)
+ google-apis-playcustomapp_v1 (0.11.0)
+ google-apis-core (>= 0.9.0, < 2.a)
+ google-apis-storage_v1 (0.19.0)
+ google-apis-core (>= 0.9.0, < 2.a)
+ google-cloud-core (1.6.0)
+ google-cloud-env (~> 1.0)
+ google-cloud-errors (~> 1.0)
+ google-cloud-env (1.6.0)
+ faraday (>= 0.17.3, < 3.0)
+ google-cloud-errors (1.3.0)
+ google-cloud-storage (1.43.0)
+ addressable (~> 2.8)
+ digest-crc (~> 0.4)
+ google-apis-iamcredentials_v1 (~> 0.1)
+ google-apis-storage_v1 (~> 0.19.0)
+ google-cloud-core (~> 1.6)
+ googleauth (>= 0.16.2, < 2.a)
+ mini_mime (~> 1.0)
+ googleauth (1.2.0)
+ faraday (>= 0.17.3, < 3.a)
+ jwt (>= 1.4, < 3.0)
+ memoist (~> 0.16)
+ multi_json (~> 1.11)
+ os (>= 0.9, < 2.0)
+ signet (>= 0.16, < 2.a)
+ highline (2.0.3)
+ http-cookie (1.0.5)
+ domain_name (~> 0.5)
+ httpclient (2.8.3)
+ jmespath (1.6.1)
+ json (2.6.2)
+ jwt (2.5.0)
+ memoist (0.16.2)
+ mini_magick (4.11.0)
+ mini_mime (1.1.2)
+ multi_json (1.15.0)
+ multipart-post (2.0.0)
+ nanaimo (0.3.0)
+ naturally (2.2.1)
+ optparse (0.1.1)
+ os (1.1.4)
+ plist (3.6.0)
+ public_suffix (5.0.0)
+ rake (13.0.6)
+ representable (3.2.0)
+ declarative (< 0.1.0)
+ trailblazer-option (>= 0.1.1, < 0.2.0)
+ uber (< 0.2.0)
+ retriable (3.1.2)
+ rexml (3.2.5)
+ rouge (2.0.7)
+ ruby2_keywords (0.0.5)
+ rubyzip (2.3.2)
+ security (0.1.3)
+ signet (0.17.0)
+ addressable (~> 2.8)
+ faraday (>= 0.17.5, < 3.a)
+ jwt (>= 1.5, < 3.0)
+ multi_json (~> 1.10)
+ simctl (1.6.8)
+ CFPropertyList
+ naturally
+ terminal-notifier (2.0.0)
+ terminal-table (1.8.0)
+ unicode-display_width (~> 1.1, >= 1.1.1)
+ trailblazer-option (0.1.2)
+ tty-cursor (0.7.1)
+ tty-screen (0.8.1)
+ tty-spinner (0.9.3)
+ tty-cursor (~> 0.7)
+ uber (0.1.0)
+ unf (0.1.4)
+ unf_ext
+ unf_ext (0.0.8.2)
+ unicode-display_width (1.8.0)
+ webrick (1.7.0)
+ word_wrap (1.0.0)
+ xcodeproj (1.22.0)
+ CFPropertyList (>= 2.3.3, < 4.0)
+ atomos (~> 0.1.3)
+ claide (>= 1.0.2, < 2.0)
+ colored2 (~> 3.1)
+ nanaimo (~> 0.3.0)
+ rexml (~> 3.2.4)
+ xcpretty (0.3.0)
+ rouge (~> 2.0.7)
+ xcpretty-travis-formatter (1.0.1)
+ xcpretty (~> 0.2, >= 0.0.7)
+
+PLATFORMS
+ arm64-darwin-21
+
+DEPENDENCIES
+ fastlane
+
+BUNDLED WITH
+ 2.3.11
diff --git a/app/.gitignore b/app/.gitignore
index 796b96d..42afabf 100644
--- a/app/.gitignore
+++ b/app/.gitignore
@@ -1 +1 @@
-/build
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
deleted file mode 100644
index b2d063e..0000000
--- a/app/build.gradle
+++ /dev/null
@@ -1,31 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 28
- defaultConfig {
- minSdkVersion 18
- targetSdkVersion 28
- versionCode 1
- versionName "1.0"
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- }
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
- }
- }
-}
-
-dependencies {
- implementation fileTree(dir: 'libs', include: ['*.jar'])
- implementation 'androidx.appcompat:appcompat:1.1.0'
- implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
- implementation "androidx.work:work-runtime:2.2.0"
-
- testImplementation 'junit:junit:4.12'
- androidTestImplementation 'androidx.test.ext:junit:1.1.0'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
-
- implementation 'io.sentry:sentry-android:1.7.21'
-}
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
new file mode 100644
index 0000000..6879c65
--- /dev/null
+++ b/app/build.gradle.kts
@@ -0,0 +1,83 @@
+plugins {
+ id("com.android.application")
+ id("org.jetbrains.kotlin.android")
+ id("org.jlleitschuh.gradle.ktlint")
+ id("io.gitlab.arturbosch.detekt")
+}
+
+android {
+
+ namespace = "com.hover.app"
+
+ compileSdk = 33
+
+ defaultConfig {
+ applicationId = "com.hover.app"
+ minSdk = 21
+ targetSdk = 33
+ versionCode = 1
+ versionName = "1.0"
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ vectorDrawables {
+ useSupportLibrary = true
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+
+ kotlinOptions {
+ jvmTarget = "11"
+ }
+
+ buildTypes {
+ getByName("release") {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+
+ buildFeatures {
+ compose = true
+ }
+
+ packagingOptions {
+ resources {
+ excludes += "/META-INF/{AL2.0,LGPL2.1}"
+ }
+ }
+
+ composeOptions {
+ kotlinCompilerExtensionVersion = "1.2.0"
+ }
+}
+
+dependencies {
+
+ implementation(project(":multisim"))
+
+ implementation(libs.android.coreKtx)
+ implementation(libs.android.appcompat)
+ implementation(libs.android.material)
+
+ implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.5.1")
+ implementation("androidx.activity:activity-compose:1.6.0")
+ implementation("androidx.compose.ui:ui:1.2.1")
+ implementation("androidx.compose.ui:ui-tooling-preview:1.2.1")
+ implementation("androidx.compose.material:material:1.2.1")
+
+ androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.2.1")
+ debugImplementation("androidx.compose.ui:ui-tooling:1.2.1")
+ debugImplementation("androidx.compose.ui:ui-test-manifest:1.2.1")
+
+ androidTestImplementation(libs.test.android.junit)
+ androidTestImplementation(libs.test.android.espresso)
+
+ testImplementation(libs.test.junit4)
+}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..ff59496
--- /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.kts.
+#
+# 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/hover/app/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/hover/app/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..93ba096
--- /dev/null
+++ b/app/src/androidTest/java/com/hover/app/ExampleInstrumentedTest.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2022 UseHover
+ *
+ * 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
+ *
+ * http://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.
+ */
+package com.hover.app
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import junit.framework.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * 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.hover.app", appContext.packageName)
+ }
+}
diff --git a/app/src/androidTest/java/com/hover/multisim/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/hover/multisim/ExampleInstrumentedTest.java
deleted file mode 100644
index 5bfb06c..0000000
--- a/app/src/androidTest/java/com/hover/multisim/ExampleInstrumentedTest.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.hover.multisim;
-
-import android.content.Context;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static org.junit.Assert.*;
-
-/**
- * Instrumented test, which will execute on an Android device.
- *
- * @see Testing documentation
- */
-@RunWith(AndroidJUnit4.class)
-public class ExampleInstrumentedTest {
- @Test
- public void useAppContext() {
- // Context of the app under test.
- Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
-
- assertEquals("com.hover.multisim", appContext.getPackageName());
- }
-}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ec7315a..6610b74 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,9 +1,28 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/hover/app/MainActivity.kt b/app/src/main/java/com/hover/app/MainActivity.kt
new file mode 100644
index 0000000..cee7a36
--- /dev/null
+++ b/app/src/main/java/com/hover/app/MainActivity.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2022 UseHover
+ *
+ * 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
+ *
+ * http://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.
+ */
+package com.hover.app
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Surface
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import com.hover.app.ui.theme.MultiSimTheme
+
+class MainActivity : ComponentActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent {
+ MultiSimTheme {
+ // A surface container using the 'background' color from the theme
+ Surface(
+ modifier = Modifier.fillMaxSize(),
+ color = MaterialTheme.colors.background
+ ) {
+ Greeting("Android")
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun Greeting(name: String) {
+ Text(text = "Hello $name!")
+}
+
+@Preview(showBackground = true)
+@Composable
+fun DefaultPreview() {
+ MultiSimTheme {
+ Greeting("Android")
+ }
+}
diff --git a/app/src/main/java/com/hover/app/ui/theme/Color.kt b/app/src/main/java/com/hover/app/ui/theme/Color.kt
new file mode 100644
index 0000000..a8b19d8
--- /dev/null
+++ b/app/src/main/java/com/hover/app/ui/theme/Color.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2022 UseHover
+ *
+ * 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
+ *
+ * http://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.
+ */
+package com.hover.app.ui.theme
+
+import androidx.compose.ui.graphics.Color
+
+val Purple200 = Color(0xFFBB86FC)
+val Purple500 = Color(0xFF6200EE)
+val Purple700 = Color(0xFF3700B3)
+val Teal200 = Color(0xFF03DAC5)
diff --git a/app/src/main/java/com/hover/app/ui/theme/Shape.kt b/app/src/main/java/com/hover/app/ui/theme/Shape.kt
new file mode 100644
index 0000000..5516a33
--- /dev/null
+++ b/app/src/main/java/com/hover/app/ui/theme/Shape.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2022 UseHover
+ *
+ * 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
+ *
+ * http://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.
+ */
+package com.hover.app.ui.theme
+
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Shapes
+import androidx.compose.ui.unit.dp
+
+val Shapes = Shapes(
+ small = RoundedCornerShape(4.dp),
+ medium = RoundedCornerShape(4.dp),
+ large = RoundedCornerShape(0.dp)
+)
diff --git a/app/src/main/java/com/hover/app/ui/theme/Theme.kt b/app/src/main/java/com/hover/app/ui/theme/Theme.kt
new file mode 100644
index 0000000..5286226
--- /dev/null
+++ b/app/src/main/java/com/hover/app/ui/theme/Theme.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2022 UseHover
+ *
+ * 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
+ *
+ * http://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.
+ */
+package com.hover.app.ui.theme
+
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.darkColors
+import androidx.compose.material.lightColors
+import androidx.compose.runtime.Composable
+
+private val DarkColorPalette = darkColors(
+ primary = Purple200,
+ primaryVariant = Purple700,
+ secondary = Teal200
+)
+
+private val LightColorPalette = lightColors(
+ primary = Purple500,
+ primaryVariant = Purple700,
+ secondary = Teal200
+
+ /* Other default colors to override
+ background = Color.White,
+ surface = Color.White,
+ onPrimary = Color.White,
+ onSecondary = Color.Black,
+ onBackground = Color.Black,
+ onSurface = Color.Black,
+ */
+)
+
+@Composable
+fun MultiSimTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) {
+ val colors = if (darkTheme) {
+ DarkColorPalette
+ } else {
+ LightColorPalette
+ }
+
+ MaterialTheme(
+ colors = colors,
+ typography = Typography,
+ shapes = Shapes,
+ content = content
+ )
+}
diff --git a/app/src/main/java/com/hover/app/ui/theme/Type.kt b/app/src/main/java/com/hover/app/ui/theme/Type.kt
new file mode 100644
index 0000000..81d8253
--- /dev/null
+++ b/app/src/main/java/com/hover/app/ui/theme/Type.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2022 UseHover
+ *
+ * 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
+ *
+ * http://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.
+ */
+package com.hover.app.ui.theme
+
+import androidx.compose.material.Typography
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.sp
+
+// Set of Material typography styles to start with
+val Typography = Typography(
+ body1 = TextStyle(
+ fontFamily = FontFamily.Default, fontWeight = FontWeight.Normal, fontSize = 16.sp
+ ),
+ button = TextStyle(
+ fontFamily = FontFamily.Default, fontWeight = FontWeight.W500, fontSize = 14.sp
+ ),
+ caption = TextStyle(
+ fontFamily = FontFamily.Default, fontWeight = FontWeight.Normal, fontSize = 12.sp
+ )
+)
diff --git a/app/src/main/java/com/hover/multisim/DataSource.java b/app/src/main/java/com/hover/multisim/DataSource.java
deleted file mode 100644
index 3f94a3a..0000000
--- a/app/src/main/java/com/hover/multisim/DataSource.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.hover.multisim;
-
-import android.content.Context;
-import android.database.SQLException;
-import android.database.sqlite.SQLiteDatabase;
-
-public class DataSource {
- public final static String TAG = "DataSource";
-
- private Context context;
- protected SQLiteDatabase database;
-
- protected DataSource(Context context) { context = context.getApplicationContext(); }
-
- protected void open() throws SQLException {
- database = SimDatabase.getInstance(context).getWritableDatabase();
- }
- @SuppressWarnings("EmptyMethod")
- protected void close() throws SQLException {
- // Apparently don't need to do this? Seems strange but: https://stackoverflow.com/questions/6608498/best-place-to-close-database-connection
- }
-}
diff --git a/app/src/main/java/com/hover/multisim/MultiSimWorker.java b/app/src/main/java/com/hover/multisim/MultiSimWorker.java
deleted file mode 100644
index 1fa151f..0000000
--- a/app/src/main/java/com/hover/multisim/MultiSimWorker.java
+++ /dev/null
@@ -1,432 +0,0 @@
-package com.hover.multisim;
-
-import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Build;
-import android.os.SystemClock;
-import androidx.annotation.NonNull;
-import androidx.localbroadcastmanager.content.LocalBroadcastManager;
-import android.telephony.PhoneStateListener;
-import android.telephony.ServiceState;
-import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-import android.util.Log;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
-
-import androidx.work.ListenableWorker;
-import androidx.work.OneTimeWorkRequest;
-import androidx.work.PeriodicWorkRequest;
-import androidx.work.WorkerParameters;
-import androidx.work.impl.utils.futures.SettableFuture;
-import io.sentry.Sentry;
-
-final public class MultiSimWorker extends ListenableWorker {
- public static final String TAG = "MultiSimTeleMgr";
- private static final String NEW_SIM_INFO = "NEW_SIM_INFO_ACTION";
- static final int SLOT_COUNT = 3; // Need to check 0, 1, and 2. Some phones index from 1.
-
- private SettableFuture workerFuture;
- private Result result = null;
-
- private final Semaphore slotSemaphore = new Semaphore(1, true);
- private final Semaphore simSemaphore = new Semaphore(1, true);
-
- private SimStateReceiver simStateReceiver;
- private SimStateListener simStateListener;
- private ArrayList validClassNames;
-
- private final String[] POSS_CLASS_NAMES = new String[] {
- null,
- "android.telephony.TelephonyManager",
- "android.telephony.MSimTelephonyManager",
- "android.telephony.MultiSimTelephonyService",
- "com.mediatek.telephony.TelephonyManagerEx",
- "com.android.internal.telephony.Phone",
- "com.android.internal.telephony.PhoneFactory"
- };
-
- public MultiSimWorker(@NonNull Context context, @NonNull WorkerParameters params) {
- super(context, params);
- }
-
- public static PeriodicWorkRequest makeToil() {
- return new PeriodicWorkRequest.Builder(MultiSimWorker.class, 15, TimeUnit.MINUTES).build();
- }
-
- public static OneTimeWorkRequest makeWork() {
- return new OneTimeWorkRequest.Builder(MultiSimWorker.class).build();
- }
-
- @Override
- @SuppressLint("RestrictedApi")
- public @NonNull ListenableFuture startWork() {
- Log.v(TAG, "Starting new Multi SIM worker");
- workerFuture = SettableFuture.create();
-
- if (Utils.hasPhonePerm(getApplicationContext()))
- startListeners();
- else
- workerFuture.set(Result.failure());
- return workerFuture;
- }
-
- @SuppressLint("RestrictedApi")
- private void startListeners() {
- try {
- registerSimStateReceiver();
- if (simStateListener == null) simStateListener = new SimStateListener();
- // TelephonyManager.listen() must take place on the main thread
- ((TelephonyManager) getApplicationContext().getSystemService(Context.TELEPHONY_SERVICE))
- .listen(simStateListener, PhoneStateListener.LISTEN_SERVICE_STATE | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
- } catch (Exception e) {
- Log.d(TAG, "Failed to start SIM listeners, setting retry", e);
- workerFuture.set(Result.retry());
- }
- }
-
- @SuppressLint("RestrictedApi")
- synchronized private void updateSimInfo() {
- getBackgroundExecutor().execute(new Runnable() {
- @Override
- public void run() {
- try {
- simSemaphore.acquire();
- if (Utils.hasPhonePerm(getApplicationContext())) {
- Log.v(TAG, "reviewing sim info");
- List oldList = getSaved();
- List newList = findUniqueSimInfo();
-
- if (newList != null) {
- compareNewAndOld(newList, oldList);
- result = Result.success();
- } else
- result = Result.failure();
- } else result = Result.failure();
- } catch (Exception e) { Log.w(TAG, "threw while attempting to update sim list", e); Sentry.capture(e); result = Result.failure();
- } finally {
- simSemaphore.release();
-// Give the listeners a chance to receive a few events - sometimes the first trigger isn't the needed info. 5s is long but this is a background thread anyway and the info has already been updated in the DB
- SystemClock.sleep(5000);
- if (!workerFuture.isDone()) {
- Log.v(TAG, "Finishing Multi SIM worker");
- workerFuture.set(result);
- }
- }
- }
- });
- }
-
- private void compareNewAndOld(List newList, List oldList) {
- if (oldList == null || oldList.size() != newList.size()) {
- Log.v(TAG, "no old list or sizes differ. Old: " + (oldList != null ? oldList.size() : "null") + ", new: " + newList.size());
- onSimInfoUpdate(newList);
- } else {
- for (int i = 0; i < newList.size(); i++)
- if (newList.get(i).isNotContainedInOrHasMoved(oldList)) {
- Log.v(TAG, "some sim moved");
- onSimInfoUpdate(newList);
- break;
- }
- }
- }
-
- private ArrayList getSaved() {
- ArrayList oldList = null;
- for (int i = 0; i < SLOT_COUNT; i++) {
- SimInfo si = new SimDataSource(getApplicationContext()).get(i);
- if (si != null) {
- if (oldList == null) oldList = new ArrayList<>();
- oldList.add(si);
- }
- }
- Log.v(TAG, "Loaded old list from db. Size: " + (oldList != null ? oldList.size() : "null"));
- return oldList;
- }
-
- private void onSimInfoUpdate(List newList) {
- updateDb(newList);
- Log.v(TAG, "Saved. Firing broadcast");
- LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(new Intent(action(getApplicationContext())));
- }
-
- private void updateDb(List newList) {
- for (SimInfo si: newList) {
- Log.i(TAG, "Saving SIM in slot " + si.slotIdx + ": " + si.toString());
- si.save(getApplicationContext());
- }
-
- List dbInfos = new SimDataSource(getApplicationContext()).getAll();
- for (SimInfo dbSi: dbInfos) {
- if (!findPhysicalSim(newList, dbSi)) {
- Log.i(TAG, "Couldn't find SIM: " + dbSi.toString() + ", removing");
- dbSi.setSimRemoved(getApplicationContext());
- }
- }
- }
-
- private boolean findPhysicalSim(List newList, SimInfo savedSim) {
- for (SimInfo si: newList) {
- if (si.isSameSim(savedSim))
- return true;
- }
- return false;
- }
-
- @SuppressWarnings({"MissingPermission"})
- synchronized private List findUniqueSimInfo() {
- List newList = new ArrayList<>();
- try {
- slotSemaphore.acquire();
- List slotMgrList = new ArrayList<>();
- List