Commit 54308cad authored by Sven Eric Panitz's avatar Sven Eric Panitz
Browse files

Initial commit from original bachelor theses Roth with a lot of further features.

parents
# Cardelli
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'androidx.navigation.safeargs.kotlin'
id 'kotlin-android-extensions'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
dataBinding{
enabled=true
}
lintOptions {
abortOnError false
}
defaultConfig {
applicationId "com.example.inventorymanagement"
minSdkVersion 23
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
packagingOptions {
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/LICENSE'
exclude 'META-INF/LICENSE.txt'
pickFirst 'META-INF/LICENSE.md'
exclude 'META-INF/license.txt'
exclude 'META-INF/NOTICE'
exclude 'META-INF/NOTICE.txt'
exclude 'META-INF/notice.txt'
pickFirst 'META-INF/NOTICE.md'
exclude 'META-INF/ASL2.0'
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "io.ktor:ktor-client-cio:$ktor_version"
implementation 'androidx.core:core-ktx:1.2.0'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'com.google.android.material:material:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.navigation:navigation-fragment-ktx:2.2.2'
implementation 'androidx.navigation:navigation-ui-ktx:2.2.2'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.wear:wear:1.0.0'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
compileOnly 'com.google.android.wearable:wearable:2.6.0'
implementation "io.ktor:ktor-client-core:$ktor_version"
implementation "io.ktor:ktor-client-android:$ktor_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-xml:$jackson_dataformat_xml_version"
implementation 'javax.xml.stream:stax-api:1.0'
implementation 'org.json:json:20201115'
implementation "io.ktor:ktor-client-serialization:$ktor_version"
implementation "io.ktor:ktor-client-okhttp:$ktor_version"
implementation "io.ktor:ktor-jackson:$ktor_version"
implementation "androidx.wear:wear:1.2.0-alpha03"
implementation "ch.qos.logback:logback-classic:$logback_version"
implementation "com.squareup.okhttp3:logging-interceptor:4.9.0"
implementation group: 'org.apache.commons', name: 'commons-text', version: '1.9'
}
apply plugin: 'kotlin-android-extensions'
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.cardelli">
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-feature android:name="android.hardware.type.watch" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Cardelli">
<activity android:name=".LoadingActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!--
Set to true if your app is Standalone, that is, it does not require the handheld
app to run.
-->
<meta-data
android:name="com.google.android.wearable.standalone"
android:value="true" />
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:theme="@style/Theme.Cardelli.NoActionBar"
android:configChanges="orientation|screenSize"
>
</activity>
</application>
</manifest>
package org.cardelli
import org.cardelli.model.Exercise
import org.cardelli.model.ProgrammingExercise
import org.cardelli.model.MultipleChoice
// Contains and provides global constants
class Constants{
companion object {
private const val PROGRAMMING_EXERCISE_MAX_PROGRESS = 3
private const val PROGRAMMING_EXERCISE_INCREMENT = 1
const val SESSION_LENGTH = 5
fun getMaxProgressForExerciseType(exercise: Exercise): Int {
when (exercise) {
is ProgrammingExercise -> return PROGRAMMING_EXERCISE_MAX_PROGRESS
is MultipleChoice -> return PROGRAMMING_EXERCISE_MAX_PROGRESS
}
return 0
}
fun getIncrementForExerciseType(exercise: Exercise): Int {
when (exercise) {
is ProgrammingExercise -> return PROGRAMMING_EXERCISE_INCREMENT
is MultipleChoice -> return PROGRAMMING_EXERCISE_MAX_PROGRESS
}
return 0
}
}
}
package org.cardelli
import android.content.Context
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log.d
import org.cardelli.db.CourseRespoitory
import org.cardelli.model.Course
import org.cardelli.service.CourseService
import org.cardelli.service.CourseServiceImpl
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
/**
* Initial activity of the application that synchronizes data
*/
class LoadingActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_loading)
}
override fun onResume() {
super.onResume()
applicationContext?.let {
GlobalScope.launch {
synchronizeDatabase(it)
d("Start", "Starting Main Activity")
val ide = Intent(this@LoadingActivity, MainActivity::class.java)
startActivity(ide)
}
}
}
private suspend fun synchronizeDatabase(context: Context) {
val courseService: CourseService = CourseServiceImpl()
val repository = CourseRespoitory(context)
// Compare local and remote sets of courses
val courses = courseService.getAllCourses()
val courseIdsRemote = courseService.getAllCourseIds().toMutableList()
val courseIdsLocal = repository.readAllCourseIds().toMutableList()
val copy = courseIdsLocal.map { it }
for (id in copy) {
if (id in courseIdsRemote) {
courseIdsLocal.remove(id)
courseIdsRemote.remove(id)
}
}
// courseIdsLocal now contains the ids of all courses that exist locally, but not on the server. TODO: Remove?
// courseIdsRemote now contains the ids of all courses on the server that are not in the database. Fetch and save
// val courses: List<Course> = courseIdsRemote
//courseService.getCourseById(it)
// }.filter {
// it.exercises != null && it.exercises!!.isNotEmpty()
// }
repository.saveAll(courses)
}
}
package org.cardelli
import android.content.Context
import android.os.Bundle
import android.util.Log
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.snackbar.Snackbar
import androidx.appcompat.app.AppCompatActivity
import android.view.Menu
import android.view.MenuItem
import androidx.navigation.findNavController
import org.cardelli.db.CourseRespoitory
import org.cardelli.db.DatabaseContract
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import java.lang.Exception
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(findViewById(R.id.toolbar))
}
}
package org.cardelli.adapter
interface AnswerEvaluator{
fun isCorrect():Pair<Boolean,String>
}
package org.cardelli.adapter
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.navigation.findNavController
import androidx.recyclerview.widget.RecyclerView
import org.cardelli.R
import org.cardelli.fragments.practice.MultipleChoiceFragmentDirections
import org.cardelli.model.Answer
import kotlinx.android.synthetic.main.answer_list.view.*
import org.cardelli.fragments.practice.PracticeOverviewFragment
/**
* Adapter for the course list RecyclerView displayed in the [org.cardelli.fragments.HomeFragment]
*/
class AnswerListAdapter(private val answers: List<Answer>,val overview:PracticeOverviewFragment): RecyclerView.Adapter<AnswerListAdapter.CardViewHolder>(),AnswerEvaluator {
override fun isCorrect():Pair<Boolean,String> = Pair(false,"")
inner class CardViewHolder(private val view: View): RecyclerView.ViewHolder(view) {
fun bind(answer: Answer) {
view.answer_title.text = answer.antwort
view.answer.setOnClickListener {
if (answer.isCorrect){
overview.displayAnswer(true,answer.antwort?:"")
}else {
overview.displayAnswer(false,answer.antwort?:"")
}
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CardViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.answer_list, parent, false)
return CardViewHolder(view)
}
override fun getItemCount() = answers.size
override fun onBindViewHolder(holder: CardViewHolder, position: Int) {
val singleAnswer = answers[position]
holder.bind(singleAnswer)
}
}
package org.cardelli.adapter
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.navigation.findNavController
import androidx.recyclerview.widget.RecyclerView
import org.cardelli.R
import org.cardelli.fragments.practice.MultipleChoiceFragmentDirections
import org.cardelli.model.Answer
import kotlinx.android.synthetic.main.answer_selection_list.view.*
import org.cardelli.fragments.practice.PracticeOverviewFragment
/**
* Adapter for the course list RecyclerView displayed in the [org.cardelli.fragments.HomeFragment]
*/
class AnswerSelectionListAdapter(private val answers: List<Answer>,val overview:PracticeOverviewFragment): RecyclerView.Adapter<AnswerSelectionListAdapter.CardViewHolder>(),AnswerEvaluator {
val inputs:MutableMap<Int,String> = mutableMapOf()
override fun isCorrect():Pair<Boolean,String>{
var correctAnswers=0
var vs = inputs.values
val buf = StringBuffer()
for (v in vs) buf.append(v+"\n")
for (answer in answers)if (answer.isCorrect)correctAnswers++
return Pair(correctAnswers==correctChecked,buf.toString())
}
var correctChecked:Int=0
inner class CardViewHolder(private val view: View): RecyclerView.ViewHolder(view) {
fun bind(answer: Answer,pos:Int) {
view.answerText.text = answer.antwort
view.checkAnswer.setOnClickListener{
if (view.checkAnswer.isChecked()){
if (answer.isCorrect) correctChecked++
inputs.put(pos,answer.antwort?:"")
}else{
inputs.remove(pos)
if (!answer.isCorrect)correctChecked++
}
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CardViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.answer_selection_list, parent, false)
return CardViewHolder(view)
}
override fun getItemCount() = answers.size
override fun onBindViewHolder(holder: CardViewHolder, position: Int) {
val singleAnswer = answers[position]
holder.bind(singleAnswer,position)
}
}
package org.cardelli.adapter
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.navigation.findNavController
import androidx.recyclerview.widget.RecyclerView
import org.cardelli.R
import org.cardelli.fragments.practice.MultipleChoiceFragmentDirections
import org.cardelli.model.Answer
import android.text.TextWatcher
import android.text.Editable
import kotlinx.android.synthetic.main.answer_value_list.view.*
import org.cardelli.fragments.practice.PracticeOverviewFragment
/**
* Adapter for the course list RecyclerView displayed in the [org.cardelli.fragments.HomeFragment]
*/
class AnswerValueListAdapter(private val answers: List<Answer>,val overview:PracticeOverviewFragment): RecyclerView.Adapter<AnswerValueListAdapter.CardViewHolder>(),AnswerEvaluator {
override fun isCorrect():Pair<Boolean,String>{
var vs = inputs.values
var rs:MutableSet<String> = mutableSetOf()
val buf = StringBuffer()
for (v in vs) buf.append(v+"\n")
for (ans in answers) rs.add(ans.antwort?.trim()?.toLowerCase()?:"")
//why does equals of sets nit work????
for (r in rs) if(!vs.contains(r)) return Pair(false,buf.toString());
for (v in vs) if(!rs.contains(v)) return Pair(false,buf.toString());
return Pair(true,buf.toString())
}
val inputs:MutableMap<Int,String> = mutableMapOf()
inner class CardViewHolder(private val view: View): RecyclerView.ViewHolder(view) {
fun bind(answer: Answer,pos:Int) {
view.solutionInput.addTextChangedListener(object:TextWatcher{
override fun afterTextChanged(s: Editable?) {
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
inputs.put(pos,view.solutionInput.getText().toString().trim().toLowerCase())
}
})
// view.answerText.text = answer.antwort
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CardViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.answer_value_list, parent, false)
return CardViewHolder(view)
}
override fun getItemCount() = answers.size
override fun onBindViewHolder(holder: CardViewHolder, position: Int) {
val singleAnswer = answers[position]
holder.bind(singleAnswer,position)
}
}
package org.cardelli.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import org.cardelli.model.Correctness
import org.cardelli.R
import kotlinx.android.synthetic.main.correctness_icon.view.*
/**
* Adapter for the correctness indicator RecyclerView
*/
class CorrectnessIconsAdapter(private val booleans: MutableList<Correctness>) : RecyclerView.Adapter<CorrectnessIconsAdapter.ViewHolder>() {
class ViewHolder(private val view: View): RecyclerView.ViewHolder(view) {
fun bind(solution: Correctness) {
when (solution) {
Correctness.UNSOLVED -> view.icon.setImageResource(R.drawable.ic_baseline_brightness_1_24)
Correctness.INCORRECT -> view.icon.setImageResource(R.drawable.ic_baseline_close_24)
Correctness.CORRECT -> view.icon.setImageResource(R.drawable.ic_baseline_done_24)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.correctness_icon, parent, false)
return ViewHolder(view)
}
override fun getItemCount() = booleans.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(booleans[position])
}
fun updateIcon(position: Int, isCorrect: Boolean) {
if (isCorrect) {
booleans[position] = Correctness.CORRECT
} else {
booleans[position] = Correctness.INCORRECT
}
notifyItemChanged(position)
}
}
package org.cardelli.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.navigation.findNavController
import androidx.recyclerview.widget.RecyclerView
import org.cardelli.R
import org.cardelli.fragments.HomeFragmentDirections
import org.cardelli.model.Course
import kotlinx.android.synthetic.main.course_card.view.*
/**
* Adapter for the course list RecyclerView displayed in the [org.cardelli.fragments.HomeFragment]
*/
class CourseListAdapter(private val courses: List<Course>): RecyclerView.Adapter<CourseListAdapter.CardViewHolder>() {
class CardViewHolder(private val view: View): RecyclerView.ViewHolder(view) {
fun bind(course: Course) {
view.course_title.text = course.title
view.card.setOnClickListener {
val action = HomeFragmentDirections.actionHomeFragmentToPracticeOverviewFragment(course.id)
view.findNavController().navigate(action)
}
view.courseProgressBarCard.max = 100
view.courseProgressBarCard.progress = course.progress
view.courseProgressPercentage.text = "${course.progress} %"
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CardViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.course_card, parent, false)
return CardViewHolder(view)
}
override fun getItemCount() = courses.size
override fun onBindViewHolder(holder: CardViewHolder, position: Int) {
val singleCourse = courses[position]
holder.bind(singleCourse)
}
}
package org.cardelli.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import org.cardelli.Constants
import org.cardelli.R
import org.cardelli.model.Exercise
import kotlinx.android.synthetic.main.result_overview_entry.view.*
import kotlin.math.floor
/**
* Adapter for the RecyclerView used in the [org.cardelli.fragments.practice.ResultsOverviewFragment]
*/
class ResultsListAdapter(
private val exercises: List<Exercise>,
private val booleans: List<Boolean>
): RecyclerView.Adapter<ResultsListAdapter.ViewHolder>() {
class ViewHolder(private val view: View): RecyclerView.ViewHolder(view) {
fun bind(exercise: Exercise, boolean: Boolean, position: Int) {
itemView.exerciseName.text = "${position + 1}: ${exercise.name}"
itemView.correctness.text = if (boolean) "Richtig" else "Falsch"
val updatedExerciseProgress = if (boolean) (exercise.progress + 1) else exercise.progress
var updatedExerciseProgressPercentage: Double = updatedExerciseProgress.toDouble() / Constants.getMaxProgressForExerciseType(
exercise
).toDouble() * 100
updatedExerciseProgressPercentage = floor(updatedExerciseProgressPercentage * 10) /10
itemView.exerciseProgress.text = "$updatedExerciseProgressPercentage %"
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.result_overview_entry, parent, false)
return ViewHolder(view)
}
override fun getItemCount() = exercises.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {