diff --git a/.gitignore b/.gitignore index 935e026..987bfe3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.iml .gradle /local.properties +/app/signing.properties /.idea/caches /.idea/libraries /.idea/modules.xml 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/.name b/.idea/.name new file mode 100644 index 0000000..50c98fc --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +Poms Lab 1 \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..fb7f4a8 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml new file mode 100644 index 0000000..46b5ae0 --- /dev/null +++ b/.idea/deploymentTargetDropDown.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..a2d7c21 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..54d5acd --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..587a4c0 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,70 @@ +plugins { + id 'com.android.application' + id 'org.jetbrains.kotlin.android' + id 'kotlin-kapt' +} + +android { + namespace 'xyz.nuark.pomslab1' + compileSdk 33 + + defaultConfig { + applicationId "xyz.nuark.pomslab1" + minSdk 24 + targetSdk 33 + versionCode 1 + versionName "1.0" + + vectorDrawables.useSupportLibrary = true + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + signingConfigs { + release { + def props = new Properties() + file("signing.properties").withInputStream { props.load(it) } + + storeFile file(props.RELEASE_STORE_FILE) + storePassword props.RELEASE_STORE_PASSWORD + keyAlias props.RELEASE_KEY_ALIAS + keyPassword props.RELEASE_KEY_PASSWORD + + v1SigningEnabled true + v2SigningEnabled true + } + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + signingConfig signingConfigs.release + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + buildFeatures { + viewBinding true + dataBinding true + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.9.0' + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.google.android.material:material:1.8.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation "androidx.activity:activity-ktx:1.6.1" + implementation "androidx.fragment:fragment-ktx:1.5.5" + implementation "androidx.room:room-runtime:2.5.0" + annotationProcessor "androidx.room:room-compiler:2.5.0" + kapt "androidx.room:room-compiler:2.5.0" + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' +} \ 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/xyz/nuark/pomslab1/ExampleInstrumentedTest.kt b/app/src/androidTest/java/xyz/nuark/pomslab1/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..cc178ed --- /dev/null +++ b/app/src/androidTest/java/xyz/nuark/pomslab1/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package xyz.nuark.pomslab1 + +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("xyz.nuark.pomslab1", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..5b569ff --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000..2d20878 Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ diff --git a/app/src/main/java/xyz/nuark/pomslab1/PomsApp.kt b/app/src/main/java/xyz/nuark/pomslab1/PomsApp.kt new file mode 100644 index 0000000..969bf79 --- /dev/null +++ b/app/src/main/java/xyz/nuark/pomslab1/PomsApp.kt @@ -0,0 +1,25 @@ +package xyz.nuark.pomslab1 + +import android.app.Application +import androidx.room.Room +import xyz.nuark.pomslab1.model.repo.AppDatabase + +class PomsApp : Application() { + lateinit var db: AppDatabase + private set + + override fun onCreate() { + super.onCreate() + + instance = this + db = Room.databaseBuilder( + applicationContext, + AppDatabase::class.java, "poms-database" + ).build() + } + + companion object { + lateinit var instance: PomsApp + private set + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/nuark/pomslab1/model/data/CalculationRecord.kt b/app/src/main/java/xyz/nuark/pomslab1/model/data/CalculationRecord.kt new file mode 100644 index 0000000..77686ed --- /dev/null +++ b/app/src/main/java/xyz/nuark/pomslab1/model/data/CalculationRecord.kt @@ -0,0 +1,12 @@ +package xyz.nuark.pomslab1.model.data + +import androidx.room.ColumnInfo +import androidx.room.Entity +import java.util.* + +@Entity(tableName = "calc_records", primaryKeys = ["date", "equation"]) +data class CalculationRecord( + @ColumnInfo(name = "date") val date: Date, + @ColumnInfo(name = "equation") val equation: String, + @ColumnInfo(name = "result") val result: String, +) diff --git a/app/src/main/java/xyz/nuark/pomslab1/model/data/QuadraticEquationModel.kt b/app/src/main/java/xyz/nuark/pomslab1/model/data/QuadraticEquationModel.kt new file mode 100644 index 0000000..23e9fe0 --- /dev/null +++ b/app/src/main/java/xyz/nuark/pomslab1/model/data/QuadraticEquationModel.kt @@ -0,0 +1,61 @@ +package xyz.nuark.pomslab1.model.data + +import kotlin.math.sqrt + +class QuadraticEquationModel { + var a: Double? = null + var b: Double? = null + var c: Double? = null + + val ok: Boolean get() = a != null && b != null && c != null + + val result: Array + get() { + if (!ok) { + return emptyArray() + } + val _a = a!! + val _b = b!! + val _c = c!! + val d = _b * _b - 4 * _a * _c + if (d < 0) { + return emptyArray() + } + val x1 = (-_b + sqrt(d)) / (2 * _a) + val x2 = (-_b - sqrt(d)) / (2 * _a) + return arrayOf(x1, x2) + } + + fun setAV(v: String) { + a = v.toDoubleOrNull() + } + + fun setBV(v: String) { + b = v.toDoubleOrNull() + } + + fun setCV(v: String) { + c = v.toDoubleOrNull() + } + + val formatMain: String + get() = when (ok) { + true -> "$a*x^2 + $b*x + $c" + false -> "" + } + + val formatResult: String + get() = when (result.size) { + 0 -> "x = []" + 1 -> "x = ${result[0]}" + 2 -> "x = [${result[0]}; ${result[1]}]" + else -> "" + } + + override fun toString(): String { + if (!ok) { + return "" + } + return "$formatMain;\n$formatResult" + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/nuark/pomslab1/model/data/SumModel.kt b/app/src/main/java/xyz/nuark/pomslab1/model/data/SumModel.kt new file mode 100644 index 0000000..2172d6c --- /dev/null +++ b/app/src/main/java/xyz/nuark/pomslab1/model/data/SumModel.kt @@ -0,0 +1,45 @@ +package xyz.nuark.pomslab1.model.data + +class SumModel { + var a: Double? = null + var b: Double? = null + + val ok: Boolean get() = a != null && b != null + + val result: Array + get() { + if (a == null || b == null) { + return emptyArray() + } + val _a = a!! + val _b = b!! + return arrayOf(_a + _b) + } + + fun setAV(v: String) { + a = v.toDoubleOrNull() + } + + fun setBV(v: String) { + b = v.toDoubleOrNull() + } + + val formatMain: String + get() = when (ok) { + true -> "$a + $b" + false -> "" + } + + val formatResult: String + get() = when (result.size) { + 1 -> "${result[0]}" + else -> "" + } + + override fun toString(): String { + if (!ok) { + return "" + } + return "$formatMain = $formatResult" + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/nuark/pomslab1/model/interfaces/CalculationRecordDao.kt b/app/src/main/java/xyz/nuark/pomslab1/model/interfaces/CalculationRecordDao.kt new file mode 100644 index 0000000..5305a2c --- /dev/null +++ b/app/src/main/java/xyz/nuark/pomslab1/model/interfaces/CalculationRecordDao.kt @@ -0,0 +1,20 @@ +package xyz.nuark.pomslab1.model.interfaces + +import androidx.lifecycle.LiveData +import androidx.room.* +import xyz.nuark.pomslab1.model.data.CalculationRecord + +@Dao +interface CalculationRecordDao { + @Query("SELECT * FROM calc_records") + fun getAll(): LiveData> + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertOne(record: CalculationRecord) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertAll(vararg records: CalculationRecord) + + @Delete + fun delete(user: CalculationRecord) +} \ No newline at end of file diff --git a/app/src/main/java/xyz/nuark/pomslab1/model/repo/AppDatabase.kt b/app/src/main/java/xyz/nuark/pomslab1/model/repo/AppDatabase.kt new file mode 100644 index 0000000..e6dc9f4 --- /dev/null +++ b/app/src/main/java/xyz/nuark/pomslab1/model/repo/AppDatabase.kt @@ -0,0 +1,14 @@ +package xyz.nuark.pomslab1.model.repo + +import androidx.room.Database +import androidx.room.RoomDatabase +import androidx.room.TypeConverters +import xyz.nuark.pomslab1.model.data.CalculationRecord +import xyz.nuark.pomslab1.model.interfaces.CalculationRecordDao +import xyz.nuark.pomslab1.utils.RoomConverters + +@Database(entities = [CalculationRecord::class], version = 1) +@TypeConverters(RoomConverters::class) +abstract class AppDatabase : RoomDatabase() { + abstract fun calculationRecordDao(): CalculationRecordDao +} \ No newline at end of file diff --git a/app/src/main/java/xyz/nuark/pomslab1/services/HeavyTask.kt b/app/src/main/java/xyz/nuark/pomslab1/services/HeavyTask.kt new file mode 100644 index 0000000..3e41700 --- /dev/null +++ b/app/src/main/java/xyz/nuark/pomslab1/services/HeavyTask.kt @@ -0,0 +1,88 @@ +package xyz.nuark.pomslab1.services + +import android.app.Service +import android.content.Context +import android.content.Intent +import android.os.Binder +import android.os.IBinder +import android.util.Log +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import xyz.nuark.pomslab1.R +import java.io.* + +class HeavyTask : Service() { + private val binder = HeavyTaskBinder() + + private val _messengerChannel = MutableLiveData() + val messengerChannel: LiveData = _messengerChannel + + private var outputStreamWriter: OutputStreamWriter? = null + + override fun onBind(intent: Intent): IBinder { + return binder + } + + override fun onUnbind(intent: Intent?): Boolean { + return true + } + + fun startCalculationOfPI(steps: Int) { + outputStreamWriter = OutputStreamWriter(applicationContext.openFileOutput(LOG_FILENAME, Context.MODE_APPEND)) + log("startCalculationOfPI: starting\n") + Thread { + _messengerChannel.postValue(buildString { + append(getString(R.string.messagePICalculationStarted)) + append(steps) + }) + var index = 0 + var pi = 0.0 + var denominator = 1 + + while (index < steps) { + if (index % 2 == 0) { + pi += 4.0 / denominator + } else { + pi -= 4.0 / denominator + } + + index += 1 + denominator += 2 + } + + log("startCalculationOfPI: posting value $pi to live data channel\n") + _messengerChannel.postValue(getString(R.string.messagePICalculationEnded).format(steps, pi)) + outputStreamWriter?.flush() + outputStreamWriter?.close() + }.start() + } + + fun getLogText(): String { + return try { + val inputStream: InputStream = applicationContext.openFileInput(LOG_FILENAME) + inputStream.bufferedReader().use(BufferedReader::readText) + } catch (e: FileNotFoundException) { + Log.e(TAG, "getLogText: ENOENT", e) + e.stackTraceToString() + } catch (e: IOException) { + Log.e(TAG, "getLogText: IOE", e) + e.stackTraceToString() + }.ifEmpty { getString(R.string.empty) } + } + + private fun log(line: String) { + outputStreamWriter?.write(line) + Log.i(TAG, "log: $outputStreamWriter") + Log.i(TAG, line) + } + + inner class HeavyTaskBinder : Binder() { + val service: HeavyTask get() = this@HeavyTask + } + + companion object { + const val TAG = "HeavyTask" + + const val LOG_FILENAME = "heavy_task.log" + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/nuark/pomslab1/utils/RoomConverters.kt b/app/src/main/java/xyz/nuark/pomslab1/utils/RoomConverters.kt new file mode 100644 index 0000000..18c8639 --- /dev/null +++ b/app/src/main/java/xyz/nuark/pomslab1/utils/RoomConverters.kt @@ -0,0 +1,16 @@ +package xyz.nuark.pomslab1.utils + +import androidx.room.TypeConverter +import java.util.* + +class RoomConverters { + @TypeConverter + fun fromTimestamp(value: Long?): Date? { + return if (value == null) null else Date(value) + } + + @TypeConverter + fun dateToTimestamp(date: Date?): Long? { + return date?.time + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/nuark/pomslab1/view/activities/GraphicsActivity.kt b/app/src/main/java/xyz/nuark/pomslab1/view/activities/GraphicsActivity.kt new file mode 100644 index 0000000..42514f9 --- /dev/null +++ b/app/src/main/java/xyz/nuark/pomslab1/view/activities/GraphicsActivity.kt @@ -0,0 +1,54 @@ +package xyz.nuark.pomslab1.view.activities + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import xyz.nuark.pomslab1.databinding.ActivityGraphicsBinding + +class GraphicsActivity : AppCompatActivity() { + private var _binding: ActivityGraphicsBinding? = null + private val binding get() = _binding!! + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + _binding = ActivityGraphicsBinding.inflate(layoutInflater) + setContentView(binding.root) + + binding.tractor.setOnClickListener { + binding.tractor.apply { + clearAnimation() + rotation = 0f + scaleX = 1f + scaleY = 1f + translationX = 0f + translationY = 0f + alpha = 1f + } + } + + binding.rotate.setOnClickListener { + binding.tractor.animate().rotation(3600f).duration = 500 + } + + binding.scale.setOnClickListener { + binding.tractor.animate().scaleXBy(-4f).scaleYBy(-4f).setDuration(1000).withEndAction { + binding.tractor.animate().scaleXBy(4f).scaleYBy(4f).duration = 1000 + } + + } + + binding.translate.setOnClickListener { + binding.tractor.animation + binding.tractor.animate().translationXBy(-100f).setDuration(100).withEndAction { + binding.tractor.animate().translationXBy(200f).setDuration(800).withEndAction { + binding.tractor.animate().translationXBy(-100f).duration = 100 + } + } + } + + binding.alpha.setOnClickListener { + binding.tractor.animate().alpha(0f).setDuration(100).withEndAction { + binding.tractor.animate().alpha(1f).duration = 1000 + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/nuark/pomslab1/view/activities/LinkerActivity.kt b/app/src/main/java/xyz/nuark/pomslab1/view/activities/LinkerActivity.kt new file mode 100644 index 0000000..4d40e3c --- /dev/null +++ b/app/src/main/java/xyz/nuark/pomslab1/view/activities/LinkerActivity.kt @@ -0,0 +1,36 @@ +package xyz.nuark.pomslab1.view.activities + +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.core.text.buildSpannedString +import xyz.nuark.pomslab1.R +import xyz.nuark.pomslab1.databinding.ActivityLinkerBinding + +class LinkerActivity : AppCompatActivity() { + private var _binding: ActivityLinkerBinding? = null + private val binding get() = _binding!! + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + _binding = ActivityLinkerBinding.inflate(layoutInflater) + setContentView(binding.root) + + binding.intentContents.text = intent.let { + buildSpannedString { + append(getString(R.string.youTriedToOpen)) + appendLine(it.dataString) + } + } + + binding.openInBrowser.setOnClickListener { + try { + startActivity(Intent.createChooser(Intent(Intent.ACTION_VIEW).apply { + data = Uri.parse(binding.linkEditor.text.toString()) + }, getString(R.string.chooseBrowserTitle))) + } catch (_: Exception) { + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/nuark/pomslab1/view/activities/MainActivity.kt b/app/src/main/java/xyz/nuark/pomslab1/view/activities/MainActivity.kt new file mode 100644 index 0000000..17eb107 --- /dev/null +++ b/app/src/main/java/xyz/nuark/pomslab1/view/activities/MainActivity.kt @@ -0,0 +1,96 @@ +package xyz.nuark.pomslab1.view.activities + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.view.Menu +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.app.AppCompatDelegate +import androidx.core.content.edit +import xyz.nuark.pomslab1.R +import xyz.nuark.pomslab1.databinding.ActivityMainBinding + +class MainActivity : AppCompatActivity() { + private var _binding: ActivityMainBinding? = null + private val binding get() = _binding!! + + override fun onCreate(savedInstanceState: Bundle?) { + updateTheme() + super.onCreate(savedInstanceState) + _binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + return super.onCreateOptionsMenu(menu?.apply { + addSubMenu(getString(R.string.openActionsLabel)).apply { + add(getString(R.string.openSwitcher))?.apply { + setOnMenuItemClickListener { + startActivity(Intent(this@MainActivity, SwitcherActivity::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + }) + true + } + } + add(getString(R.string.openCalcHistory))?.apply { + setOnMenuItemClickListener { + startActivity(Intent(this@MainActivity, ResultActivity::class.java)) + true + } + } + add(getString(R.string.openHeavyTaskConnector))?.apply { + setOnMenuItemClickListener { + startActivity(Intent(this@MainActivity, ServiceCommunicatorActivity::class.java)) + true + } + } + add(getString(R.string.openGraphicsActivity))?.apply { + setOnMenuItemClickListener { + startActivity(Intent(this@MainActivity, GraphicsActivity::class.java)) + true + } + } + } + val sp = getSharedPreferences(SPG_THEME_MODE, Context.MODE_PRIVATE) + if (sp.getBoolean(SP_LIGHT_MODE_ENABLED, false)) { + add(getString(R.string.switchToDarkLabel))?.apply { + setOnMenuItemClickListener { + sp.edit(true) { + putBoolean(SP_LIGHT_MODE_ENABLED, false) + } + updateTheme() + true + } + } + } else { + add(getString(R.string.switchToLightLabel))?.apply { + setOnMenuItemClickListener { + sp.edit(true) { + putBoolean(SP_LIGHT_MODE_ENABLED, true) + } + updateTheme() + true + } + } + } + }) + } + + private fun updateTheme() { + val sp = getSharedPreferences(SPG_THEME_MODE, Context.MODE_PRIVATE) + val lightMode = when (sp.getBoolean(SP_LIGHT_MODE_ENABLED, false)) { + true -> AppCompatDelegate.MODE_NIGHT_NO + false -> AppCompatDelegate.MODE_NIGHT_YES + } + Log.i(TAG, "updateTheme: lightMode") + AppCompatDelegate.setDefaultNightMode(lightMode) + } + + companion object { + const val TAG = "MainActivity" + + const val SPG_THEME_MODE = "SPG_THEME_MODE" + const val SP_LIGHT_MODE_ENABLED = "SP_LIGHT_MODE_ENABLED" + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/nuark/pomslab1/view/activities/ResultActivity.kt b/app/src/main/java/xyz/nuark/pomslab1/view/activities/ResultActivity.kt new file mode 100644 index 0000000..9ec0d2c --- /dev/null +++ b/app/src/main/java/xyz/nuark/pomslab1/view/activities/ResultActivity.kt @@ -0,0 +1,33 @@ +package xyz.nuark.pomslab1.view.activities + +import android.os.Bundle +import android.view.View +import androidx.activity.viewModels +import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.LinearLayoutManager +import xyz.nuark.pomslab1.databinding.ActivityResultBinding +import xyz.nuark.pomslab1.view.adapters.CalculationHistoryAdapter +import xyz.nuark.pomslab1.viewmodels.ResultActivityViewModel + +class ResultActivity : AppCompatActivity() { + private var _binding: ActivityResultBinding? = null + private val binding get() = _binding!! + + private val viewModel: ResultActivityViewModel by viewModels() + + private val adapter = CalculationHistoryAdapter() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + _binding = ActivityResultBinding.inflate(layoutInflater) + setContentView(binding.root) + + viewModel.records.observe(this) { + binding.noHistoryLabel.visibility = if (it.isEmpty()) View.VISIBLE else View.GONE + adapter.setRecords(it) + } + + binding.historyList.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, true) + binding.historyList.adapter = adapter + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/nuark/pomslab1/view/activities/ServiceCommunicatorActivity.kt b/app/src/main/java/xyz/nuark/pomslab1/view/activities/ServiceCommunicatorActivity.kt new file mode 100644 index 0000000..0acfa5e --- /dev/null +++ b/app/src/main/java/xyz/nuark/pomslab1/view/activities/ServiceCommunicatorActivity.kt @@ -0,0 +1,146 @@ +package xyz.nuark.pomslab1.view.activities + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.ServiceConnection +import android.os.Bundle +import android.os.IBinder +import android.util.Log +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import xyz.nuark.pomslab1.R +import xyz.nuark.pomslab1.databinding.ActivityServiceCommunicatorBinding +import xyz.nuark.pomslab1.services.HeavyTask + +class ServiceCommunicatorActivity : AppCompatActivity() { + private var _binding: ActivityServiceCommunicatorBinding? = null + private val binding get() = _binding!! + + private val connection = HeavyServiceConnector() + private lateinit var heavyIntentService: Intent + + private var subscribed = false + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + _binding = ActivityServiceCommunicatorBinding.inflate(layoutInflater) + setContentView(binding.root) + + heavyIntentService = Intent(this, HeavyTask::class.java) + + binding.serviceController.setOnClickListener { + if (connection.bound.value == true) { + unbindService(connection) + connection.stopService(this) + subscribed = false + } else { + binding.logHolder.text = "" + bindService(heavyIntentService, connection, Context.BIND_AUTO_CREATE) + } + } + + binding.calc.setOnClickListener { + val stepsString = binding.stepsCount.editText?.text?.toString() ?: "" + if (stepsString.isEmpty()) { + return@setOnClickListener + } + val steps = stepsString.toIntOrNull() + if (steps == null || steps <= 0) { + return@setOnClickListener + } + + val binder = connection.binder + if (connection.bound.value == true && binder != null && binder.isBinderAlive) { + Log.i(TAG, "onCreate: service is ready for work!") + binder.service.startCalculationOfPI(steps) + Log.i(TAG, "onCreate: started calculation thread in service for 200 steps") + if (!subscribed) { + binder.service.messengerChannel.observe(this) { + appendLogLine(it) + } + subscribed = true + } + } else { + Log.i(TAG, "onCreate: service is not ready! (binder: $binder)") + } + } + + binding.readServiceLog.setOnClickListener { + connection.binder?.service?.let { + binding.serviceLogHolder.text = it.getLogText() + } + } + + connection.bound.observe(this) { result -> + binding.serviceController.apply { + setChipIconResource(if (result) R.drawable.baseline_play_arrow_24 else R.drawable.baseline_stop_24) + setChipIconTintResource(if (result) R.color.green else R.color.red) + text = getText(if (result) R.string.serviceStatusRunning else R.string.serviceStatusStopped) + } + } + } + + private fun appendLogLine(line: String) { + binding.logHolder.text = buildString { + appendLine(line) + append(binding.logHolder.text) + } + } + + override fun onDestroy() { + if (connection.bound.value == true) { + unbindService(connection) + } + super.onDestroy() + } + + class HeavyServiceConnector : ServiceConnection { + private var _bound = MutableLiveData() + val bound: LiveData get() = _bound + + var binder: HeavyTask.HeavyTaskBinder? = null + private set + + init { + _bound.value = false + } + + override fun onServiceConnected(service: ComponentName?, binder: IBinder?) { + Log.i(TAG, "onServiceConnected: service connected, got $service and $binder") + if (binder == null) { + Log.e(TAG, "onServiceConnected: binder is null!") + return + } + if (binder !is HeavyTask.HeavyTaskBinder) { + Log.e(TAG, "onServiceConnected: binder is not ours!") + return + } + this.binder = binder + _bound.value = true + } + + override fun onServiceDisconnected(service: ComponentName?) { + Log.i(TAG, "onServiceDisconnected: service disconnected, got $service") + binder = null + _bound.value = false + } + + fun stopService(lifecycleOwner: LifecycleOwner) { + binder?.service?.stopSelf() + binder?.service?.messengerChannel?.removeObservers(lifecycleOwner) + binder = null + _bound.value = false + } + + companion object { + const val TAG = "HeavyServiceConnector" + } + } + + companion object { + const val TAG = "ServiceCommunicatorActivity" + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/nuark/pomslab1/view/activities/SwitcherActivity.kt b/app/src/main/java/xyz/nuark/pomslab1/view/activities/SwitcherActivity.kt new file mode 100644 index 0000000..2424037 --- /dev/null +++ b/app/src/main/java/xyz/nuark/pomslab1/view/activities/SwitcherActivity.kt @@ -0,0 +1,78 @@ +package xyz.nuark.pomslab1.view.activities + +import android.content.Intent +import android.os.Bundle +import android.view.Menu +import androidx.appcompat.app.AppCompatActivity +import xyz.nuark.pomslab1.R +import xyz.nuark.pomslab1.databinding.ActivitySwitcherBinding +import xyz.nuark.pomslab1.view.fragments.QuadraticFragment +import xyz.nuark.pomslab1.view.fragments.SumFragment + +class SwitcherActivity : AppCompatActivity() { + private var _binding: ActivitySwitcherBinding? = null + private val binding get() = _binding!! + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + _binding = ActivitySwitcherBinding.inflate(layoutInflater) + setContentView(binding.root) + + supportFragmentManager + .beginTransaction().apply { + add(binding.fragmentHolder.id, sumFragment) + add(binding.fragmentHolder.id, quadraticFragment) + } + .commit() + supportFragmentManager.executePendingTransactions() + supportFragmentManager + .beginTransaction().apply { + detach(sumFragment) + } + .commit() + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + return super.onCreateOptionsMenu(menu?.apply { + add(getString(R.string.openMultipanel))?.apply { + setOnMenuItemClickListener { + startActivity(Intent(this@SwitcherActivity, MainActivity::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + }) + true + } + } + add(getString(R.string.switchFragment))?.apply { + setOnMenuItemClickListener { + this@SwitcherActivity.switchFragment() + true + } + } + add(getString(R.string.openCalcHistory))?.apply { + setOnMenuItemClickListener { + startActivity(Intent(this@SwitcherActivity, ResultActivity::class.java)) + true + } + } + }) + } + + private fun switchFragment() { + supportFragmentManager + .beginTransaction().apply { + if (quadraticFragment.isDetached) { + detach(sumFragment) + attach(quadraticFragment) + } else if (sumFragment.isDetached) { + attach(sumFragment) + detach(quadraticFragment) + } + } + .commit() + } + + companion object { + private val quadraticFragment = QuadraticFragment() + private val sumFragment = SumFragment() + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/nuark/pomslab1/view/adapters/CalculationHistoryAdapter.kt b/app/src/main/java/xyz/nuark/pomslab1/view/adapters/CalculationHistoryAdapter.kt new file mode 100644 index 0000000..d9593f9 --- /dev/null +++ b/app/src/main/java/xyz/nuark/pomslab1/view/adapters/CalculationHistoryAdapter.kt @@ -0,0 +1,58 @@ +package xyz.nuark.pomslab1.view.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import xyz.nuark.pomslab1.databinding.CalculationHistoryItemBinding +import xyz.nuark.pomslab1.model.data.CalculationRecord +import java.text.SimpleDateFormat +import java.util.* + +class CalculationHistoryAdapter : RecyclerView.Adapter() { + private val records = arrayListOf() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH { + val inflater = LayoutInflater.from(parent.context) + val binding = CalculationHistoryItemBinding.inflate( + inflater, parent, + false + ) + return VH(binding) + } + + override fun getItemCount(): Int { + return records.size + } + + override fun onBindViewHolder(holder: VH, position: Int) { + holder.bind(records[position]) + } + + fun setRecords(newRecords: List) { + val size = records.size + records.clear() + if (size > 0) { + notifyItemRangeRemoved(0, size) + } + records.addAll(newRecords) + if (records.size > 0) { + notifyItemRangeInserted(0, records.size) + } + } + + fun isEmpty(): Boolean { + return records.isEmpty() + } + + class VH(private val binding: CalculationHistoryItemBinding) : RecyclerView.ViewHolder(binding.root) { + fun bind(record: CalculationRecord) { + binding.dateHolder.text = formatter.format(record.date) + binding.equHolder.text = record.equation + binding.resultHolder.text = record.result + } + } + + companion object { + private val formatter = SimpleDateFormat("dd.MM.yy HH:mm:ss", Locale.CANADA) + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/nuark/pomslab1/view/fragments/QuadraticFragment.kt b/app/src/main/java/xyz/nuark/pomslab1/view/fragments/QuadraticFragment.kt new file mode 100644 index 0000000..d13bae4 --- /dev/null +++ b/app/src/main/java/xyz/nuark/pomslab1/view/fragments/QuadraticFragment.kt @@ -0,0 +1,36 @@ +package xyz.nuark.pomslab1.view.fragments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import xyz.nuark.pomslab1.databinding.FragmentQuadraticBinding +import xyz.nuark.pomslab1.viewmodels.QuadraticCalculationViewModel + +class QuadraticFragment : Fragment() { + private var _binding: FragmentQuadraticBinding? = null + private val binding get() = _binding!! + + private val calculationViewModel: QuadraticCalculationViewModel by activityViewModels() + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentQuadraticBinding.inflate(inflater) + binding.equation = calculationViewModel.quadraticEquationModel + binding.resultString = calculationViewModel.quadraticEquationModel.toString() + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.button.setOnClickListener { v: View? -> + binding.resultString = calculationViewModel.quadraticEquationModel.toString() + calculationViewModel.saveQuadraticCalculation() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/nuark/pomslab1/view/fragments/SumFragment.kt b/app/src/main/java/xyz/nuark/pomslab1/view/fragments/SumFragment.kt new file mode 100644 index 0000000..ed95ea1 --- /dev/null +++ b/app/src/main/java/xyz/nuark/pomslab1/view/fragments/SumFragment.kt @@ -0,0 +1,36 @@ +package xyz.nuark.pomslab1.view.fragments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import xyz.nuark.pomslab1.databinding.FragmentSumBinding +import xyz.nuark.pomslab1.viewmodels.SumCalculationViewModel + +class SumFragment : Fragment() { + private var _binding: FragmentSumBinding? = null + private val binding get() = _binding!! + + private val calculationViewModel: SumCalculationViewModel by activityViewModels() + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentSumBinding.inflate(inflater) + binding.equation = calculationViewModel.sumModel + binding.resultString = calculationViewModel.sumModel.toString() + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.button.setOnClickListener { v: View? -> + binding.resultString = calculationViewModel.sumModel.toString() + calculationViewModel.saveSumCalculation() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/nuark/pomslab1/viewmodels/QuadraticCalculationViewModel.kt b/app/src/main/java/xyz/nuark/pomslab1/viewmodels/QuadraticCalculationViewModel.kt new file mode 100644 index 0000000..47e8c4c --- /dev/null +++ b/app/src/main/java/xyz/nuark/pomslab1/viewmodels/QuadraticCalculationViewModel.kt @@ -0,0 +1,23 @@ +package xyz.nuark.pomslab1.viewmodels + +import androidx.lifecycle.ViewModel +import xyz.nuark.pomslab1.PomsApp +import xyz.nuark.pomslab1.model.data.CalculationRecord +import xyz.nuark.pomslab1.model.data.QuadraticEquationModel +import java.util.* + +class QuadraticCalculationViewModel : ViewModel() { + val quadraticEquationModel = QuadraticEquationModel() + + fun saveQuadraticCalculation() { + if (quadraticEquationModel.ok) { + Thread { + PomsApp.instance.db.calculationRecordDao().insertOne( + CalculationRecord( + Date(), quadraticEquationModel.formatMain, quadraticEquationModel.formatResult + ) + ) + }.start() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/nuark/pomslab1/viewmodels/ResultActivityViewModel.kt b/app/src/main/java/xyz/nuark/pomslab1/viewmodels/ResultActivityViewModel.kt new file mode 100644 index 0000000..113ef4c --- /dev/null +++ b/app/src/main/java/xyz/nuark/pomslab1/viewmodels/ResultActivityViewModel.kt @@ -0,0 +1,10 @@ +package xyz.nuark.pomslab1.viewmodels + +import androidx.lifecycle.LiveData +import androidx.lifecycle.ViewModel +import xyz.nuark.pomslab1.PomsApp +import xyz.nuark.pomslab1.model.data.CalculationRecord + +class ResultActivityViewModel : ViewModel() { + val records: LiveData> = PomsApp.instance.db.calculationRecordDao().getAll() +} \ No newline at end of file diff --git a/app/src/main/java/xyz/nuark/pomslab1/viewmodels/SumCalculationViewModel.kt b/app/src/main/java/xyz/nuark/pomslab1/viewmodels/SumCalculationViewModel.kt new file mode 100644 index 0000000..4b64c9b --- /dev/null +++ b/app/src/main/java/xyz/nuark/pomslab1/viewmodels/SumCalculationViewModel.kt @@ -0,0 +1,23 @@ +package xyz.nuark.pomslab1.viewmodels + +import androidx.lifecycle.ViewModel +import xyz.nuark.pomslab1.PomsApp +import xyz.nuark.pomslab1.model.data.CalculationRecord +import xyz.nuark.pomslab1.model.data.SumModel +import java.util.* + +class SumCalculationViewModel : ViewModel() { + val sumModel = SumModel() + + fun saveSumCalculation() { + if (sumModel.ok) { + Thread { + PomsApp.instance.db.calculationRecordDao().insertOne( + CalculationRecord( + Date(), sumModel.formatMain, sumModel.formatResult + ) + ) + }.start() + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/baseline_play_arrow_24.xml b/app/src/main/res/drawable/baseline_play_arrow_24.xml new file mode 100644 index 0000000..e3fd2e9 --- /dev/null +++ b/app/src/main/res/drawable/baseline_play_arrow_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/baseline_stop_24.xml b/app/src/main/res/drawable/baseline_stop_24.xml new file mode 100644 index 0000000..19bcbee --- /dev/null +++ b/app/src/main/res/drawable/baseline_stop_24.xml @@ -0,0 +1,5 @@ + + + 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..910da4f --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,10 @@ + + + + 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..1d46844 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-land/activity_main.xml b/app/src/main/res/layout-land/activity_main.xml new file mode 100644 index 0000000..b81dbb6 --- /dev/null +++ b/app/src/main/res/layout-land/activity_main.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-land/fragment_quadratic.xml b/app/src/main/res/layout-land/fragment_quadratic.xml new file mode 100644 index 0000000..09e22eb --- /dev/null +++ b/app/src/main/res/layout-land/fragment_quadratic.xml @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + +