Compare commits

..

63 Commits

Author SHA1 Message Date
Paul
442ef70b8d update AboutActivity 2024-10-24 13:59:01 +02:00
BuildTools
ec3a95fc2d Merge remote-tracking branch 'origin/master' 2024-10-14 15:26:13 +02:00
BuildTools
b07cd27fce Why tf is "Index": 0 @ index = 1 and "Index": 2 @ index = 0 2024-10-14 15:25:58 +02:00
matthias
39c037a9ab delete empty line 2024-07-05 17:44:51 +02:00
matthias
f6956349e0 Small adjustment
3 month --> 3 months
2024-06-26 06:59:01 +02:00
BuildTools
a41bd9cbb3 Small adjustment 2024-06-21 16:52:28 +02:00
BuildTools
0db1e78cd4 Small adjustments 2024-06-16 13:16:56 +02:00
matthias
7bdcb7e3fb Added restart of app when first setup isn't done
when going back via phone feature, user sees the empty timetable
2024-06-09 23:46:58 +02:00
matthias
d36cadeb18 Fixed typo 2024-06-09 23:45:32 +02:00
BuildTools
1056dc39bf Add deleteAllData 2024-06-09 21:58:28 +02:00
matthias
15cadb76c6 Minor appearance adjustments
added scaling with 1.1f(lesson indexes & "Pause") / 0.8f(times)
2024-06-09 13:22:00 +02:00
matthias
41b54f15ea Major Bugfix
fixed removal of lessons
2024-06-09 13:20:31 +02:00
matthias
50eb19a464 minor code cleanup 2024-06-08 14:16:01 +02:00
matthias
4c3de94dd3 Stundenplan bugfixes
fixed indexing with wrong variable for breaks in core
added compatibility for lessons without subject, room or teacher
added horizontal scroll for too long user input
added info icon with alert dialog for scroll explanation
2024-06-08 14:15:43 +02:00
matthias
30ad4bed64 Stundenplan most definitely finished
Added Commenting to StundenplanActivity and TimeTableSetupActivity
2024-06-07 02:13:02 +02:00
BuildTools
f091451fe8 Add Ko-fi
Remove Langlebigstesisotop
Update stuff
2024-06-06 19:21:33 +02:00
1c665fea26 README.md updated
added android version minimum 8.0 as minimum api level defined in CleverClass/app/build.gradle.kts is 26
enlarged link to the license as version and release date belong to its name
2024-06-05 08:07:46 +00:00
matthias
41be804d5b Stundenplan commit for meeting
Setup most definitely done
Display still has some issues
2024-06-05 06:25:15 +02:00
matthias
173a3fc5a5 code cleanup
changed contentDescription = "" to null
2024-06-05 06:23:35 +02:00
BuildTools
718fe65a75 Add README
Update some stuff
2024-06-04 16:19:11 +02:00
BuildTools
e9ca2d8704 Add DSB Zoom
Minor changes PSE
2024-06-03 16:27:39 +02:00
BuildTools
75302eed36 Update Logo
Small changes in PSE
2024-06-02 22:02:30 +02:00
Matthias
ac7ce0ad3d Progressed with the Stundenplan
Progressed with the TimeTableSetupActivity -> (nearly done, only individual day setup left)
hardly progressed with the StundenplanActivity
switching back to Desktop tomorrow evening (progress from holidays)
2024-05-31 23:29:52 +02:00
Matthias
344aa1ef9f Merge remote-tracking branch 'origin/master' 2024-05-27 17:53:02 +02:00
Matthias
2aae27e77c fixed typo "Speichen" --> "Speichern" 2024-05-27 17:52:35 +02:00
2a6e970c71 app/src/main/assets/elements_data.json aktualisiert 2024-05-23 20:47:25 +00:00
BuildTools
e64a010e1d Fix PSE jsonIndex
Update About Page
2024-05-23 21:07:11 +02:00
cfdf00e0a5 app/src/main/assets/elements_data.json aktualisiert 2024-05-23 17:40:58 +00:00
matthias
d982d71f31 Started StundenplanActivity
began with the implementation of sharedPreferences
added LessonStoreManager
added TimeTableSetupActivity (-> + entries in the AndroidManifest.xml & strings.xml)
committed version of "Stundenplan" is unfinished and malfunctional (switching to Laptop for holidays)
2024-05-20 21:36:47 +02:00
BuildTools
25bbf0f6b9 PSE remove groups 2024-05-17 13:31:10 +02:00
BuildTools
c54a79e218 Polish PSE 2024-05-16 19:20:28 +02:00
BuildTools
7941081b90 Finish Mebis 2024-05-14 17:23:16 +02:00
BuildTools
b2e4a4f093 Start Mebis
Fix App crash DSBmobile
2024-04-30 17:16:02 +02:00
BuildTools
c16cc2d364 Finish DSBmobile 2024-04-25 19:28:37 +02:00
matthias
d3a9bf72c9 removed the ">>>" in the dialog
added Modifier.wrapContentWidth(unbounded = true) to dialogs
2024-04-25 16:26:43 +02:00
matthias
f2779cb442 Merge remote-tracking branch 'origin/master' 2024-04-24 21:52:11 +02:00
matthias
1012287c4c put PSE Boxes into loop
added colors list
changed the dialog assignment from list to a single var
added new data from elements_data.json into the dialog
2024-04-24 21:51:56 +02:00
matthias
d77c20e890 cosmetical change 2024-04-24 21:48:43 +02:00
matthias
2a32bcebcc Added "Gruppe", "Periode/Hauptquantenzahl" & "Schale" so PSE GUI is clean 2024-04-24 21:32:59 +02:00
BuildTools
98b68d2d70 Color adjustments 2024-04-24 20:25:52 +02:00
BuildTools
f669ba29f0 Fix back icon PSE 2024-04-23 16:36:23 +02:00
matthias
afc4f7e30a hard-coded elementsymbol into Array "elementButtonCurrent" 2024-04-23 15:38:52 +02:00
matthias
70408661a8 removed "Elementsymbol" from all objects 2024-04-23 15:36:15 +02:00
matthias
515e73bb94 complete redesign 2024-04-22 23:36:31 +02:00
matthias
4c4763dc9b moved data reading to PSEActivity 2024-04-22 23:34:34 +02:00
matthias
f6a6d8c367 switch from txt /w bufferedReader to JSON 2024-04-22 23:34:14 +02:00
matthias
82dadd7e21 removed readData() 2024-04-22 23:33:45 +02:00
matthias
7116edc376 removed landscape force for PSEActivity 2024-04-22 23:32:26 +02:00
matthias
f37d597aef removed dialog quit string 2024-04-22 23:31:58 +02:00
BuildTools
cd9e26fbe5 Update DSBmobile 2024-04-21 20:24:22 +02:00
matthias
2e1f335a47 appeared as unversioned revert push if i breaks the app for anyone else ignore 2024-04-20 23:11:02 +02:00
matthias
25a4992e43 Changed input to only values 2024-04-20 23:04:19 +02:00
matthias
39a24020d2 Finished PSEActivity 2024-04-20 23:04:03 +02:00
BuildTools
ee1239750f Add DSBDownloader
Add update DSBActivity MainContent
2024-04-20 15:15:27 +02:00
matthias
6e24a54682 Merge remote-tracking branch 'origin/master' 2024-04-18 17:53:25 +02:00
matthias
7f9bacdf28 added information to the dialog 2024-04-18 17:52:57 +02:00
matthias
180d2b2532 removed useless information and correlating Strings 2024-04-18 17:52:14 +02:00
matthias
c3c487ac54 landscape mode for PSEActivity 2024-04-18 17:51:21 +02:00
BuildTools
90098cc83c Finish NotenActivity and FachActivity 2024-04-18 16:42:16 +02:00
BuildTools
888d737965 Update colors
Add average to NotenActivity
Fix Bugs in NotenActivity and FachActivity
2024-04-18 13:22:45 +02:00
BuildTools
dbfed92a42 Add DSBActivity and MebisActivity 2024-04-17 15:32:44 +02:00
BuildTools
100dfd9c42 Add save function to grades 2024-04-16 22:45:54 +02:00
BuildTools
46f0ae21a6 Add select functionality to grades 2024-04-11 19:53:03 +02:00
60 changed files with 5412 additions and 2369 deletions

2
.idea/compiler.xml generated
View File

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

View File

@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<value>
<entry key="All Tests">
<State />
</entry>
<entry key="PSEActivity">
<State />
</entry>
<entry key="app">
<State />
</entry>
</value>
</component>
</project>

4
.idea/gradle.xml generated
View File

@@ -4,8 +4,10 @@
<component name="GradleSettings"> <component name="GradleSettings">
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" /> <option name="gradleJvm" value="18" />
<option name="modules"> <option name="modules">
<set> <set>
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />

2
.idea/misc.xml generated
View File

@@ -1,6 +1,6 @@
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_18" default="true" project-jdk-name="18" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">

View File

@@ -1,2 +1,32 @@
# CleverClass # CleverClass
CleverClass ist eine Android-App, die Schülern hilft, ihre Schulnoten zu verwalten, ihren Durchschnitt zu berechnen, den Vertretungsplan von DSBmobile einzusehen, den Stundenplan zu speichern und anzuzeigen sowie ein Periodensystem zu nutzen.
## Funktionen und Verwendung
- **Notenverwaltung:** Trage deine Schulnoten ein und berechne deinen Durchschnitt. Perfekt, um alle Noten aus der Schule übersichtlich zu speichern.
- **Vertretungsplan:** Rufe den aktuellen Vertretungsplan von DSBmobile direkt in der App auf.
- **Stundenplan:** Trage deinen Stundenplan ein, speichere und verwalte ihn in der App, um immer den Überblick zu behalten.
- **Periodensystem:** Nutze das integrierte Periodensystem, um schnell Informationen zu chemischen Elementen nachzuschlagen.
## Installation
CleverClass kann einfach über den Play Store installiert werden oder durch das Herunterladen der APK-Datei:
Play Store:
- Öffne den Google Play Store auf deinem Android-Gerät
- Suche nach "CleverClass"
- Tippe auf "Installieren"
APK-Datei:
- Lade die APK-Datei von der [Website](https://google.com/) herunter
- Öffne die heruntergeladene APK-Datei auf deinem Android-Gerät
- Folge den Anweisungen auf dem Bildschirm, um die Installation abzuschließen
## Anforderungen
Betriebssystem: Android 8.0 oder neuer
## Autoren
- Paul Posch
- Matthias Meyer
- Jakub Szarko
- Emilian Bührer
## Lizenz
Dieses Projekt steht unter der [GNU General Public License Version 3, 29 June 2007](LICENSE).
## Sonstiges
Dieses Projekt ist als Schulprojekt inerhalb von 3 Monaten entstanden.

View File

@@ -23,6 +23,7 @@ android {
buildTypes { buildTypes {
release { release {
isMinifyEnabled = false isMinifyEnabled = false
isDebuggable = false
proguardFiles( proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"), getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro" "proguard-rules.pro"
@@ -50,38 +51,21 @@ android {
} }
dependencies { dependencies {
implementation("androidx.core:core-ktx:1.9.0") implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
implementation("androidx.activity:activity-compose:1.8.2") implementation("androidx.activity:activity-compose:1.8.2")
implementation(platform("androidx.compose:compose-bom:2023.03.00"))
implementation("androidx.compose.ui:ui") implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics") implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview") implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3") implementation("androidx.compose.material3:material3")
implementation(platform("androidx.compose:compose-bom:2023.03.00")) implementation("androidx.datastore:datastore-preferences:1.0.0")
implementation(platform("androidx.compose:compose-bom:2023.03.00")) implementation("com.google.code.gson:gson:2.9.0")
implementation(platform("androidx.compose:compose-bom:2023.03.00"))
implementation(platform("androidx.compose:compose-bom:2023.03.00"))
implementation(platform("androidx.compose:compose-bom:2023.03.00"))
implementation(platform("androidx.compose:compose-bom:2023.03.00"))
implementation(platform("androidx.compose:compose-bom:2023.03.00"))
implementation(platform("androidx.compose:compose-bom:2023.03.00"))
implementation(platform("androidx.compose:compose-bom:2023.03.00")) implementation(platform("androidx.compose:compose-bom:2023.03.00"))
testImplementation("junit:junit:4.13.2") testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4") androidTestImplementation("androidx.compose.ui:ui-test-junit4")
androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00")) androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00"))
androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00"))
androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00"))
androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00"))
androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00"))
androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00"))
androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00"))
androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00"))
androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00"))
debugImplementation("androidx.compose.ui:ui-tooling") debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest") debugImplementation("androidx.compose.ui:ui-test-manifest")
} }

View File

@@ -2,6 +2,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application <application
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
@@ -12,6 +15,21 @@
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.CleverClass" android:theme="@style/Theme.CleverClass"
tools:targetApi="31"> tools:targetApi="31">
<activity
android:name=".DSBLoginActivity"
android:exported="false"
android:label="@string/title_activity_dsblogin"
android:theme="@style/Theme.CleverClass" />
<activity
android:name=".DSBDayViewActivity"
android:exported="false"
android:label="@string/title_activity_dsbday_view"
android:theme="@style/Theme.CleverClass" />
<activity
android:name=".DSBActivity"
android:exported="false"
android:label="@string/title_activity_dsbactivity"
android:theme="@style/Theme.CleverClass" />
<activity <activity
android:name=".FachActivity" android:name=".FachActivity"
android:exported="false" android:exported="false"
@@ -22,6 +40,11 @@
android:exported="false" android:exported="false"
android:label="@string/title_activity_noten" android:label="@string/title_activity_noten"
android:theme="@style/Theme.CleverClass" /> android:theme="@style/Theme.CleverClass" />
<activity
android:name=".TimeTableSetupActivity"
android:exported="false"
android:label="@string/title_activity_timetable_setup"
android:theme="@style/Theme.CleverClass" />
<activity <activity
android:name=".StundenplanActivity" android:name=".StundenplanActivity"
android:exported="false" android:exported="false"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View File

@@ -1,9 +1,15 @@
package com.schoolapp.cleverclass package com.schoolapp.cleverclass
import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
@@ -14,6 +20,7 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
@@ -23,7 +30,11 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.schoolapp.cleverclass.ui.theme.CleverClassTheme import com.schoolapp.cleverclass.ui.theme.CleverClassTheme
@@ -50,14 +61,16 @@ fun AboutContent(activity: ComponentActivity){
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(MaterialTheme.colorScheme.primaryContainer), colors = TopAppBarDefaults.centerAlignedTopAppBarColors(MaterialTheme.colorScheme.primaryContainer),
title = { title = {
Text(text = "About", Text(text = "About",
style = MaterialTheme.typography.headlineSmall style = MaterialTheme.typography.headlineSmall,
color = MaterialTheme.colorScheme.onPrimaryContainer
)}, )},
navigationIcon = { navigationIcon = {
IconButton(onClick = { activity.finish() }) { IconButton(onClick = { activity.finish() }) {
Icon( Icon(
imageVector = Icons.Filled.ArrowBack, imageVector = Icons.Filled.ArrowBack,
contentDescription = null, contentDescription = null,
modifier = Modifier.size(28.dp) modifier = Modifier.size(28.dp),
tint = MaterialTheme.colorScheme.onPrimaryContainer
) )
} }
}, },
@@ -68,14 +81,46 @@ fun AboutContent(activity: ComponentActivity){
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
.fillMaxWidth()) .fillMaxWidth())
{ {
Spacer(modifier = Modifier.height(16.dp)) AboutTextElement("This Product is published under the\nGNU General Public License\nVersion 3, 29 June 2007", 16.dp)
Text(text = "This Product is published under the\nGNU General Public License\nVersion 3, 29 June 2007", AboutTextElement("Developed by:\n- Paul Posch\n- Matthias Meyer\n- Jakub Szarko\n- Emilian Bührer", 32.dp)
style = MaterialTheme.typography.bodySmall, AboutTextElement("Fun Facts:\nThis app consists of 4.189 lines of code\nThe repository is 768KB big\nThe development took about 3 months", 64.dp)
modifier = Modifier.padding(start = 16.dp, end = 16.dp))
Spacer(modifier = Modifier.height(32.dp)) Spacer(modifier = Modifier.height(32.dp))
Text(text = "Developed by:\n- Paul Posch\n- Matthias Meyer\n- Jakub Szarko\n- Emilian Bührer", Divider()
style = MaterialTheme.typography.bodySmall, Box(contentAlignment = Alignment.CenterStart,
modifier = Modifier.padding(start = 16.dp, end = 16.dp)) modifier = Modifier
.fillMaxWidth()
.clickable {
val intent =
Intent(Intent.ACTION_VIEW, Uri.parse("https://ko-fi.com/cleverclass"))
activity.startActivity(intent)
}
) {
Row {
Text(
text = "Support the Devs",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onBackground,
modifier = Modifier.padding(16.dp)
)
Spacer(modifier = Modifier.weight(1f))
Image(
painter = painterResource(R.drawable.baseline_open_in_browser_24),
contentDescription = null,
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onBackground),
modifier = Modifier.padding(16.dp)
)
}
}
} }
} }
}
@Composable
fun AboutTextElement(text: String, space: Dp){
Spacer(modifier = Modifier.height(space))
Text(text = text,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onBackground,
modifier = Modifier.padding(start = 16.dp, end = 16.dp))
} }

View File

@@ -0,0 +1,254 @@
package com.schoolapp.cleverclass
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.graphics.BitmapFactory
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.unit.dp
import com.schoolapp.cleverclass.ui.theme.CleverClassTheme
import com.schoolapp.cleverclass.ui.theme.MiddleColor
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.json.JSONObject
import java.io.File
class DSBActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val sharedPreferences = this.getSharedPreferences("DSBmobile", Context.MODE_PRIVATE)
val username = sharedPreferences.getString("username", "").toString()
val password = sharedPreferences.getString("password", "").toString()
setContent {
CleverClassTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
if(username.isEmpty() || password.isEmpty()) {
val intent = Intent(this@DSBActivity, DSBLoginActivity::class.java).apply {
putExtra(DSBLoginActivity.TYPE_KEY, "FirstTime")
}
startActivity(intent)
}
DSBContent(activity = this)
}
}
}
}
}
// Content of DSBmobile
@OptIn(ExperimentalMaterial3Api::class, DelicateCoroutinesApi::class)
@Composable
fun DSBContent(activity: ComponentActivity){
val mainFolder = File(activity.filesDir, "dataDSB")
val sharedPreferences = activity.getSharedPreferences("DSBmobile", Context.MODE_PRIVATE)
var loadingState by remember {
mutableStateOf(-2)
}
var folders by remember {
mutableStateOf(mainFolder.listFiles())
}
LaunchedEffect(Unit) {
GlobalScope.launch(Dispatchers.IO) {
loadingState = downloadDSB(activity)
folders = mainFolder.listFiles()
}
}
Column {
TopAppBar(
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(MaterialTheme.colorScheme.primaryContainer),
title = {
Text(text = "DSBmobile",
style = MaterialTheme.typography.headlineSmall,
color = MaterialTheme.colorScheme.onPrimaryContainer
)},
navigationIcon = {
IconButton(onClick = { activity.finish() }) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = null,
modifier = Modifier.size(28.dp),
tint = MaterialTheme.colorScheme.onPrimaryContainer
)
}
},
actions = {
if(loadingState != -2) {
IconButton(
onClick = {
loadingState = -2
GlobalScope.launch(Dispatchers.IO) {
loadingState = downloadDSB(activity)
folders = mainFolder.listFiles()
}
}
) {
Icon(
imageVector = Icons.Filled.Refresh,
contentDescription = null,
modifier = Modifier.size(28.dp),
tint = MaterialTheme.colorScheme.onPrimaryContainer
)
}
}
},
modifier = Modifier.fillMaxWidth()
)
when (loadingState) {
-2 -> {
LoadingScreen()
}
NO_INTERNET_CONNECTION_CODE -> {
DSBError("Keine Internetverbindung")
}
1 ->{
DSBError("Login fehlgeschlagen")
}
0 -> {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.verticalScroll(rememberScrollState())
.fillMaxWidth()
) {
Spacer(modifier = Modifier.height(32.dp))
folders.forEach { folder ->
DayPrefab(folder, activity)
Spacer(modifier = Modifier.height(32.dp))
Divider()
Spacer(modifier = Modifier.height(32.dp))
}
}
}
else -> {
DSBError("Leider ist ein Problem aufgetreten\nFehlercode: $loadingState")
}
}
}
// changes state when Shared Preference is updated
val observer = remember {
SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
if (key == "password" || key == "username") {
loadingState = -2
GlobalScope.launch(Dispatchers.IO) {
loadingState = downloadDSB(activity)
folders = mainFolder.listFiles()
}
}
}
}
// handles observer disposal
DisposableEffect(Unit) {
sharedPreferences.registerOnSharedPreferenceChangeListener(observer)
onDispose {
sharedPreferences.unregisterOnSharedPreferenceChangeListener(observer)
}
}
}
@Composable
fun DSBError(message: String){
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
){
Text(
text = message,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onBackground
)
}
}
@Composable
fun DayPrefab(folder: File, activity: Context){
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.fillMaxWidth()
.height(300.dp)
.clickable {
val intent = Intent(activity, DSBDayViewActivity::class.java).apply {
putExtra(DSBDayViewActivity.FOLDER_KEY, folder.absolutePath)
}
activity.startActivity(intent)
}
) {
val information = JSONObject(File(folder, "info.json").readText())
val previewImageBitmap = BitmapFactory.decodeFile(File(folder, "0.jpg").absolutePath)
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = "${information.get("FormatInfo")} - ${information.get("PlanInfo").toString().removeSuffix(".pdf")}",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onBackground
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = information.get("Date").toString(),
style = MaterialTheme.typography.bodySmall,
color = MiddleColor
)
Spacer(modifier = Modifier.height(8.dp))
Image(
bitmap = previewImageBitmap.asImageBitmap(),
contentDescription = null
)
}
}
}

View File

@@ -0,0 +1,139 @@
package com.schoolapp.cleverclass
import android.graphics.BitmapFactory
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.gestures.detectTransformGestures
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import com.schoolapp.cleverclass.ui.theme.CleverClassTheme
import org.json.JSONObject
import java.io.File
import java.lang.Float.max
import java.lang.Float.min
private lateinit var folder : File
private lateinit var information : JSONObject
class DSBDayViewActivity : ComponentActivity() {
companion object{
const val FOLDER_KEY = "folder_key"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
folder = File(intent.getStringExtra(FOLDER_KEY) ?: "")
information = JSONObject(File(folder, "info.json").readText())
setContent {
CleverClassTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
DSBDayViewContent(activity = this)
}
}
}
}
}
// Content of DSBDayView
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DSBDayViewContent(activity: ComponentActivity){
var scale by remember { mutableStateOf(1f) }
var XOffset by remember { mutableStateOf(0f) }
var YOffset by remember { mutableStateOf(0f) }
Column{
TopAppBar(
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(MaterialTheme.colorScheme.primaryContainer),
title = {
Text(
text = information.get("PlanInfo").toString().removeSuffix(".pdf"),
style = MaterialTheme.typography.headlineSmall,
color = MaterialTheme.colorScheme.onPrimaryContainer
)},
navigationIcon = {
IconButton(onClick = { activity.finish() }) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = null,
modifier = Modifier.size(28.dp),
tint = MaterialTheme.colorScheme.onPrimaryContainer
)
}
},
modifier = Modifier
.fillMaxWidth()
.zIndex(1f)
)
val imageFiles = folder.listFiles { file -> file.extension == "jpg" }?.toList()
LazyColumn(
userScrollEnabled = true,
modifier = Modifier
.padding(8.dp)
.pointerInput(Unit) {
detectTransformGestures { _, move, zoom, _ ->
scale = max(min(zoom * scale, 2f), 1f)
val maxXOffset = 520 * (scale - 1)
val maxYOffset = 992 * (scale - 1)
XOffset = max(min(XOffset + move.x, maxXOffset), -maxXOffset)
YOffset = max(min(YOffset + move.y, maxYOffset), -maxYOffset)
}
}
.graphicsLayer(
scaleX = scale,
scaleY = scale,
translationX = XOffset,
translationY = YOffset
)
) {
imageFiles?.forEach {
item {
Image(
bitmap = BitmapFactory.decodeFile(it.absolutePath).asImageBitmap(),
contentDescription = null,
contentScale = ContentScale.FillWidth,
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(8.dp))
}
}
}
}
}

View File

@@ -0,0 +1,226 @@
package com.schoolapp.cleverclass
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.util.Base64
import android.util.Log
import org.json.JSONObject
import java.io.ByteArrayOutputStream
import java.io.File
import java.net.HttpURLConnection
import java.net.URL
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream
import java.io.ByteArrayInputStream
import java.io.FileOutputStream
import java.net.SocketException
import java.time.LocalDateTime
import java.util.UUID
suspend fun downloadDSB(context: Context): Int{
val sharedPreferences = context.getSharedPreferences("DSBmobile", Context.MODE_PRIVATE)
val username = sharedPreferences.getString("username", "").toString()
val password = sharedPreferences.getString("password", "").toString()
if(!isInternetAvailable(context))
return NO_INTERNET_CONNECTION_CODE
val mainDir = createDataFolder(context)
val response = downloadDataTask(username = username, password = password)
if(response == "-1")
return NO_INTERNET_CONNECTION_CODE
val jsonResponse = JSONObject(response)
if(jsonResponse.get("Resultcode") != 0)
return jsonResponse.getInt("Resultcode")
return downloadImagesTask(jsonResponse, mainDir)
}
private fun downloadDataTask(username: String, password: String) : String {
return try {
val requestBody = createRequestBody(username = username, password = password)
val jsonRequest = createJsonRequest(requestBody)
val responseData = fetchData(jsonRequest)
responseData
} catch (e: Exception) {
Log.e("HTTP", "Error: ${e.message}")
"-1"
}
}
private fun createRequestBody(username: String, password: String): String {
val currentDateTime = LocalDateTime.now().toString()
val requestBody = JSONObject().apply {
put("UserId", username)
put("UserPw", password)
put("AppVersion", "2.5.9")
put("Language", "de")
put("OsVersion", "28 8.0")
put("AppId", UUID.randomUUID().toString())
put("Device", "SM-G930F")
put("BundleId", "de.heinekingmedia.dsbmobile")
put("Date", currentDateTime)
put("LastUpdate", currentDateTime)
}
return Base64.encodeToString(GZIPCompress(requestBody.toString().toByteArray()), Base64.DEFAULT)
}
private fun createJsonRequest(requestBody: String): JSONObject {
return JSONObject().apply {
put("req", JSONObject().apply {
put("Data", requestBody)
put("DataType", 1)
})
}
}
private fun fetchData(jsonRequest: JSONObject): String {
val url = URL("https://app.dsbcontrol.de/JsonHandler.ashx/GetData")
val connection = (url.openConnection() as HttpURLConnection).apply {
requestMethod = "POST"
setRequestProperty("Content-Type", "application/json")
doOutput = true
}
connection.outputStream.use { outputStream ->
outputStream.write(jsonRequest.toString().toByteArray())
}
return if (connection.responseCode == HttpURLConnection.HTTP_OK) {
val responseData = connection.inputStream.bufferedReader().use { it.readText() }
val compressedResponseData = JSONObject(responseData).getString("d")
decompressGZIPAndDecodeBase64(compressedResponseData)
} else {
Log.e("HTTP", "Error response code: ${connection.responseCode}")
""
}
}
private fun GZIPCompress(data: ByteArray): ByteArray {
return ByteArrayOutputStream().use { outputStream ->
GZIPOutputStream(outputStream).bufferedWriter().use { it.write(String(data)) }
outputStream.toByteArray()
}
}
private fun decompressGZIPAndDecodeBase64(compressedData: String): String {
val compressedByteArray = Base64.decode(compressedData, Base64.DEFAULT)
val inputStream = ByteArrayInputStream(compressedByteArray)
val outputStream = ByteArrayOutputStream()
GZIPInputStream(inputStream).use { gzipInputStream ->
val buffer = ByteArray(1024)
var len: Int
while (gzipInputStream.read(buffer).also { len = it } > 0) {
outputStream.write(buffer, 0, len)
}
}
return outputStream.toString("UTF-8")
}
private fun downloadImagesTask(jsonResponse: JSONObject, mainDir : File) : Int {
clearFolder(mainDir)
var jsonDaysObject = jsonResponse.getJSONArray("ResultMenuItems")
for (i in 0 until jsonDaysObject.length()) {
if (jsonDaysObject.getJSONObject(i).get("Index") == 0) {
jsonDaysObject = jsonDaysObject.getJSONObject(i).getJSONArray("Childs")
break
}
}
for (i in 0 until jsonDaysObject.length()) {
if (jsonDaysObject.getJSONObject(i).get("Index") == 0) {
jsonDaysObject = jsonDaysObject.getJSONObject(i).getJSONObject("Root").getJSONArray("Childs")
break
}
}
for (d in 0 until jsonDaysObject.length()) {
val jsonDayObject = jsonDaysObject.getJSONObject(d)
val dayID = jsonDayObject.get("Id")
val day = File(mainDir, dayID.toString())
day.mkdir()
createDayInformationJson(day, jsonDayObject)
val jsonPagesObject = jsonDayObject.getJSONArray("Childs")
for (p in 0 until jsonPagesObject.length()) {
val jsonPageObject = jsonPagesObject.getJSONObject(p)
val pageIndex = jsonPageObject.get("Index")
val imageLink = jsonPageObject.get("Detail").toString()
val downloadState = downloadImage(day, URL(imageLink), "$pageIndex.jpg")
if (downloadState != 0)
return downloadState
}
}
return 0
}
private fun createDayInformationJson(folder: File, jsonDayObject: JSONObject){
val infoFile = File(folder, "info.json")
val date = jsonDayObject.get("Date")
val formatInfo = jsonDayObject.get("Title")
val planInfo = jsonDayObject.getJSONArray("Childs").getJSONObject(0).get("Title")
val infoJson = JSONObject()
infoJson.put("Date", date)
infoJson.put("FormatInfo", formatInfo)
infoJson.put("PlanInfo", planInfo)
infoFile.writeText(infoJson.toString())
}
private fun downloadImage(folder: File, url: URL, fileName: String) : Int {
val file = File(folder, fileName)
try {
val connection = url.openConnection()
val inputStream = connection.getInputStream()
val outputStream = FileOutputStream(file)
val buffer = ByteArray(1024)
var bytesRead: Int
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
outputStream.write(buffer, 0, bytesRead)
}
inputStream.close()
outputStream.close()
}
catch (e: SocketException){
Log.d("ImageDownloader", e.toString())
return NO_INTERNET_CONNECTION_CODE
}
return 0
}
private fun createDataFolder(context: Context) : File {
val folderName = "dataDSB"
val folder = File(context.filesDir, folderName)
if (!folder.exists()) {
folder.mkdir()
}
return folder
}
private fun isInternetAvailable(context: Context): Boolean {
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val network = connectivityManager.activeNetwork
return try {
val networkCapabilities = connectivityManager.getNetworkCapabilities(network)
networkCapabilities?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) ?: false
} catch (e: SecurityException) {
Log.e("Internet", e.toString())
false
}
}

View File

@@ -0,0 +1,316 @@
package com.schoolapp.cleverclass
import android.content.Context
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.selection.TextSelectionColors
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
import com.schoolapp.cleverclass.ui.theme.CleverClassTheme
import com.schoolapp.cleverclass.ui.theme.InputPrimaryColor
import com.schoolapp.cleverclass.ui.theme.InputSecondaryColor
private lateinit var activityType : String
class DSBLoginActivity : ComponentActivity() {
companion object{
const val TYPE_KEY = "type_key"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activityType = intent.getStringExtra(TYPE_KEY).toString()
setContent {
CleverClassTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
DSBLoginContent(activity = this)
}
}
}
}
}
// Content of DSBLogin
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DSBLoginContent(activity: ComponentActivity){
val inputFieldColors = TextFieldDefaults.outlinedTextFieldColors(
textColor = MaterialTheme.colorScheme.onPrimaryContainer,
containerColor = MaterialTheme.colorScheme.secondaryContainer,
cursorColor = InputPrimaryColor,
selectionColors = TextSelectionColors(
handleColor = InputPrimaryColor,
backgroundColor = InputSecondaryColor
),
focusedBorderColor = InputSecondaryColor,
unfocusedBorderColor = Color.Transparent
)
val sharedPreferences = activity.getSharedPreferences("DSBmobile", Context.MODE_PRIVATE)
val editor = sharedPreferences.edit()
var username by remember {
mutableStateOf(sharedPreferences.getString("username", "").toString())
}
var password by remember {
mutableStateOf(sharedPreferences.getString("password", "").toString())
}
var showPassword by remember {
mutableStateOf(false)
}
var showInfo by remember {
mutableStateOf(false)
}
var showDeleteConfirmation by remember {
mutableStateOf(false)
}
Column {
TopAppBar(
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(MaterialTheme.colorScheme.primaryContainer),
title = { Text(text = "") },
navigationIcon = {
IconButton(onClick = { activity.finish() }) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = null,
modifier = Modifier.size(28.dp),
tint = MaterialTheme.colorScheme.onPrimaryContainer
)
}
},
modifier = Modifier.fillMaxWidth()
)
Column(
verticalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxHeight()
.verticalScroll(rememberScrollState())
) {
Surface(
color = MaterialTheme.colorScheme.primaryContainer,
shape = RoundedCornerShape(20),
modifier = Modifier.padding(16.dp)
) {
Column(horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(16.dp)
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = "DSBmobile Login Daten",
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onPrimaryContainer,
modifier = Modifier.padding(10.dp)
)
Spacer(modifier = Modifier.weight(1f))
if (activityType == "Settings")
IconButton(onClick = { showDeleteConfirmation = !showDeleteConfirmation }) {
Icon(
imageVector = Icons.Outlined.Delete,
contentDescription = null,
modifier = Modifier.size(28.dp),
tint = MaterialTheme.colorScheme.onPrimaryContainer
)
}
else if (activityType == "FirstTime")
IconButton(onClick = { showInfo = !showInfo }) {
Icon(
imageVector = Icons.Outlined.Info,
contentDescription = null,
tint = MaterialTheme.colorScheme.onPrimaryContainer
)
}
}
Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField(
value = username,
onValueChange = { username = it },
placeholder = {
Text(
text = "Benutzername",
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.primaryContainer
)
},
modifier = Modifier.fillMaxWidth(),
singleLine = true,
shape = RoundedCornerShape(20),
textStyle = MaterialTheme.typography.labelMedium,
colors = inputFieldColors
)
Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField(
value = password,
onValueChange = { password = it },
placeholder = {
Text(
text = "Passwort",
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.primaryContainer
)
},
modifier = Modifier.fillMaxWidth(),
singleLine = true,
shape = RoundedCornerShape(20),
textStyle = MaterialTheme.typography.labelMedium,
colors = inputFieldColors,
visualTransformation = if(showPassword) VisualTransformation.None else PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
trailingIcon = {
IconButton(onClick = { showPassword = !showPassword })
{
Image(
painter = painterResource(
id = if(showPassword)
R.drawable.baseline_visibility_24
else
R.drawable.baseline_visibility_off_24
),
contentDescription = null,
)
}
}
)
Spacer(modifier = Modifier.height(10.dp))
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.clickable {
editor.putString("username", username)
editor.putString("password", password)
editor.apply()
activity.finish()
}
) {
Text(
text = "Speichern",
color = MaterialTheme.colorScheme.secondary,
style = MaterialTheme.typography.labelMedium,
modifier = Modifier.padding(10.dp)
)
}
}
}
}
}
if (showInfo) {
AlertDialog(
onDismissRequest = { showInfo = false },
text = {
Text(
text = "Der Benutzername und das Passwort können später in den Einstellungen geändert werden",
color = MaterialTheme.colorScheme.onPrimaryContainer,
style = MaterialTheme.typography.labelMedium)
},
confirmButton = {
Text(
text = "Schließen",
color = MaterialTheme.colorScheme.secondary,
style = MaterialTheme.typography.labelMedium,
modifier = Modifier.clickable { showInfo = false }
)
},
containerColor = MaterialTheme.colorScheme.primaryContainer,
modifier = Modifier
.padding(5.dp)
)
}
if (showDeleteConfirmation) {
AlertDialog(
onDismissRequest = { showDeleteConfirmation = false },
text = {
Text(
text = "Möchten Sie wirklich die Anmeldedaten löschen?",
color = MaterialTheme.colorScheme.onPrimaryContainer,
style = MaterialTheme.typography.labelMedium
)
},
confirmButton = {
Row(modifier = Modifier.fillMaxWidth()){
Text(
text = "Bestätigen",
color = MaterialTheme.colorScheme.secondary,
style = MaterialTheme.typography.labelMedium,
modifier = Modifier.clickable {
editor.putString("username", "")
editor.putString("password", "")
editor.apply()
showDeleteConfirmation = false
activity.finish()
}
)
Spacer(modifier = Modifier.weight(1f))
Text(
text = "Abbrechen",
color = MaterialTheme.colorScheme.secondary,
style = MaterialTheme.typography.labelMedium,
modifier = Modifier.clickable { showDeleteConfirmation = false }
)
}
},
containerColor = MaterialTheme.colorScheme.primaryContainer
)
}
}

View File

@@ -1,5 +1,7 @@
package com.schoolapp.cleverclass package com.schoolapp.cleverclass
import android.content.Context
import android.content.SharedPreferences.Editor
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
@@ -20,9 +22,12 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.KeyboardArrowUp import androidx.compose.material.icons.filled.KeyboardArrowUp
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material.icons.outlined.Edit
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.BottomAppBar import androidx.compose.material3.BottomAppBar
import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
@@ -37,6 +42,8 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@@ -45,18 +52,46 @@ import androidx.compose.ui.Alignment.Companion.CenterVertically
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.schoolapp.cleverclass.GradeStoreManager.getGrades
import com.schoolapp.cleverclass.GradeStoreManager.saveGrades
import com.schoolapp.cleverclass.ui.theme.CleverClassTheme import com.schoolapp.cleverclass.ui.theme.CleverClassTheme
import com.schoolapp.cleverclass.ui.theme.TextOnColouredButton import com.schoolapp.cleverclass.ui.theme.TextOnColouredButton
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
private var SAWeight = 0
private var name = "Test"
private var typesSelection = listOf("Test", "Ex", "Referat", "Mündlich", "Sonstiges")
private val gradesSelection = listOf(1, 2, 3, 4, 5, 6)
private val weightsSelection = listOf(0.5f, 1f, 1.5f, 2f)
class FachActivity : ComponentActivity() { class FachActivity : ComponentActivity() {
companion object { companion object {
const val ARGUMENT_KEY = "argument_key" const val NAME_KEY = "name_key"
const val WEIGHT_KEY = "weight_key"
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
// get subject argument from NotenActivity var grades = emptyList<GradeData>()
val name = intent.getStringExtra(ARGUMENT_KEY) ?: "missing subject"
// get arguments from NotenActivity
name = intent.getStringExtra(NAME_KEY) ?: "missing subject"
SAWeight = intent.getIntExtra(WEIGHT_KEY, 0)
if(SAWeight != 0 && typesSelection[0] != "Schulaufgabe")
typesSelection = listOf("Schulaufgabe") + typesSelection
CoroutineScope(Dispatchers.IO).launch {
getGrades(this@FachActivity, name).collect { savedGrades ->
grades = savedGrades.toMutableList()
}
}
setContent { setContent {
CleverClassTheme { CleverClassTheme {
@@ -64,7 +99,7 @@ class FachActivity : ComponentActivity() {
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background color = MaterialTheme.colorScheme.background
) { ) {
GradeContent(activity = this, name = name) GradeContent(this, grades)
} }
} }
} }
@@ -73,7 +108,21 @@ class FachActivity : ComponentActivity() {
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun GradeContent(activity: ComponentActivity, name : String){ fun GradeContent(activity: ComponentActivity, loadedGrades: List<GradeData>){
val sharedPreferences = activity.getSharedPreferences("gradeAverages", Context.MODE_PRIVATE)
val editor = sharedPreferences.edit()
var grades by remember {
mutableStateOf(loadedGrades)
}
var average by remember {
mutableStateOf(0.00f)
}
var showDeleteConfirmation by remember {
mutableStateOf(false)
}
val colors = listOf( val colors = listOf(
Color(0xFF536DFE), Color(0xFF536DFE),
Color(0xFF448AFF), Color(0xFF448AFF),
@@ -82,8 +131,15 @@ fun GradeContent(activity: ComponentActivity, name : String){
Color(0xFF64FFDA), Color(0xFF64FFDA),
Color(0xFF69F0AE) Color(0xFF69F0AE)
) )
var grades by remember {
mutableStateOf(emptyList<GradeData>()) LaunchedEffect(grades) {
average = calculateAverage(grades, editor)
}
DisposableEffect(activity) {
onDispose {
saveGradesStart(activity, grades)
}
} }
Scaffold( Scaffold(
@@ -92,17 +148,33 @@ fun GradeContent(activity: ComponentActivity, name : String){
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(MaterialTheme.colorScheme.primaryContainer), colors = TopAppBarDefaults.centerAlignedTopAppBarColors(MaterialTheme.colorScheme.primaryContainer),
title = { title = {
Text(text = name, Text(text = name,
style = MaterialTheme.typography.headlineSmall style = MaterialTheme.typography.headlineSmall,
color = MaterialTheme.colorScheme.onPrimaryContainer
)}, )},
navigationIcon = { navigationIcon = {
IconButton(onClick = { activity.finish() }) { IconButton(onClick = {
typesSelection = listOf("Test", "Ex", "Referat", "Mündlich", "Sonstiges")
saveGradesStart(activity, grades)
activity.finish()
}) {
Icon( Icon(
imageVector = Icons.Filled.ArrowBack, imageVector = Icons.Filled.ArrowBack,
contentDescription = null, contentDescription = null,
modifier = Modifier.size(28.dp) modifier = Modifier.size(28.dp),
tint = MaterialTheme.colorScheme.onPrimaryContainer
) )
} }
}, },
actions = {
IconButton(onClick = { showDeleteConfirmation = true }) {
Icon(
imageVector = Icons.Outlined.Delete,
contentDescription = null,
modifier = Modifier.size(28.dp),
tint = MaterialTheme.colorScheme.onPrimaryContainer
)
}
},
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
}, },
@@ -120,7 +192,8 @@ fun GradeContent(activity: ComponentActivity, name : String){
Text(text = "Schnitt:", Text(text = "Schnitt:",
style = MaterialTheme.typography.headlineSmall) style = MaterialTheme.typography.headlineSmall)
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
Text(text = "Ø0.00", Text(
text = "Ø" + formatFloat(average),
style = MaterialTheme.typography.headlineSmall) style = MaterialTheme.typography.headlineSmall)
Spacer(modifier = Modifier.width(16.dp)) Spacer(modifier = Modifier.width(16.dp))
} }
@@ -128,7 +201,12 @@ fun GradeContent(activity: ComponentActivity, name : String){
}, },
floatingActionButton = { floatingActionButton = {
FloatingActionButton( FloatingActionButton(
onClick = { grades = grades + GradeData(grades.size, colors.random()) }, onClick = { grades = grades + GradeData(
if (grades.isEmpty()) 0 else grades[grades.size - 1].id + 1,
colors.random(),
1,
1.0f,
"Typ") },
shape = RoundedCornerShape(40) shape = RoundedCornerShape(40)
) { ) {
Icon(imageVector = Icons.Filled.Add, Icon(imageVector = Icons.Filled.Add,
@@ -142,23 +220,102 @@ fun GradeContent(activity: ComponentActivity, name : String){
.verticalScroll(rememberScrollState())) .verticalScroll(rememberScrollState()))
{ {
grades.forEach{ gradeData -> grades.forEach{ gradeData ->
GradePrefab(onClose = { grades = grades.filter { it != gradeData } }, color = gradeData.color) GradePrefab(
id = gradeData.id,
onClose = {
grades = grades.filter { it.id != gradeData.id }
saveGradesStart(activity, grades) },
type = gradeData.type,
grade = gradeData.grade,
weight = gradeData.weight,
color = gradeData.color,
onGradeChange = { grade ->
grades.find { it.id == gradeData.id }?.grade = grade
average = calculateAverage(grades, editor)
saveGradesStart(activity, grades) },
onWeightChange = { weight ->
grades.find { it.id == gradeData.id }?.weight = weight
average = calculateAverage(grades, editor)
saveGradesStart(activity, grades) },
onTypeChange = { type ->
grades.find { it.id == gradeData.id }?.type = type
average = calculateAverage(grades, editor)
saveGradesStart(activity, grades) }
)
} }
Spacer(modifier = Modifier.height(216.dp))
} }
} }
if (showDeleteConfirmation){
AlertDialog(
onDismissRequest = { showDeleteConfirmation = false },
text = { Text(text = "Möchten Sie wirklich alle Noten löschen?", color = MaterialTheme.colorScheme.onPrimaryContainer, style = MaterialTheme.typography.labelMedium) },
confirmButton = {
Row(modifier = Modifier.fillMaxWidth()){
Text(
text = "Bestätigen",
color = MaterialTheme.colorScheme.secondary,
style = MaterialTheme.typography.labelMedium,
modifier = Modifier.clickable {
grades = emptyList()
saveGradesStart(activity, grades)
editor.putFloat(name, 0.0f).apply()
showDeleteConfirmation = false
}
)
Spacer(modifier = Modifier.weight(1f))
Text(
text = "Abbrechen",
color = MaterialTheme.colorScheme.secondary,
style = MaterialTheme.typography.labelMedium,
modifier = Modifier.clickable { showDeleteConfirmation = false }
)
}
},
containerColor = MaterialTheme.colorScheme.primaryContainer
)
}
} }
@Composable @Composable
fun GradePrefab(onClose: () -> Unit, color: Color){ fun GradePrefab(
var typeExpanded by remember { id: Int,
onClose: () -> Unit,
type: String,
grade: Int,
weight: Float,
color: Color,
onGradeChange: (grade: Int) -> Unit,
onWeightChange: (weight: Float) -> Unit,
onTypeChange: (type: String) -> Unit,
){
var editing by remember(id) {
mutableStateOf(false) mutableStateOf(false)
} }
var gradeExpanded by remember {
var typeExpanded by remember(id) {
mutableStateOf(false) mutableStateOf(false)
} }
var weightExpanded by remember { var gradeExpanded by remember(id) {
mutableStateOf(false) mutableStateOf(false)
} }
var weightExpanded by remember(id) {
mutableStateOf(false)
}
var selectedType by remember(id) {
mutableStateOf(type)
}
var selectedGrade by remember(id) {
mutableStateOf(grade)
}
var selectedWeight by remember(id) {
mutableStateOf(weight)
}
// Background // Background
Surface( Surface(
@@ -166,47 +323,90 @@ fun GradePrefab(onClose: () -> Unit, color: Color){
color = color, color = color,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(128.dp) .height(120.dp)
.padding(start = 16.dp, end = 16.dp, top = 16.dp) .padding(start = 16.dp, end = 16.dp, top = 16.dp)
) { ) {
// Content // Content
Column(modifier = Modifier.padding(16.dp)) { Column(modifier = Modifier.padding(16.dp)) {
// Top Row
Row { Row {
// Type // Type
Surface( Surface(
shape = RoundedCornerShape(10), shape = RoundedCornerShape(10),
color = color, color = color,
border = BorderStroke(2.dp, TextOnColouredButton), border = if(editing) BorderStroke(2.dp, TextOnColouredButton) else null,
modifier = Modifier modifier = Modifier
.size(width = 128.dp, height = 32.dp) .size(width = 170.dp, height = 32.dp)
.clickable { typeExpanded = !typeExpanded } .let { if (editing) it.clickable { typeExpanded = !typeExpanded } else it }
) { ) {
Row(verticalAlignment = CenterVertically, Row(verticalAlignment = CenterVertically,
modifier = Modifier.padding(4.dp)) { modifier = Modifier.padding(4.dp)) {
Text(text = "Typ", color = TextOnColouredButton, style = MaterialTheme.typography.labelMedium) Text(text = selectedType, color = TextOnColouredButton, style = MaterialTheme.typography.labelMedium)
DropdownMenu(expanded = typeExpanded, onDismissRequest = { typeExpanded = false }) {
DropdownMenuItem(text = { Text(text = "Test") }, onClick = { /*TODO*/ }) if(editing) {
DropdownMenu(
expanded = typeExpanded,
onDismissRequest = { typeExpanded = false }
){
typesSelection.forEach { type ->
Item(
name = type,
onItemClick = {
selectedType = type
typeExpanded = !typeExpanded
onTypeChange(selectedType)
}
)
}
}
Spacer(modifier = Modifier.weight(1f))
Icon(
imageVector = if (typeExpanded) Icons.Filled.KeyboardArrowUp else Icons.Filled.KeyboardArrowDown,
contentDescription = null,
tint = TextOnColouredButton
)
} }
Spacer(modifier = Modifier.weight(1f))
Icon(imageVector = if (typeExpanded) Icons.Filled.KeyboardArrowUp else Icons.Filled.KeyboardArrowDown,
contentDescription = null,
tint = TextOnColouredButton)
} }
} }
// Delete
// Buttons
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
Icon( if (editing) {
imageVector = Icons.Filled.Close, // Delete
contentDescription = null, Icon(
tint = TextOnColouredButton, imageVector = Icons.Outlined.Delete,
modifier = Modifier contentDescription = null,
.size(28.dp) tint = TextOnColouredButton,
.clickable { onClose() } modifier = Modifier
) .size(28.dp)
.clickable { onClose() }
)
Spacer(modifier = Modifier.width(4.dp))
// Confirm
Icon(
imageVector = Icons.Filled.Check,
contentDescription = null,
tint = TextOnColouredButton,
modifier = Modifier
.size(28.dp)
.clickable { editing = !editing }
)
}
else{
// Edit
Icon(
imageVector = Icons.Outlined.Edit,
contentDescription = null,
tint = TextOnColouredButton,
modifier = Modifier
.size(28.dp)
.clickable { editing = !editing }
)
}
} }
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
// Bottom Row
Row (verticalAlignment = CenterVertically) { Row (verticalAlignment = CenterVertically) {
// Grade // Grade
Text(text = "Note:", color = TextOnColouredButton, style = MaterialTheme.typography.labelMedium) Text(text = "Note:", color = TextOnColouredButton, style = MaterialTheme.typography.labelMedium)
@@ -214,41 +414,94 @@ fun GradePrefab(onClose: () -> Unit, color: Color){
Surface( Surface(
shape = RoundedCornerShape(10), shape = RoundedCornerShape(10),
color = color, color = color,
border = BorderStroke(2.dp, TextOnColouredButton), border = if(editing) BorderStroke(2.dp, TextOnColouredButton) else null,
modifier = Modifier modifier = Modifier
.size(width = 48.dp, height = 32.dp) .size(width = 48.dp, height = 32.dp)
.clickable { gradeExpanded = !gradeExpanded } .let { if (editing) it.clickable { gradeExpanded = !gradeExpanded } else it }
) { ) {
Row(verticalAlignment = CenterVertically, Row(verticalAlignment = CenterVertically,
modifier = Modifier.padding(4.dp)) { modifier = Modifier.padding(4.dp)) {
Text(text = "1", color = TextOnColouredButton, style = MaterialTheme.typography.labelMedium) Text(text = selectedGrade.toString(), color = TextOnColouredButton, style = MaterialTheme.typography.labelMedium)
DropdownMenu(expanded = gradeExpanded, onDismissRequest = { gradeExpanded = false }) {
DropdownMenuItem(text = { Text(text = "Test") }, onClick = { /*TODO*/ }) if(editing) {
DropdownMenu(
expanded = gradeExpanded,
onDismissRequest = { gradeExpanded = false }) {
gradesSelection.forEach { grade ->
Item(
name = grade.toString(),
onItemClick = {
selectedGrade = grade
gradeExpanded = !gradeExpanded
onGradeChange(selectedGrade)
}
)
}
}
Spacer(modifier = Modifier.weight(1f))
Icon(
imageVector = if (gradeExpanded) Icons.Filled.KeyboardArrowUp else Icons.Filled.KeyboardArrowDown,
contentDescription = null,
tint = TextOnColouredButton
)
} }
Spacer(modifier = Modifier.weight(1f))
Icon(imageVector = if (gradeExpanded) Icons.Filled.KeyboardArrowUp else Icons.Filled.KeyboardArrowDown, contentDescription = null, tint = TextOnColouredButton)
} }
} }
// Weight // Weight
Spacer(modifier = Modifier.width(16.dp)) if (selectedType != "Schulaufgabe") {
Text(text = "Gewichtung:", color = TextOnColouredButton, style = MaterialTheme.typography.labelMedium) Spacer(modifier = Modifier.width(8.dp))
Spacer(modifier = Modifier.width(8.dp)) Text(
Surface( text = "Gewichtung:",
shape = RoundedCornerShape(10), color = TextOnColouredButton,
color = color, style = MaterialTheme.typography.labelMedium
border = BorderStroke(2.dp, TextOnColouredButton), )
modifier = Modifier Spacer(modifier = Modifier.width(8.dp))
.size(width = 64.dp, height = 32.dp) Surface(
.clickable { weightExpanded = !weightExpanded } shape = RoundedCornerShape(10),
) { color = color,
Row(verticalAlignment = CenterVertically, border = if (editing) BorderStroke(2.dp, TextOnColouredButton) else null,
modifier = Modifier.padding(4.dp)) { modifier = Modifier
Text(text = "1x", color = TextOnColouredButton, style = MaterialTheme.typography.labelMedium) .size(width = 80.dp, height = 32.dp)
DropdownMenu(expanded = weightExpanded, onDismissRequest = { weightExpanded = false }) { .let {
DropdownMenuItem(text = { Text(text = "Test") }, onClick = { /*TODO*/ }) if (editing) it.clickable {
weightExpanded = !weightExpanded
} else it
}
) {
Row(
verticalAlignment = CenterVertically,
modifier = Modifier.padding(4.dp)
) {
Text(
text = "$selectedWeight" + "x",
color = TextOnColouredButton,
style = MaterialTheme.typography.labelMedium
)
if (editing) {
DropdownMenu(
expanded = weightExpanded,
onDismissRequest = { weightExpanded = false }) {
weightsSelection.forEach { weight ->
Item(
name = weight.toString(),
onItemClick = {
selectedWeight = weight
weightExpanded = !weightExpanded
onWeightChange(selectedWeight)
}
)
}
}
Spacer(modifier = Modifier.weight(1f))
Icon(
imageVector = if (weightExpanded) Icons.Filled.KeyboardArrowUp else Icons.Filled.KeyboardArrowDown,
contentDescription = null,
tint = TextOnColouredButton
)
}
} }
Spacer(modifier = Modifier.weight(1f))
Icon(imageVector = if (weightExpanded) Icons.Filled.KeyboardArrowUp else Icons.Filled.KeyboardArrowDown, contentDescription = null, tint = TextOnColouredButton)
} }
} }
} }
@@ -256,5 +509,57 @@ fun GradePrefab(onClose: () -> Unit, color: Color){
} }
} }
@Composable
fun Item(name : String, onItemClick: (String) -> Unit){
DropdownMenuItem(
text = { Text(text = name, style = MaterialTheme.typography.labelMedium) },
onClick = { onItemClick(name) }
)
}
data class GradeData(val id: Int, val color: Color) data class GradeData(
val id: Int,
val color: Color,
var grade: Int,
var weight: Float,
var type: String
)
fun calculateAverage(grades: List<GradeData>, editor: Editor): Float {
if (grades.isEmpty()) return 0.0f
var totalGrade = 0.0f
var totalWeights = 0.0f
var saTotalGrade = 0.0f
var saTotalWeights = 0.0f
grades.forEach { gradeData ->
if (gradeData.type != "Typ")
if (gradeData.type == "Schulaufgabe" && SAWeight != 0) {
saTotalGrade += gradeData.grade
saTotalWeights += 1
} else {
totalGrade += gradeData.grade * gradeData.weight
totalWeights += gradeData.weight
}
}
val smallAverage = totalGrade / totalWeights
val saAverage = saTotalGrade / saTotalWeights
val finalGrade = if (smallAverage.isNaN()) saAverage
else if (saAverage.isNaN()) smallAverage
else (smallAverage + saAverage * SAWeight) / (1 + SAWeight)
editor.putFloat(name, finalGrade).apply()
return finalGrade
}
fun saveGradesStart(context: Context, grades: List<GradeData>){
CoroutineScope(Dispatchers.IO).launch {
saveGrades(context, grades, name)
}
}

View File

@@ -0,0 +1,31 @@
package com.schoolapp.cleverclass
import android.content.Context
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStore
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
object GradeStoreManager {
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "grades_preferences")
suspend fun saveGrades(context: Context, grades: List<GradeData>, subject: String) {
context.dataStore.edit { preferences ->
val json = Gson().toJson(grades)
preferences[stringPreferencesKey(subject)] = json
}
}
fun getGrades(context: Context, subject: String): Flow<List<GradeData>> {
return context.dataStore.data.map { preferences ->
val json = preferences[stringPreferencesKey(subject)] ?: return@map emptyList()
val type = object : TypeToken<List<GradeData>>() {}.type
Gson().fromJson(json, type)
}
}
}

View File

@@ -0,0 +1,31 @@
package com.schoolapp.cleverclass
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
object LessonStoreManager {
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "lesson_preferences")
suspend fun saveLessons(context: Context, lessons: List<LessonData>, day: String) {
context.dataStore.edit { preferences ->
val json = Gson().toJson(lessons)
preferences[stringPreferencesKey(day)] = json
}
}
fun getLessons(context: Context, day: String): Flow<List<LessonData>> {
return context.dataStore.data.map { preferences ->
val json = preferences[stringPreferencesKey(day)] ?: return@map emptyList()
val type = object : TypeToken<List<LessonData>>() {}.type
Gson().fromJson(json, type)
}
}
}

View File

@@ -3,6 +3,7 @@ package com.schoolapp.cleverclass
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
@@ -42,7 +43,6 @@ import com.schoolapp.cleverclass.ui.theme.TextOnColouredButton
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
readData(applicationContext)
setContent { setContent {
CleverClassTheme { CleverClassTheme {
Surface(modifier = Modifier.fillMaxSize(), Surface(modifier = Modifier.fillMaxSize(),
@@ -67,7 +67,7 @@ fun MainContent(activity: ComponentActivity){
title = { title = {
Text(text = "CleverClass", Text(text = "CleverClass",
style = MaterialTheme.typography.headlineMedium, style = MaterialTheme.typography.headlineMedium,
color = MaterialTheme.colorScheme.onPrimary) color = MaterialTheme.colorScheme.onPrimaryContainer)
}, },
actions = { actions = {
IconButton( IconButton(
@@ -75,7 +75,8 @@ fun MainContent(activity: ComponentActivity){
Icon( Icon(
imageVector = Icons.Filled.Settings, imageVector = Icons.Filled.Settings,
contentDescription = null, contentDescription = null,
modifier = Modifier.size(28.dp)) modifier = Modifier.size(28.dp),
tint = MaterialTheme.colorScheme.onPrimaryContainer)
} }
}, },
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
@@ -85,8 +86,15 @@ fun MainContent(activity: ComponentActivity){
MainButton(onClick = { switchToActivity(activity, StundenplanActivity::class.java) }, color = Color(0xFFFF4081), text = "Stundenplan", sharedPreferences) MainButton(onClick = { switchToActivity(activity, StundenplanActivity::class.java) }, color = Color(0xFFFF4081), text = "Stundenplan", sharedPreferences)
MainButton(onClick = { switchToActivity(activity, NotenActivity::class.java) }, color = Color(0xFFE040FB), text = "Noten", sharedPreferences) MainButton(onClick = { switchToActivity(activity, NotenActivity::class.java) }, color = Color(0xFFE040FB), text = "Noten", sharedPreferences)
MainButton(onClick = { switchToActivity(activity, PSEActivity::class.java) }, color = Color(0xFF536DFE), text = "Periodensystem", sharedPreferences) MainButton(onClick = { switchToActivity(activity, PSEActivity::class.java) }, color = Color(0xFF536DFE), text = "Periodensystem", sharedPreferences)
MainButton(onClick = { /*TODO: Mebis activity*/ }, color = Color(0xFF7C4DFF), text = "Mebis", sharedPreferences) MainButton(onClick = { switchToActivity(activity, DSBActivity::class.java) }, color = Color(0xFFFF6E40), text = "DSBmobile", sharedPreferences)
MainButton(onClick = { /*TODO: DSBmobile activity*/ }, color = Color(0xFFFF6E40), text = "DSBmobile", sharedPreferences) MainButton(
onClick = {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://mebis.bycs.de/"))
activity.startActivity(intent)
},
color = Color(0xFF7C4DFF),
text = "Mebis", sharedPreferences
)
} }
} }
} }

View File

@@ -1,9 +1,13 @@
package com.schoolapp.cleverclass package com.schoolapp.cleverclass
import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences
import android.content.SharedPreferences.Editor
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
@@ -12,27 +16,79 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.BottomAppBar
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.schoolapp.cleverclass.ui.theme.CleverClassTheme import com.schoolapp.cleverclass.ui.theme.CleverClassTheme
import com.schoolapp.cleverclass.ui.theme.TextOnColouredButton import com.schoolapp.cleverclass.ui.theme.TextOnColouredButton
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
private val subjects = listOf(
"Mathe", "Deutsch", "Englisch", "Französisch", "Latein",
"Physik", "Chemie", "Biologie", "Informatik",
"Geographie", "Wirtschaft", "Geschichte", "Politik und Gesellschaft",
"Ethik", "Evangelisch", "Katholisch",
"Kunst", "Musik", "Sport"
)
private val saWeights = listOf(
2, 2, 2, 2, 2,
1, 1, 0, 0,
0, 0, 0, 0,
0, 0, 0,
0, 0, 0
)
private val colorsA = listOf(
Color(0xFF536DFE),
Color(0xFF18FFFF),
Color(0xFF448AFF),
Color(0xFF64FFDA),
Color(0xFF40C4FF)
)
private val colorsB = listOf(
Color(0xFFFFFF00),
Color(0xFFFFAB40),
Color(0xFFFFD740),
Color(0xFFEEFF41)
)
private val colorsC = listOf(
Color(0xFFFF5252),
Color(0xFFFF4081),
Color(0xFFFF6052),
Color(0xFFFF6E40)
)
class NotenActivity : ComponentActivity() { class NotenActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -54,40 +110,193 @@ class NotenActivity : ComponentActivity() {
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun NotenContent(activity: ComponentActivity){ fun NotenContent(activity: ComponentActivity){
Column() { val sharedPreferences = activity.getSharedPreferences("gradeAverages", Context.MODE_PRIVATE)
TopAppBar( val editor = sharedPreferences.edit()
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(MaterialTheme.colorScheme.primaryContainer),
title = { var allAverage by remember {
Text(text = "Noten", mutableStateOf(0f)
style = MaterialTheme.typography.headlineSmall }
)},
navigationIcon = { allAverage = calculateAllAverage(sharedPreferences)
IconButton(onClick = { activity.finish() }) {
Icon( var showDeleteConfirmation by remember {
imageVector = Icons.Filled.ArrowBack, mutableStateOf(false)
contentDescription = null, }
modifier = Modifier.size(28.dp)
Scaffold(
topBar = {
TopAppBar(
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(MaterialTheme.colorScheme.primaryContainer),
title = {
Text(text = "Noten",
style = MaterialTheme.typography.headlineSmall,
color = MaterialTheme.colorScheme.onPrimaryContainer
)},
navigationIcon = {
IconButton(onClick = { activity.finish() }) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = null,
modifier = Modifier.size(28.dp),
tint = MaterialTheme.colorScheme.onPrimaryContainer
)
}
},
actions = {
IconButton(onClick = { showDeleteConfirmation = true }) {
Icon(
imageVector = Icons.Outlined.Delete,
contentDescription = null,
modifier = Modifier.size(28.dp),
tint = MaterialTheme.colorScheme.onPrimaryContainer
)
}
},
modifier = Modifier.fillMaxWidth()
)
},
bottomBar = {
BottomAppBar(
containerColor = MaterialTheme.colorScheme.primaryContainer,
modifier = Modifier
.fillMaxWidth()
.height(64.dp)
) {
Row (modifier = Modifier
.fillMaxWidth()
.align(Alignment.CenterVertically)){
Spacer(modifier = Modifier.width(16.dp))
Text(text = "Gesamtschnitt:",
style = MaterialTheme.typography.headlineSmall)
Spacer(modifier = Modifier.weight(1f))
Text(
text = "Ø" + formatFloat(allAverage),
style = MaterialTheme.typography.headlineSmall)
Spacer(modifier = Modifier.width(16.dp))
}
}
}
) {innerPadding ->
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.verticalScroll(rememberScrollState())
.padding(innerPadding)
)
{
GroopTitle("Hauptfächer", true)
for (i in 0..4){
FachButton(
color = colorsA[i],
subject = subjects[i],
saWeight = saWeights[i],
activity = activity,
sharedPreferences = sharedPreferences,
onAvgChange = { allAverage = calculateAllAverage(sharedPreferences) }
)
}
GroopTitle("Naturwissenschaften", false)
for (i in 5..8){
FachButton(
color = colorsB[i - 5],
subject = subjects[i],
saWeight = saWeights[i],
activity = activity,
sharedPreferences = sharedPreferences,
onAvgChange = { allAverage = calculateAllAverage(sharedPreferences) }
)
}
GroopTitle("Gesellschaftswissenschaften", false)
for (i in 9..12){
FachButton(
color = colorsC[i - 9],
subject = subjects[i],
saWeight = saWeights[i],
activity = activity,
sharedPreferences = sharedPreferences,
onAvgChange = { allAverage = calculateAllAverage(sharedPreferences) }
)
}
GroopTitle("Religionen", false)
for (i in 13..15){
FachButton(
color = colorsA[i - 13],
subject = subjects[i],
saWeight = saWeights[i],
activity = activity,
sharedPreferences = sharedPreferences,
onAvgChange = { allAverage = calculateAllAverage(sharedPreferences) }
)
}
GroopTitle("Sonstiges", false)
for (i in 16..18){
FachButton(
color = colorsB[i - 16],
subject = subjects[i],
saWeight = saWeights[i],
activity = activity,
sharedPreferences = sharedPreferences,
onAvgChange = { allAverage = calculateAllAverage(sharedPreferences) }
)
}
Spacer(modifier = Modifier.height(16.dp))
}
}
if (showDeleteConfirmation){
AlertDialog(
onDismissRequest = { showDeleteConfirmation = false },
text = { Text(text = "Möchten Sie wirklich alle Noten aus Allen Fächern löschen?", color = MaterialTheme.colorScheme.onPrimaryContainer, style = MaterialTheme.typography.labelMedium) },
confirmButton = {
Row(modifier = Modifier.fillMaxWidth()){
Text(
text = "Bestätigen",
color = MaterialTheme.colorScheme.secondary,
style = MaterialTheme.typography.labelMedium,
modifier = Modifier.clickable {
deleteAll(editor, activity)
showDeleteConfirmation = false
}
)
Spacer(modifier = Modifier.weight(1f))
Text(
text = "Abbrechen",
color = MaterialTheme.colorScheme.secondary,
style = MaterialTheme.typography.labelMedium,
modifier = Modifier.clickable { showDeleteConfirmation = false }
) )
} }
}, },
modifier = Modifier.fillMaxWidth() containerColor = MaterialTheme.colorScheme.primaryContainer
) )
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
FachButton(color = Color(0xFF69F0AE), text = "Deutsch", 3.66f, activity)
FachButton(color = Color(0xFFEEFF41), text = "Mathe", 1.56f, activity)
FachButton(color = Color(0xFFFFAB40), text = "Englisch", 2.27f, activity)
FachButton(color = Color(0xFF18FFFF), text = "Latein", 4.75f, activity)
FachButton(color = Color(0xFFB2FF59), text = "Französisch", 2.33f, activity)
}
} }
} }
@Composable @Composable
fun FachButton(color : Color, text : String, average : Float, activity : ComponentActivity){ fun FachButton(
color : Color,
subject : String,
saWeight: Int,
activity: ComponentActivity,
sharedPreferences: SharedPreferences,
onAvgChange: () -> Unit){
var avg by remember {
mutableStateOf(sharedPreferences.getFloat(subject, 0.0f))
}
Button( Button(
onClick = { onClick = {
val intent = Intent(activity, FachActivity::class.java).apply { putExtra(FachActivity.ARGUMENT_KEY, text) } val intent = Intent(activity, FachActivity::class.java).apply {
putExtra(FachActivity.NAME_KEY, subject)
putExtra(FachActivity.WEIGHT_KEY, saWeight)
}
activity.startActivity(intent) }, activity.startActivity(intent) },
shape = RoundedCornerShape(40), shape = RoundedCornerShape(40),
colors = ButtonDefaults.outlinedButtonColors(containerColor = color), colors = ButtonDefaults.outlinedButtonColors(containerColor = color),
@@ -97,13 +306,76 @@ fun FachButton(color : Color, text : String, average : Float, activity : Compone
.padding(start = 16.dp, end = 16.dp, top = 16.dp) .padding(start = 16.dp, end = 16.dp, top = 16.dp)
) { ) {
Row(modifier = Modifier.fillMaxWidth()) { Row(modifier = Modifier.fillMaxWidth()) {
Text(text = text, Text(text = subject,
color = TextOnColouredButton, color = TextOnColouredButton,
style = MaterialTheme.typography.labelMedium) style = MaterialTheme.typography.labelMedium)
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
Text(text = "Ø$average", Text(text = if (avg != 0.0f) "Ø${formatFloat(avg)}" else "---",
color = TextOnColouredButton, color = TextOnColouredButton,
style = MaterialTheme.typography.labelMedium) style = MaterialTheme.typography.labelMedium)
} }
} }
}
// changes state when Shared Preference is updated
val observer = remember {
SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
if (key == subject) {
avg = sharedPreferences.getFloat(subject, 0.0f)
onAvgChange()
}
}
}
// handles observer disposal
DisposableEffect(Unit) {
sharedPreferences.registerOnSharedPreferenceChangeListener(observer)
onDispose {
sharedPreferences.unregisterOnSharedPreferenceChangeListener(observer)
}
}
}
@Composable
fun GroopTitle(name: String, top: Boolean){
if(!top) {
Spacer(modifier = Modifier.height(32.dp))
Divider()
}
Spacer(modifier = Modifier.height(32.dp))
Text(text = name, style = MaterialTheme.typography.headlineSmall, color = MaterialTheme.colorScheme.onBackground)
Spacer(modifier = Modifier.height(12.dp))
}
fun calculateAllAverage(sharedPreferences: SharedPreferences): Float{
var total = 0.0f
var number = 0
val avgs = loadAllAverages(sharedPreferences)
avgs.forEach{ avg ->
if (avg != 0.0f && !avg.isNaN()){
total += avg
number += 1
}
}
return (total / number)
}
fun loadAllAverages(sharedPreferences: SharedPreferences): List<Float>{
val avgs = emptyList<Float>().toMutableList()
subjects.forEach { name ->
avgs += sharedPreferences.getFloat(name, 0.0f)
}
return avgs
}
fun deleteAll(editor: Editor, context: Context){
subjects.forEach{name ->
editor.putFloat(name, 0.0f).apply()
CoroutineScope(Dispatchers.IO).launch {
GradeStoreManager.saveGrades(context, emptyList(), name)
}
}
}

View File

@@ -1,36 +1,60 @@
package com.schoolapp.cleverclass package com.schoolapp.cleverclass
import android.app.AlertDialog
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.schoolapp.cleverclass.ui.theme.CleverClassTheme import com.schoolapp.cleverclass.ui.theme.CleverClassTheme
import org.json.JSONObject
import java.io.InputStreamReader
class PSEActivity : ComponentActivity() { class PSEActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContent { setContent {
CleverClassTheme { CleverClassTheme {
Surface(modifier = Modifier.fillMaxSize(), Surface(
color = MaterialTheme.colorScheme.background) { modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background)
{
PSEContent(activity = this) PSEContent(activity = this)
} }
} }
@@ -38,17 +62,78 @@ class PSEActivity : ComponentActivity() {
} }
} }
@Composable
fun PSEContent(activity: ComponentActivity){
var loading by remember {
mutableStateOf(true)
}
LaunchedEffect(key1 = true) {
loading = false
}
if (loading) {
StaticLoadingScreen()
} else {
PSEMainContent(activity)
}
}
private lateinit var rootObj : JSONObject
private val colors = listOf(
Color(0xFFBCA76D),
Color(0xFFC65253),
Color(0xFF337733),
Color(0xFFDC2222),
Color(0xFF129DC3),
Color(0xFFDDCC69),
Color(0xFF7044DD)
)
private val shells = listOf(
"K", "L", "M", "N", "O", "P", "Q"
)
private val elementNameList = listOf(
"H", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "He",
"Li", "Be", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "B", "C", "N", "O", "F", "Ne",
"Na", "Mg", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "Al", "Si", "P", "S", "Cl", "Ar",
"K", "Ca", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "Sc", "Ti", "V", "Cr", "Mn", "Fe", "Co", "Ni", "Cu", "Zn", "Ga", "Ge", "As", "Se", "Br", "Kr",
"Rb", "Sr", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "Y", "Zr", "Nb", "Mo", "Tc", "Ru", "Rh", "Pd", "Ag", "Cd", "In", "Sn", "Sb", "Te", "I", "Xe",
"Cs", "Ba", "La", "Ce", "Pr", "Nd", "Pm", "Sm", "Eu", "Gd", "Tb", "Dy", "Ho", "Er", "Tm", "Yb", "Lu", "Hf", "Ta", "W", "Re", "Os", "Ir", "Pt", "Au", "Hg", "Tl", "Pb", "Bi", "Po", "At", "Rn",
"Fr", "Ra", "Ac", "Th", "Pa", "U", "Np", "Pu", "Am", "Cm", "Bk", "Cf", "Es", "Fm", "Md", "No", "Lr", "Rf", "Db", "Sg", "Bh", "Hs", "Mt", "Ds", "Rg", "Cn", "Nh", "Fl", "Mc", "Lv", "Ts", "Og"
)
private val elementButtonColors = listOf(
0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0,
1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1,
2, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 2, 2, 2, 2, 2,
3, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3,
4, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4,
5, 5, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5,
6, 6, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6
)
// Content of PSE // Content of PSE
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun PSEContent(activity: ComponentActivity){ fun PSEMainContent(activity: ComponentActivity) {
Column() { val inputStream = activity.assets.open("elements_data.json")
rootObj = JSONObject(InputStreamReader(inputStream).readText())
var showInfo by remember {
mutableStateOf(false)
}
var dialogChooser by remember {
mutableStateOf(-1)
}
Column {
TopAppBar( TopAppBar(
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(MaterialTheme.colorScheme.primaryContainer), colors = TopAppBarDefaults.centerAlignedTopAppBarColors(MaterialTheme.colorScheme.primaryContainer),
title = { title = {
Text( Text(
text = "Periodensystem", text = "Periodensystem der Elemente",
style = MaterialTheme.typography.headlineSmall style = MaterialTheme.typography.headlineSmall,
color = MaterialTheme.colorScheme.onPrimaryContainer
) )
}, },
navigationIcon = { navigationIcon = {
@@ -56,26 +141,173 @@ fun PSEContent(activity: ComponentActivity){
Icon( Icon(
imageVector = Icons.Filled.ArrowBack, imageVector = Icons.Filled.ArrowBack,
contentDescription = null, contentDescription = null,
modifier = Modifier.size(28.dp) modifier = Modifier.size(28.dp),
tint = MaterialTheme.colorScheme.onPrimaryContainer
)
}
},
actions = {
IconButton(onClick = {
showInfo = true
}) {
Icon(
imageVector = Icons.Outlined.Info,
contentDescription = null,
modifier = Modifier.size(28.dp),
tint = MaterialTheme.colorScheme.onPrimaryContainer
) )
} }
}, },
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
Column() { Column(
//region //Element dialog definitions verticalArrangement = Arrangement.Center,
val HBuilder = AlertDialog.Builder(activity) modifier = Modifier
HBuilder.setIcon(null) .fillMaxHeight()
HBuilder.setTitle(elements[0].data0) .padding(3.dp)
HBuilder.setMessage(elements[0].data0) .verticalScroll(rememberScrollState())
HBuilder.setPositiveButton(R.string.dialog_schließen, null) .horizontalScroll(rememberScrollState())
//endregion ) {
var dialogCounter = 0
TextButton( for (row in 0..6) {
onClick = { HBuilder.show() }) { Row {
Text(text = "H") InformationBox(text = (row + 1).toString(), color = MaterialTheme.colorScheme.onBackground)
for (element in 0..31) {
val index = row * 32 + element
if (elementNameList[index] != "") {
val elementJsonIndex = dialogCounter
++dialogCounter
ElementBox(arrayIndex = index, onClick = { dialogChooser = elementJsonIndex })
} else {
Box(
modifier = Modifier
.padding(3.dp)
.size(60.dp)
)
}
}
InformationBox(text = shells[row], color = colors[row])
}
} }
} }
} }
if (dialogChooser != -1) {
val dcValue = dialogChooser
AlertDialog(
onDismissRequest = { dialogChooser = -1 },
text = {
Text(
modifier = Modifier
.verticalScroll(rememberScrollState())
.horizontalScroll(rememberScrollState()),
text = readElementData(dcValue),
color = MaterialTheme.colorScheme.onPrimaryContainer,
style = MaterialTheme.typography.labelMedium
)
},
confirmButton = {
Text(
text = "Schließen",
color = MaterialTheme.colorScheme.secondary,
style = MaterialTheme.typography.labelMedium,
modifier = Modifier.clickable {
dialogChooser = -1
}
)
},
containerColor = MaterialTheme.colorScheme.primaryContainer,
modifier = Modifier
.padding(5.dp)
.wrapContentWidth(unbounded = true)
)
}
if (showInfo) {
AlertDialog(
onDismissRequest = { showInfo = false },
text = {
Text(
text = "Auf ein Element tippen, um genauere Informationen zu erhalten.\n\n" +
"Verwendung im Querformat empfohlen",
color = MaterialTheme.colorScheme.onPrimaryContainer,
style = MaterialTheme.typography.labelMedium
)
},
confirmButton = {
Text(
text = "Schließen",
color = MaterialTheme.colorScheme.secondary,
style = MaterialTheme.typography.labelMedium,
modifier = Modifier.clickable {
showInfo = false
}
)
},
containerColor = MaterialTheme.colorScheme.primaryContainer,
modifier = Modifier
.padding(5.dp)
)
}
}
@Composable
fun ElementBox(arrayIndex: Int, onClick: () -> Unit){
Box(modifier = Modifier
.padding(3.dp)
.size(60.dp)
.background(
color = colors[elementButtonColors[arrayIndex]],
shape = RoundedCornerShape(5)
)
.clickable { onClick() }
) {
Text(
modifier = Modifier.align(Alignment.Center),
text = elementNameList[arrayIndex],
color = com.schoolapp.cleverclass.ui.theme.TextOnColouredButton,
style = MaterialTheme.typography.labelLarge
)
}
}
@Composable
fun InformationBox(text: String, color: Color){
Box(modifier = Modifier
.padding(3.dp)
.size(60.dp)
.border(
width = 1.dp,
color = MaterialTheme.colorScheme.primaryContainer
)
) {
Text(
modifier = Modifier.align(Alignment.Center),
text = text,
color = color,
style = MaterialTheme.typography.labelLarge
)
}
}
private fun readElementData(int: Int): String {
val elementData = rootObj.getJSONArray("elements").getJSONObject(int)
return "Name:\n${elementData.get("Name")}\n\n" +
"Ordnungszahl:\n${elementData.get("Ordnungszahl")}\n\n" +
"Gruppe:\n${elementData.get("Gruppe")}\n\n" +
"Periode/Hauptquantenzahl:\n${elementData.get("Periode/Hauptquantenzahl")}\n\n" +
"Schale:\n${elementData.get("Schale")}\n\n" +
"Molare Masse:\n${elementData.get("Molare Masse")}\n\n" +
"Dichte:\n${elementData.get("Dichte")}\n\n" +
"Elektronegativität:\n${elementData.get("Elektronegativität")}\n\n" +
"Schmelztemperatur:\n${elementData.get("Schmelztemperatur")}\n\n" +
"Siedetemperatur:\n${elementData.get("Siedetemperatur")}\n\n" +
"künstlich:\n${elementData.get("künstlich")}\n\n" +
"radioaktiv:\n${elementData.get("radioaktiv")}\n\n" +
"Halbwertszeit:\n${elementData.get("Halbwertszeit")}\n\n" +
"Strahlungsart:\n${elementData.get("Strahlungsart")}"
} }

View File

@@ -1,104 +0,0 @@
package com.schoolapp.cleverclass
import android.content.Context
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
val elements = mutableListOf<PSEData>()
var i = 0
class PSEData() {
var data0: String = ""
var data1: String = ""
var data2: String = ""
var data3: String = ""
var data4: String = ""
var data5: String = ""
var data6: String = ""
var data7: String = ""
var data8: String = ""
var data9: String = ""
var data10: String = ""
var data11: String = ""
var data12: String = ""
var data13: String = ""
var data14: String = ""
var data15: String = ""
constructor(
string0: String,
string1: String,
string2: String,
string3: String,
string4: String,
string5: String,
string6: String,
string7: String,
string8: String,
string9: String,
string10: String,
string11: String,
string12: String,
string13: String,
string14: String,
string15: String
) : this() {
data0 = string0
data1 = string1
data2 = string2
data3 = string3
data4 = string4
data5 = string5
data6 = string6
data7 = string7
data8 = string8
data9 = string9
data10 = string10
data11 = string11
data12 = string12
data13 = string13
data14 = string14
data15 = string15
}
}
fun readData(context: Context) {
val elementInput = mutableListOf<String>()
val file = "elements_data.txt"
val assetManager = context.assets
val inputStream = assetManager.open(file)
try {
BufferedReader(InputStreamReader(inputStream)).use { br ->
br.lines().forEach {
elementInput.add(it)
}
}
}
catch (e: IOException) {
e.printStackTrace()
}
if (i < 118) {
while (i < 118) {
val pseobject = PSEData(
string0 = elementInput[(i * 16)],
string1 = elementInput[(i * 16) + 1],
string2 = elementInput[(i * 16) + 2],
string3 = elementInput[(i * 16) + 3],
string4 = elementInput[(i * 16) + 4],
string5 = elementInput[(i * 16) + 5],
string6 = elementInput[(i * 16) + 6],
string7 = elementInput[(i * 16) + 7],
string8 = elementInput[(i * 16) + 8],
string9 = elementInput[(i * 16) + 9],
string10 = elementInput[(i * 16) + 10],
string11 = elementInput[(i * 16) + 11],
string12 = elementInput[(i * 16) + 12],
string13 = elementInput[(i * 16) + 13],
string14 = elementInput[(i * 16) + 14],
string15 = elementInput[(i * 16) + 15]
)
elements.add(pseobject)
i++
}
}
}

View File

@@ -8,19 +8,23 @@ import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Divider import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
@@ -32,8 +36,10 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -61,6 +67,10 @@ fun SettingsContent(activity: ComponentActivity) {
val sharedPreferences = activity.getSharedPreferences("Settings", Context.MODE_PRIVATE) val sharedPreferences = activity.getSharedPreferences("Settings", Context.MODE_PRIVATE)
val editor = sharedPreferences.edit() val editor = sharedPreferences.edit()
var showDeleteConfirmation by remember {
mutableStateOf(false)
}
Column(modifier = Modifier.fillMaxSize()) { Column(modifier = Modifier.fillMaxSize()) {
// Top AppBar // Top AppBar
TopAppBar( TopAppBar(
@@ -68,7 +78,8 @@ fun SettingsContent(activity: ComponentActivity) {
title = { title = {
Text( Text(
text = "Einstellungen", text = "Einstellungen",
style = MaterialTheme.typography.headlineSmall style = MaterialTheme.typography.headlineSmall,
color = MaterialTheme.colorScheme.onPrimaryContainer
) )
}, },
navigationIcon = { navigationIcon = {
@@ -76,7 +87,8 @@ fun SettingsContent(activity: ComponentActivity) {
Icon( Icon(
imageVector = Icons.Filled.ArrowBack, imageVector = Icons.Filled.ArrowBack,
contentDescription = null, contentDescription = null,
modifier = Modifier.size(28.dp) modifier = Modifier.size(28.dp),
tint = MaterialTheme.colorScheme.onPrimaryContainer
) )
} }
}, },
@@ -89,13 +101,45 @@ fun SettingsContent(activity: ComponentActivity) {
.weight(1f) .weight(1f)
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
.fillMaxWidth() .fillMaxWidth()
) { ) {
Setting(text = "Stundenplan", sharedPreferences, editor) Setting(text = "Stundenplan", sharedPreferences, editor)
Setting(text = "Noten", sharedPreferences, editor) Setting(text = "Noten", sharedPreferences, editor)
Setting(text = "Periodensystem", sharedPreferences, editor) Setting(text = "Periodensystem", sharedPreferences, editor)
Setting(text = "Mebis", sharedPreferences, editor)
Setting(text = "DSBmobile", sharedPreferences, editor) Setting(text = "DSBmobile", sharedPreferences, editor)
Setting(text = "Mebis", sharedPreferences, editor)
Spacer(modifier = Modifier.height(16.dp))
Divider()
Box(
modifier = Modifier
.fillMaxWidth()
.clickable {
val intent = Intent(activity, DSBLoginActivity::class.java).apply {
putExtra(DSBLoginActivity.TYPE_KEY, "Settings")
}
activity.startActivity(intent)
}
) {
Text(
text = "DSBmobile Daten",
color = MaterialTheme.colorScheme.onBackground,
style = MaterialTheme.typography.labelMedium,
modifier = Modifier.padding(16.dp)
)
}
Divider()
Box(
modifier = Modifier
.fillMaxWidth()
.clickable { showDeleteConfirmation = true }
) {
Text(
text = "Alle Daten löschen",
color = MaterialTheme.colorScheme.onBackground,
style = MaterialTheme.typography.labelMedium,
modifier = Modifier.padding(16.dp)
)
}
} }
// About Button // About Button
@@ -106,20 +150,64 @@ fun SettingsContent(activity: ComponentActivity) {
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Divider() Divider()
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(8.dp))
Text( Box(
text = "About", contentAlignment = Alignment.Center,
style = MaterialTheme.typography.bodySmall, modifier = Modifier
modifier = Modifier.clickable { .wrapContentSize()
val intent = Intent(activity, AboutActivity::class.java) .clickable {
activity.startActivity(intent) val intent = Intent(activity, AboutActivity::class.java)
} activity.startActivity(intent)
) }
Spacer(modifier = Modifier.height(16.dp)) ) {
Text(
text = "About",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onBackground,
modifier = Modifier.padding(horizontal = 32.dp, vertical = 8.dp)
)
}
Spacer(modifier = Modifier.height(8.dp))
} }
} }
}
if(showDeleteConfirmation){
AlertDialog(
onDismissRequest = { showDeleteConfirmation = false },
text = {
Text(
text = "Möchten Sie wirklich alle Daten aus dieser App löschen?\n" +
"Dieser Vorgang kann nicht rückgängig gemacht werden.",
color = MaterialTheme.colorScheme.onPrimaryContainer,
style = MaterialTheme.typography.labelMedium
)
},
confirmButton = {
Row(modifier = Modifier.fillMaxWidth()){
Text(
text = "Löschen",
color = MaterialTheme.colorScheme.secondary,
style = MaterialTheme.typography.labelMedium,
modifier = Modifier.clickable {
deleteAllData(activity)
showDeleteConfirmation = false
}
)
Spacer(modifier = Modifier.weight(1f))
Text(
text = "Abbrechen",
color = MaterialTheme.colorScheme.secondary,
style = MaterialTheme.typography.labelMedium,
modifier = Modifier.clickable { showDeleteConfirmation = false }
)
}
},
containerColor = MaterialTheme.colorScheme.primaryContainer
)
}
}
@Composable @Composable
fun Setting(text : String, sharedPreferences: SharedPreferences, editor: Editor){ fun Setting(text : String, sharedPreferences: SharedPreferences, editor: Editor){
@@ -136,8 +224,10 @@ fun Setting(text : String, sharedPreferences: SharedPreferences, editor: Editor)
} }
) )
Spacer(modifier = Modifier.width(16.dp)) Spacer(modifier = Modifier.width(16.dp))
Text(text = text, Text(
color = MaterialTheme.colorScheme.onPrimaryContainer, text = text,
style = MaterialTheme.typography.labelMedium) color = MaterialTheme.colorScheme.onBackground,
style = MaterialTheme.typography.labelMedium
)
} }
} }

View File

@@ -1,14 +1,29 @@
package com.schoolapp.cleverclass package com.schoolapp.cleverclass
import android.content.Context
import android.content.Intent
import android.icu.text.DecimalFormat
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.outlined.Edit
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
@@ -18,20 +33,60 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat.startActivity
import com.schoolapp.cleverclass.LessonStoreManager.getLessons
import com.schoolapp.cleverclass.ui.theme.CleverClassTheme import com.schoolapp.cleverclass.ui.theme.CleverClassTheme
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
private val listOfDays = listOf("Mo", "Di", "Mi", "Do", "Fr")
private val listOfBreakIndexes = mutableListOf<Int>()
class StundenplanActivity : ComponentActivity() { class StundenplanActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val sharedPreferences = this.getSharedPreferences("TimeTable", Context.MODE_PRIVATE)
val setupDone = sharedPreferences.getBoolean("setupDone", false)
val lessons = MutableList(5) { listOf<LessonData>() }
//Loading saved lessons when NOT auto-loading the setup
if (setupDone) {
listOfDays.forEachIndexed { index, dayLessons ->
CoroutineScope(Dispatchers.IO).launch {
getLessons(this@StundenplanActivity, dayLessons).collect { savedLessons ->
lessons[index] = savedLessons.toMutableList()
}
}
}
}
setContent { setContent {
CleverClassTheme { CleverClassTheme {
Surface( Surface(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background color = MaterialTheme.colorScheme.background
) { ) {
StundenplanContent(activity = this) //Auto-load setup, if incomplete
if (!setupDone) {
val intent = Intent(this@StundenplanActivity, TimeTableSetupActivity::class.java).apply {
putExtra(TimeTableSetupActivity.TYPE_KEY, "TimeSetup")
}
startActivity(intent)
}
StundenplanContent(activity = this, lessons)
} }
} }
} }
@@ -41,28 +96,360 @@ class StundenplanActivity : ComponentActivity() {
// Content of Stundenplan // Content of Stundenplan
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun StundenplanContent(activity: ComponentActivity){ fun StundenplanContent(activity: ComponentActivity, loadedLessons: List<List<LessonData>>){
Column() { //Declaration of most sharedPreferences-stored data (same SharedPreferences as in TimeTableSetupActivity.kt)
val sharedPreferences = activity.getSharedPreferences("TimeTable", Context.MODE_PRIVATE)
val lessonLength = sharedPreferences.getInt("lessonLength", 0)
val firstLesson = sharedPreferences.getFloat("firstLesson", 0.0f).toString()
val breakAmnt = sharedPreferences.getInt("breakAmnt", 0)
val listOfBreakStartTimes = mutableListOf<String>()
val listOfBreakLengths = mutableListOf<Int>()
for (i in 0 until breakAmnt) {
listOfBreakStartTimes.add(sharedPreferences.getFloat("break${i}StartTime", 0.0f).toString())
listOfBreakLengths.add(sharedPreferences.getInt("break${i}Length", 0))
}
var showInfo by remember {
mutableStateOf(false)
}
Column {
TopAppBar( TopAppBar(
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(MaterialTheme.colorScheme.primaryContainer), colors = TopAppBarDefaults.centerAlignedTopAppBarColors(MaterialTheme.colorScheme.primaryContainer),
title = { title = {
Text(text = "Stundenplan", Text(text = "Stundenplan",
style = MaterialTheme.typography.headlineSmall style = MaterialTheme.typography.headlineSmall,
color = MaterialTheme.colorScheme.onPrimaryContainer
)}, )},
navigationIcon = { navigationIcon = {
IconButton(onClick = { activity.finish() }) { IconButton(onClick = { activity.finish() }) {
Icon( Icon(
imageVector = Icons.Filled.ArrowBack, imageVector = Icons.Filled.ArrowBack,
contentDescription = null, contentDescription = null,
modifier = Modifier.size(28.dp) modifier = Modifier.size(28.dp),
tint = MaterialTheme.colorScheme.onPrimaryContainer
) )
} }
}, },
actions ={
//Info button
IconButton(
onClick = { showInfo = !showInfo }) {
Icon(
imageVector = Icons.Outlined.Info,
contentDescription = null,
modifier = Modifier.size(28.dp),
tint = MaterialTheme.colorScheme.onPrimaryContainer
)
}
//Edit button
IconButton(
onClick = {
val intent = Intent(activity, TimeTableSetupActivity::class.java).apply {
putExtra(TimeTableSetupActivity.TYPE_KEY, "TimeSetup")
}
startActivity(activity, intent, null)
}) {
Icon(
imageVector = Icons.Outlined.Edit,
contentDescription = null,
modifier = Modifier.size(28.dp),
tint = MaterialTheme.colorScheme.onPrimaryContainer
)
}
},
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
Column() { //Start of the timetable --> One Row() with multiple Column() to create the table
Row(
modifier = Modifier
.verticalScroll(rememberScrollState())
.horizontalScroll(rememberScrollState())
) {
//Left-handed lesson indexing with beginning time of each lesson or break
InfoColumn(
loadedLessons = loadedLessons,
breakAmnt = breakAmnt,
breaksStartTime = listOfBreakStartTimes,
firstLesson = firstLesson,
lessonLength = lessonLength,
breaksLength = listOfBreakLengths
)
//Creation of 5 Columns
loadedLessons.forEachIndexed { index, dailyLessons ->
//Width variable for variable Box sizes
Column(modifier = Modifier.width(250.dp)) {
//Additional extraIndex to count lessons without breaks getting into the way
var extraIndex = 0
//Box with the day on top of each Column()
Box(
modifier = Modifier
.size(250.dp, 50.dp)
.padding(3.dp)
.align(Alignment.CenterHorizontally)
.border(
width = 1.dp,
color = MaterialTheme.colorScheme.primaryContainer
)
) {
Text(
text = listOfDays[index],
modifier = Modifier.align(Alignment.Center),
style = MaterialTheme.typography.labelMedium
)
}
//Core timetable
//One Box for each lesson or break
for (i in 0 until (dailyLessons.size + breakAmnt)) {
//If the current index is saved as a break in listOfBreakIndexes --> Spacer Box
if (listOfBreakIndexes.indexOf(i) != -1) {
Box(
modifier = Modifier
.size(250.dp, 100.dp)
)
}
//Else creation of a Box containing all the information from the LessonData object
else if (dailyLessons != emptyList<LessonData>() && dailyLessons.size != extraIndex) {
LessonBox(subject = dailyLessons[extraIndex].subject, teacher = dailyLessons[extraIndex].teacher, room = dailyLessons[extraIndex].room)
//ExtraIndex counted up only here as it does only index the lessons and NOT the breaks
extraIndex++
}
}
}
}
//Right-handed lesson indexing with beginning time of each lesson or break
InfoColumn(
loadedLessons = loadedLessons,
breakAmnt = breakAmnt,
breaksStartTime = listOfBreakStartTimes,
firstLesson = firstLesson,
lessonLength = lessonLength,
breaksLength = listOfBreakLengths
)
} }
} }
if (showInfo) {
AlertDialog(
onDismissRequest = { showInfo = false },
text = {
Text(
text = "Die Breite der Spalten ist auf eine bestimmte Größe festgelegt.\n\n" +
"Zu lange Eingaben können horizontal gescrollt werden.",
color = MaterialTheme.colorScheme.onPrimaryContainer,
style = MaterialTheme.typography.labelMedium)
},
confirmButton = {
Text(
text = "Schließen",
color = MaterialTheme.colorScheme.secondary,
style = MaterialTheme.typography.labelMedium,
modifier = Modifier.clickable { showInfo = false }
)
},
containerColor = MaterialTheme.colorScheme.primaryContainer,
modifier = Modifier
.padding(5.dp)
)
}
}
//Data class to save objects via DataShare containing information about lessons
data class LessonData(
var subject: String,
var teacher: String,
var room: String
)
//Implementation of Box with a darker border than the other Boxes
// contains the information from the corresponding LessonData object
@Composable
fun LessonBox(subject: String, teacher: String, room: String) {
Box(
modifier = Modifier
.size(250.dp, 100.dp)
.padding(3.dp)
.border(
width = 1.dp,
color = MaterialTheme.colorScheme.onPrimaryContainer
),
contentAlignment = Alignment.Center
) {
Column {
if (subject != "") {
Text(
modifier = Modifier
.horizontalScroll(rememberScrollState())
.align(Alignment.CenterHorizontally)
.padding(horizontal = 5.dp),
text = subject,
style = MaterialTheme.typography.labelMedium
)
}
if (room != "") {
Text(
modifier = Modifier
.horizontalScroll(rememberScrollState())
.align(Alignment.CenterHorizontally)
.padding(horizontal = 5.dp),
text = room,
style = MaterialTheme.typography.labelMedium
)
}
if (teacher != "") {
Text(
modifier = Modifier
.horizontalScroll(rememberScrollState())
.align(Alignment.CenterHorizontally)
.padding(horizontal = 5.dp),
text = teacher,
style = MaterialTheme.typography.labelMedium
)
}
}
}
}
//Left-/Right-Handed info Column
@Composable
fun InfoColumn(loadedLessons: List<List<LessonData>>, breakAmnt: Int, breaksStartTime: List<String>, firstLesson: String, lessonLength: Int, breaksLength: List<Int>) {
Column {
//Determining how much lessons the longest day of the week has
var maxLessons = 0
for (dailyLessons in loadedLessons) {
if (dailyLessons.size > maxLessons) {
maxLessons = dailyLessons.size
}
}
//Variable for time management with custom break times
var lessonTimeIncrement = 0
//Variable for decreasing the number in the InfoColumn after a "Pause" String
var lessonNegIncrement = 0
//Additional extraIndex to count lessons without breaks getting into the way
var extraIndex = 0
//Clear the list of brake indexes needed earlier in the timetable core
listOfBreakIndexes.drop(listOfBreakIndexes.size)
//Spacer Box
Box(
modifier = Modifier
.size(100.dp, 50.dp)
.padding(3.dp)
)
//Loop for creating Boxes on the side
for (i in 0 until (maxLessons + breakAmnt)) {
//Boolean to avoid double creation of the same Box
var loopDone = false
//Loop through all saved break start time
for (breakTime in breaksStartTime) {
if (!loopDone) {
//Variable = minutes of the currently looked at break
var formattedBreakSubstring = breakTime.substringAfter('.')
//When the Float wos like XX.n, it the n-Part will be n0 (fix for values 10, 20, etc.)
if (formattedBreakSubstring == "1" || formattedBreakSubstring == "2" || formattedBreakSubstring == "3" || formattedBreakSubstring == "4" || formattedBreakSubstring == "5") {
formattedBreakSubstring += "0"
}
//Format start time of the break the same way as the lesson start time is formatted
val formattedBreakTime = "@" + DecimalFormat("00").format(breakTime.substringBefore('.').toInt()) + ":" + DecimalFormat("00").format(formattedBreakSubstring.toInt())
//Check whether the start time of the currently looked at brake and lesson are the sa,e
if (substringHelper(extraIndex, firstLesson, lessonLength, lessonTimeIncrement) == formattedBreakTime) {
Box(
modifier = Modifier
.size(100.dp)
.padding(3.dp)
.border(
width = 1.dp,
color = MaterialTheme.colorScheme.primaryContainer
)
) {
//"Pause" String instead of indexing
Text(
modifier = Modifier
.align(Alignment.Center)
.scale(1.1f),
text = "Pause",
textDecoration = TextDecoration.Underline,
style = MaterialTheme.typography.labelLarge
)
//String with the starting time of the current break
Text(
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(3.dp)
.scale(0.8f),
text = formattedBreakTime,
style = MaterialTheme.typography.labelMedium
)
//Add length of current break to the increment
lessonTimeIncrement += breaksLength[breaksStartTime.indexOf(breakTime)]
//Add info that one moe brake exists to the indexing increment
lessonNegIncrement++
//Add the extraIndex (lessonIndex) into the listOfBreakIndexes list to include breaks in timetable core
listOfBreakIndexes.add(i)
//Prevent double creation of Boxes
loopDone = true
}
}
//When looped through all breaks, but none fitted
else if (breakTime == breaksStartTime.last()) {
Box(
modifier = Modifier
.size(100.dp)
.padding(3.dp)
.border(
width = 1.dp,
color = MaterialTheme.colorScheme.primaryContainer
)
) {
//String with the indexing of the lessons
Text(
modifier = Modifier
.align(Alignment.Center)
.scale(1.1f),
text = "${i + 1 - lessonNegIncrement}:",
textDecoration = TextDecoration.Underline,
style = MaterialTheme.typography.labelLarge
)
//String with the starting time of the lesson
Text(
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(3.dp)
.scale(0.8f),
text = substringHelper(extraIndex, firstLesson, lessonLength, lessonTimeIncrement),
style = MaterialTheme.typography.labelMedium
)
//Prevent double creation of Boxes
loopDone = true
//Counting up the lesson only index
extraIndex++
}
}
}
}
}
}
}
//Function that moves the formatting and calculation of the lesson start times away from in-line
fun substringHelper (int: Int, eductString: String, lessonLength: Int, increment: Int): String {
//Variable = minutes of the entered Time
var subString = eductString.substringAfter('.')
//Fix of 10, 20, etc.
if (subString == "1" || subString == "2" || subString == "3" || subString == "4" || subString == "5") {
subString += "0"
}
//Calculation based on start time of the first hour, lesson length, indexing of the current lesson (extraIndex!) and the increment calculated from the break times
//Formatting to "@hh:mm"
val productString = "@" + DecimalFormat("00").format((((subString.toInt() + int * lessonLength) + increment) / 60) + eductString.substringBefore('.').toInt()) + ":" + DecimalFormat("00").format((((subString.toInt()) + int * lessonLength) + increment) % 60)
//Return statement to use the formatted and calculated product in the timetable
return productString
} }

View File

@@ -0,0 +1,711 @@
package com.schoolapp.cleverclass
import android.app.TimePickerDialog
import android.content.Context
import android.content.Intent
import android.icu.text.DecimalFormat
import android.icu.text.DecimalFormatSymbols
import android.icu.util.Calendar
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.selection.TextSelectionColors
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.outlined.Edit
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material.ripple.LocalRippleTheme
import androidx.compose.material.ripple.RippleAlpha
import androidx.compose.material.ripple.RippleTheme
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.Checkbox
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Surface
import androidx.compose.material3.Tab
import androidx.compose.material3.TabRow
import androidx.compose.material3.Text
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat.startActivity
import com.schoolapp.cleverclass.LessonStoreManager.getLessons
import com.schoolapp.cleverclass.LessonStoreManager.saveLessons
import com.schoolapp.cleverclass.ui.theme.CleverClassTheme
import com.schoolapp.cleverclass.ui.theme.InputPrimaryColor
import com.schoolapp.cleverclass.ui.theme.InputSecondaryColor
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.util.Locale
private lateinit var activityType : String
private lateinit var activityState : String
private val listOfDays = listOf("Mo", "Di", "Mi", "Do", "Fr")
//Custom ripple to remove effect when touching tabs
private class CustomRippleTheme : RippleTheme {
@Composable
override fun defaultColor(): Color = Color.Unspecified
@Composable
override fun rippleAlpha(): RippleAlpha = RippleAlpha(
draggedAlpha = 0f,
focusedAlpha = 0f,
hoveredAlpha = 0f,
pressedAlpha = 0f
)
}
class TimeTableSetupActivity : ComponentActivity() {
companion object{
const val TYPE_KEY = "type_key"
const val STATE_KEY = "state_key"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//Defines setup type --> Time vs. Day
activityType = intent.getStringExtra(TYPE_KEY).toString()
//Defines curent day of week
activityState = intent.getStringExtra(STATE_KEY).toString()
//Load lessons of defined day from activityState
var lessons = listOf<LessonData>()
if (activityType == "DaySetup") {
CoroutineScope(Dispatchers.IO).launch {
getLessons(this@TimeTableSetupActivity, listOfDays[activityState.toInt()]).collect { savedLessons ->
lessons = savedLessons.toList()
}
}
}
setContent {
CleverClassTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
TimeTableSetupContent(activity = this, lessons)
}
}
}
}
}
// Content of TimeTableSetup
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TimeTableSetupContent(activity: ComponentActivity, loadedLessons: List<LessonData>){
//One-time definition of the inputField colorscheme
val inputFieldColors = TextFieldDefaults.outlinedTextFieldColors(
textColor = MaterialTheme.colorScheme.onPrimaryContainer,
containerColor = MaterialTheme.colorScheme.secondaryContainer,
cursorColor = InputPrimaryColor,
selectionColors = TextSelectionColors(
handleColor = InputPrimaryColor,
backgroundColor = InputSecondaryColor
),
focusedBorderColor = InputSecondaryColor,
unfocusedBorderColor = Color.Transparent
)
//Declaration of most sharedPreferences-stored data
val sharedPreferences = activity.getSharedPreferences("TimeTable", Context.MODE_PRIVATE)
val editor = sharedPreferences.edit()
//Length of one lesson
var lessonLength by remember {
mutableStateOf(sharedPreferences.getInt("lessonLength", 0).toString())
}
//Number of breaks on one day
var breakAmnt by remember {
mutableStateOf(sharedPreferences.getInt("breakAmnt", 0).toString())
}
//Beginning time of the first lesson (Format: hh.mm)
var firstLesson by remember {
mutableStateOf(sharedPreferences.getFloat("firstLesson", 0.0f))
}
//Value to determine whether the first setup will be loaded again
var setupDone by remember {
mutableStateOf(sharedPreferences.getBoolean("setupDone", false))
}
//AlertDialog
var showInfo by remember {
mutableStateOf(false)
}
//Variable for loaded lessons from onCreate()
var lessons by remember {
mutableStateOf(loadedLessons)
}
Column {
TopAppBar(
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(MaterialTheme.colorScheme.primaryContainer),
title = { Text(text = "") },
navigationIcon = {
IconButton(
onClick = {
activity.finish()
if (setupDone) {
val intent = Intent(activity, StundenplanActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(activity, intent, null)
}
else {
val intent = Intent(activity, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(activity, intent, null)
}
}
) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = null,
modifier = Modifier.size(28.dp),
tint = MaterialTheme.colorScheme.onPrimaryContainer
)
}
},
modifier = Modifier.fillMaxWidth()
)
Column(
verticalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxHeight()
.verticalScroll(rememberScrollState())
) {
//Background
Surface(
color = MaterialTheme.colorScheme.primaryContainer,
shape = RoundedCornerShape(20),
modifier = Modifier.padding(16.dp)
) {
Column(horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(16.dp)
) {
//Title
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = "Stundenplan Setup",
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onPrimaryContainer,
modifier = Modifier.padding(10.dp)
)
Spacer(modifier = Modifier.weight(1f))
//Info Button --> AlertDialog
IconButton(onClick = { showInfo = !showInfo }) {
Icon(
imageVector = Icons.Outlined.Info,
contentDescription = null,
tint = MaterialTheme.colorScheme.onPrimaryContainer
)
}
}
Spacer(modifier = Modifier.height(16.dp))
//Setup for basic time data (lesson/break length etc.)
if (activityType == "TimeSetup") {
//Time input begin of first lesson
Text(
text = "Beginn der 1. Stunde",
modifier = Modifier.fillMaxWidth(),
style = MaterialTheme.typography.labelMedium
)
OutlinedButton(
onClick = {
val c = Calendar.getInstance()
val mHour = c.get(Calendar.HOUR_OF_DAY)
val mMinute = c.get(Calendar.MINUTE)
val timePickerDialog = TimePickerDialog(activity, TimePickerDialog.OnTimeSetListener(function = { view, h, m ->
firstLesson = h.toFloat() + (m.toFloat() / 100)
editor.putFloat("firstLesson", firstLesson)
}), mHour, mMinute, true)
timePickerDialog.show()
},
modifier = Modifier.align(Alignment.Start)
) {
val locale = Locale.getDefault()
val symbols = DecimalFormatSymbols(locale)
symbols.decimalSeparator = ':'
val decimalFormat = DecimalFormat("00.00", symbols)
Text(
text = decimalFormat.format(firstLesson) + " Uhr ",
color = MaterialTheme.colorScheme.onPrimaryContainer)
Icon(imageVector = Icons.Outlined.Edit, contentDescription = null)
}
Spacer(modifier = Modifier.height(16.dp))
//Duration of one lesson
OutlinedTextField(
value = lessonLength,
onValueChange = { lessonLength = it },
label = { Text(text = "Länge einer Schulstunde in min") },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
shape = RoundedCornerShape(20),
textStyle = MaterialTheme.typography.labelMedium,
colors = inputFieldColors,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.NumberPassword)
)
Spacer(modifier = Modifier.height(16.dp))
//Input amount of breaks
OutlinedTextField(
value = breakAmnt,
onValueChange = { breakAmnt = it},
label = { Text(text = "Anzahl der Pausen") },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
shape = RoundedCornerShape(20),
textStyle = MaterialTheme.typography.labelMedium,
colors = inputFieldColors,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.NumberPassword)
)
//Input of length and start time for each break
if (breakAmnt != "") {
//declaration of sharedPreferences for break data
for (i in 0 until breakAmnt.toInt()) {
var currentBreakStartTime by remember{
mutableStateOf(sharedPreferences.getFloat("break${i}StartTime", 0.0f))
}
var currentBreakLength by remember{
mutableStateOf(sharedPreferences.getInt("break${i}Length", 0).toString())
}
Spacer(modifier = Modifier.height(16.dp))
//Input start time (break)
Text(
text = "Startzeit der ${i + 1}. Pause:",
modifier = Modifier.fillMaxWidth(),
style = MaterialTheme.typography.labelMedium
)
OutlinedButton(
onClick = {
val c = Calendar.getInstance()
val mHour = c.get(Calendar.HOUR_OF_DAY)
val mMinute = c.get(Calendar.MINUTE)
val timePickerDialog = TimePickerDialog(activity, TimePickerDialog.OnTimeSetListener(function = { view, h, m ->
currentBreakStartTime = h.toFloat() + (m.toFloat() / 100)
editor.putFloat("break${i}StartTime", currentBreakStartTime)
}), mHour, mMinute, true)
timePickerDialog.show()
},
modifier = Modifier.align(Alignment.Start)
) {
val locale = Locale.getDefault()
val symbols = DecimalFormatSymbols(locale)
symbols.decimalSeparator = ':'
val decimalFormat = DecimalFormat("00.00", symbols)
Text(
text = decimalFormat.format(currentBreakStartTime) + " Uhr ",
color = MaterialTheme.colorScheme.onPrimaryContainer,
style = MaterialTheme.typography.labelMedium
)
Icon(imageVector = Icons.Outlined.Edit, contentDescription = null)
}
Spacer(modifier = Modifier.height(5.dp))
//Input duration (break)
OutlinedTextField(
value = currentBreakLength,
onValueChange = {
currentBreakLength = it
if (currentBreakLength != "") {
editor.putInt("break${i}Length", currentBreakLength.toInt())
}},
label = { Text(text = "Länge der ${i + 1}. Pause in min") },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
shape = RoundedCornerShape(20),
textStyle = MaterialTheme.typography.labelMedium,
colors = inputFieldColors,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.NumberPassword)
)
}
}
Spacer(modifier = Modifier.height(10.dp))
Row {
//Go back to StundenplanActivity
FilledTonalButton(
onClick = {
editor.putInt("lessonLength", lessonLength.toInt())
editor.putInt("breakAmnt", breakAmnt.toInt())
editor.apply()
activity.finish()
if (setupDone) {
//Reopen of activity so values will be updated
val intent = Intent(activity, StundenplanActivity::class.java)
//Remove previous StundenplanActivity from activity stack so when going back later it won't exist twice
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(activity, intent, null)
}
else {
val intent = Intent(activity, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(activity, intent, null)
}
},
modifier = Modifier
.padding(5.dp)
.weight(1f)
) {
Text(
text = "Zurück",
color = MaterialTheme.colorScheme.secondary,
style = MaterialTheme.typography.labelMedium
)
}
Spacer(modifier = Modifier
.width(5.dp)
)
//Move on to DaySetup
Button(
onClick = {
editor.putInt("lessonLength", lessonLength.toInt())
editor.putInt("breakAmnt", breakAmnt.toInt())
editor.apply()
activity.finish()
val intent = Intent(activity, TimeTableSetupActivity::class.java).apply {
putExtra(TimeTableSetupActivity.TYPE_KEY, "DaySetup")
//State 0 for indexing days (0..4)
putExtra(TimeTableSetupActivity.STATE_KEY, "0")
}
startActivity(activity, intent, null)
},
modifier = Modifier
.padding(5.dp)
.weight(1f)
) {
Text(
text = "Weiter",
style = MaterialTheme.typography.labelMedium
)
}
}
}
//DaySetup for information on the lessons (teacher, subject, amount per day, etc.)
else if (activityType == "DaySetup") {
//State that defines the current day (e.g. for LessonData or the Tab highlighting)
val state by remember {
mutableStateOf(activityState.toInt())
}
//SharedPreferences for the amount of hours of each day (indexed by state --> 0..4)
var currentLessonAmount by remember {
mutableStateOf(sharedPreferences.getInt("lessonAmount${listOfDays[state]}", 0).toString())
}
//TabRow with Tabs to visualize the days; no onClick fun and thus the custom ripple theme
CompositionLocalProvider(LocalRippleTheme provides CustomRippleTheme()) {
TabRow(selectedTabIndex = state,
containerColor = MaterialTheme.colorScheme.primaryContainer) {
for (day in listOfDays) {
Tab(selected = false,
onClick = {},
modifier = Modifier.padding(5.dp)
) {
Text(
text = day,
color = MaterialTheme.colorScheme.onPrimaryContainer,
style = MaterialTheme.typography.labelMedium
)
}
}
}
}
Spacer(modifier = Modifier.height(16.dp))
//Input of the amount of lessons on the current day
OutlinedTextField(
value = currentLessonAmount,
onValueChange = {
currentLessonAmount = it
if (currentLessonAmount == "") {
editor.putInt("lessonAmount${listOfDays[state]}", 0)
}
else {
editor.putInt("lessonAmount${listOfDays[state]}", currentLessonAmount.toInt())
}
},
label = { Text(text = "Anzahl der Schulstunden (${listOfDays[state]})") },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
shape = RoundedCornerShape(20),
textStyle = MaterialTheme.typography.labelMedium,
colors = inputFieldColors,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.NumberPassword)
)
//Creation of TextField for further input for the lessons themselves + saveUsage into DataStore --> saveLessonUsage(...)
if (currentLessonAmount != "") {
if (currentLessonAmount != "0") {
//Creation/Removal of the current amount of LessonData object depending pn currentLessonAmount
for (i in lessons.size until currentLessonAmount.toInt()) {
lessons = lessons + LessonData("", "", "")
}
while (currentLessonAmount.toInt() < lessons.size) {
lessons = lessons.dropLast(1)
}
lessons.forEachIndexed { index, lessonData ->
//Input variables
var currentSubject by remember {
mutableStateOf(lessonData.subject)
}
var currentTeacher by remember {
mutableStateOf(lessonData.teacher)
}
var currentRoom by remember {
mutableStateOf(lessonData.room)
}
//Input of the subject of the current lesson
OutlinedTextField(
value = currentSubject,
onValueChange = {
currentSubject = it
lessonData.subject = it
saveLessonsUsage(activity, lessons, state)
},
label = { Text(text = "Fach der ${index + 1}. Stunde") },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
shape = RoundedCornerShape(20),
textStyle = MaterialTheme.typography.labelMedium,
colors = inputFieldColors,
)
//Input of the room of the current lesson
OutlinedTextField(
value = currentRoom,
onValueChange = {
currentRoom = it
lessonData.room = it
saveLessonsUsage(activity, lessons, state)
},
label = { Text(text = "Raum der ${index + 1}. Stunde") },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
shape = RoundedCornerShape(20),
textStyle = MaterialTheme.typography.labelMedium,
colors = inputFieldColors,
)
//Input of the teacher of the current lesson
OutlinedTextField(
value = currentTeacher,
onValueChange = {
currentTeacher = it
lessonData.teacher = it
saveLessonsUsage(activity, lessons, state)
},
label = { Text(text = "Lehrer der ${index + 1}. Stunde") },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
shape = RoundedCornerShape(20),
textStyle = MaterialTheme.typography.labelMedium,
colors = inputFieldColors,
)
}
saveLessonsUsage(activity, lessons, state)
}
//save an empty list into DataStore currentLessonAmount == 0
else {
saveLessonsUsage(activity, emptyList(), state)
}
}
Spacer(modifier = Modifier.height(16.dp))
//Checkbox on the friday page (state == 4) to prevent setup from automatically openening next time
if (state == 4) {
Box(
modifier = Modifier
.height(2.dp)
.fillMaxWidth()
.background(color = MaterialTheme.colorScheme.onPrimaryContainer)
)
Row {
Text(
text = "Ertes Setup beendet?",
modifier = Modifier.align(Alignment.CenterVertically),
style = MaterialTheme.typography.labelMedium
)
Checkbox(checked = setupDone, onCheckedChange = {setupDone = !setupDone})
}
}
//Go back
Row {
FilledTonalButton(
onClick = {
activity.finish()
//To TimeSetup
if (state == 0) {
val intent = Intent(activity, TimeTableSetupActivity::class.java).apply {
putExtra(TimeTableSetupActivity.TYPE_KEY, "TimeSetup")
}
startActivity(activity, intent, null)
}
//To the day before (STATE_KEY -= 1)
else {
val intent = Intent(activity, TimeTableSetupActivity::class.java).apply {
putExtra(TimeTableSetupActivity.TYPE_KEY, "DaySetup")
putExtra(TimeTableSetupActivity.STATE_KEY, "${state - 1}")
}
startActivity(activity, intent, null)
}
},
modifier = Modifier
.padding(5.dp)
.weight(1f)
) {
Text(
text = "Zurück",
color = MaterialTheme.colorScheme.secondary,
style = MaterialTheme.typography.labelMedium
)
}
Spacer(modifier = Modifier
.width(5.dp)
)
//Go forwards
Button(
onClick = {
activity.finish()
//To next day (STATE_KEY += 1)
if (state != 4) {
val intent = Intent(activity, TimeTableSetupActivity::class.java).apply {
putExtra(TimeTableSetupActivity.TYPE_KEY, "DaySetup")
putExtra(TimeTableSetupActivity.STATE_KEY, "${state + 1}")
}
startActivity(activity, intent, null)
}
//To Stundenplan
else {
editor.putBoolean("setupDone", setupDone)
//Reopen of activity so values will be updated
val intent = Intent(activity, StundenplanActivity::class.java)
//Remove previous StundenplanActivity from activity stack so when going back later it won't exist twice
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(activity, intent, null)
}
editor.apply()
},
modifier = Modifier
.padding(5.dp)
.weight(1f)
) {
Text(
text = "Weiter",
style = MaterialTheme.typography.labelMedium
)
}
}
}
}
}
}
}
//AlertDialog to inform the user of the possibility of changing all inputs later
if (showInfo) {
AlertDialog(
onDismissRequest = { showInfo = false },
text = {
Text(
text = "Alle Eingaben können später geändert werden.",
color = MaterialTheme.colorScheme.onPrimaryContainer,
style = MaterialTheme.typography.labelMedium)
},
confirmButton = {
Text(
text = "Schließen",
color = MaterialTheme.colorScheme.secondary,
style = MaterialTheme.typography.labelMedium,
modifier = Modifier.clickable { showInfo = false }
)
},
containerColor = MaterialTheme.colorScheme.primaryContainer,
modifier = Modifier
.padding(5.dp)
)
}
}
//Function that implements the save of List<LessonData>
fun saveLessonsUsage(context: Context, lessons: List<LessonData>, int: Int) {
CoroutineScope(Dispatchers.IO).launch {
saveLessons(context, lessons, listOfDays[int])
}
}

View File

@@ -0,0 +1,117 @@
package com.schoolapp.cleverclass
import android.app.Activity
import android.content.Context
import android.content.Intent
import androidx.activity.ComponentActivity
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
const val NO_INTERNET_CONNECTION_CODE = -1
fun formatFloat(number: Float): String {
var formattedString = number.toString()
formattedString = formattedString.substring(0, if(formattedString.length == 3) 3 else 4)
if(number.isNaN())
formattedString = "0.0"
return formattedString
}
@Composable
fun LoadingScreen(){
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
){
CircularProgressIndicator(color = MaterialTheme.colorScheme.secondary)
}
}
@Composable
fun StaticLoadingScreen(){
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
){
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = "Loading...",
style = MaterialTheme.typography.bodySmall,
modifier = Modifier.padding(start = 16.dp, end = 16.dp),
color = MaterialTheme.colorScheme.onBackground
)
Spacer(modifier = Modifier.height(8.dp))
Image(
painter = painterResource(id = R.drawable.static_loading_icon),
contentDescription = null,
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onBackground),
contentScale = ContentScale.FillBounds
)
}
}
}
fun deleteAllData(activity: ComponentActivity){
val sharedPrefs = listOf("Settings", "DSBmobile", "gradeAverages", "TimeTable")
for (prefName in sharedPrefs) {
val sharedPreferences = activity.getSharedPreferences(prefName, Context.MODE_PRIVATE)
val editor = sharedPreferences.edit()
editor.clear()
editor.apply()
}
clearFolder(activity.filesDir)
clearFolder(activity.cacheDir)
CoroutineScope(Dispatchers.Main).launch {
restartApp(activity)
}
}
fun clearFolder(directory: File) {
if (directory.exists()) {
val files = directory.listFiles()
if (files != null) {
for (file in files) {
if (file.isDirectory) {
clearFolder(file)
}
file.delete()
}
}
}
}
fun restartApp(context: Context) {
val packageManager = context.packageManager
val intent = packageManager.getLaunchIntentForPackage(context.packageName)
intent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
intent?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent)
if (context is Activity) {
context.finish()
}
Runtime.getRuntime().exit(0)
}

View File

@@ -4,10 +4,22 @@ import androidx.compose.ui.graphics.Color
val Black100 = Color(0xFF000000) val Black100 = Color(0xFF000000)
val Black90 = Color(0xFF1F1F1F) val Black90 = Color(0xFF1F1F1F)
val Black70 = Color(0xFF333333) val Black80 = Color(0xFF333333)
val Black70 = Color(0xFF4B4B4B)
val White100 = Color(0xFFFFFFFF) val White100 = Color(0xFFFFFFFF)
val White90 = Color(0xFFEBEBEB) val White90 = Color(0xFFFAFAFA)
val White70 = Color(0xFFD3D3D3) val White80 = Color(0xFFD3D3D3)
val White70 = Color(0xFFAFAFAF)
val TextOnColouredButton = Color(0xFF121212)
val TextOnColouredButton = Color(0xFF121212)
val MiddleColor = Color(0xFF808080)
val dismissLight = Color(0xFF536DFE)
val dismissDark = Color(0xFF448AFF)
val InputPrimaryColor = Color(0xFF3F57E0)
val InputSecondaryColor = Color(0x7E4154FF)
val testColor = Color(0xFF00FF00)

View File

@@ -18,15 +18,23 @@ import androidx.core.view.WindowCompat
private val DarkColorScheme = darkColorScheme( private val DarkColorScheme = darkColorScheme(
background = Black90, background = Black90,
primaryContainer = Black70, primaryContainer = Black80,
onPrimary = White100, secondaryContainer = Black70,
onPrimaryContainer = White100,
secondary = dismissDark,
onBackground = White100,
outlineVariant = Black80,
primary = Color(0xFF536DFE) primary = Color(0xFF536DFE)
) )
private val LightColorScheme = lightColorScheme( private val LightColorScheme = lightColorScheme(
background = White90, background = White90,
primaryContainer = White70, primaryContainer = White80,
onPrimary = Black100, secondaryContainer = White70,
onPrimaryContainer = Black100,
secondary = dismissLight,
onBackground = Black100,
outlineVariant = White80,
primary = Color(0xFF8396FF) primary = Color(0xFF8396FF)
) )
@@ -50,7 +58,7 @@ fun CleverClassTheme(
if (!view.isInEditMode) { if (!view.isInEditMode) {
SideEffect { SideEffect {
val window = (view.context as Activity).window val window = (view.context as Activity).window
window.statusBarColor = Black70.toArgb() window.statusBarColor = Black80.toArgb()
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
} }
} }

View File

@@ -17,6 +17,13 @@ val Typography = Typography(
lineHeight = 24.sp, lineHeight = 24.sp,
letterSpacing = 0.5.sp letterSpacing = 0.5.sp
), ),
labelLarge = TextStyle(
fontFamily = FontFamily(Font(R.font.arlrdbd, FontWeight.Normal)),
fontWeight = FontWeight.Normal,
fontSize = 24.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
),
headlineMedium = TextStyle( headlineMedium = TextStyle(
fontFamily = FontFamily(Font(R.font.arlrdbd, FontWeight.Normal)), fontFamily = FontFamily(Font(R.font.arlrdbd, FontWeight.Normal)),
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal,

View File

@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M19,4L5,4c-1.11,0 -2,0.9 -2,2v12c0,1.1 0.89,2 2,2h4v-2L5,18L5,8h14v10h-4v2h4c1.1,0 2,-0.9 2,-2L21,6c0,-1.1 -0.89,-2 -2,-2zM12,10l-4,4h3v6h2v-6h3l-4,-4z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,7c2.76,0 5,2.24 5,5 0,0.65 -0.13,1.26 -0.36,1.83l2.92,2.92c1.51,-1.26 2.7,-2.89 3.43,-4.75 -1.73,-4.39 -6,-7.5 -11,-7.5 -1.4,0 -2.74,0.25 -3.98,0.7l2.16,2.16C10.74,7.13 11.35,7 12,7zM2,4.27l2.28,2.28 0.46,0.46C3.08,8.3 1.78,10.02 1,12c1.73,4.39 6,7.5 11,7.5 1.55,0 3.03,-0.3 4.38,-0.84l0.42,0.42L19.73,22 21,20.73 3.27,3 2,4.27zM7.53,9.8l1.55,1.55c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.66 1.34,3 3,3 0.22,0 0.44,-0.03 0.65,-0.08l1.55,1.55c-0.67,0.33 -1.41,0.53 -2.2,0.53 -2.76,0 -5,-2.24 -5,-5 0,-0.79 0.2,-1.53 0.53,-2.2zM11.84,9.02l3.15,3.15 0.02,-0.16c0,-1.66 -1.34,-3 -3,-3l-0.17,0.01z"/>
</vector>

View File

@@ -1,170 +1,74 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector
android:width="108dp"
android:height="108dp" android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108" android:viewportWidth="108"
android:viewportHeight="108"> xmlns:android="http://schemas.android.com/apk/res/android">
<path <path android:fillColor="#3DDC84"
android:fillColor="#3DDC84" android:pathData="M0,0h108v108h-108z"/>
android:pathData="M0,0h108v108h-108z" /> <path android:fillColor="#00000000" android:pathData="M9,0L9,108"
<path android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:fillColor="#00000000" <path android:fillColor="#00000000" android:pathData="M19,0L19,108"
android:pathData="M9,0L9,108" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeWidth="0.8" <path android:fillColor="#00000000" android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path <path android:fillColor="#00000000" android:pathData="M39,0L39,108"
android:fillColor="#00000000" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:pathData="M19,0L19,108" <path android:fillColor="#00000000" android:pathData="M49,0L49,108"
android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M59,0L59,108"
<path android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:fillColor="#00000000" <path android:fillColor="#00000000" android:pathData="M69,0L69,108"
android:pathData="M29,0L29,108" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeWidth="0.8" <path android:fillColor="#00000000" android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path <path android:fillColor="#00000000" android:pathData="M89,0L89,108"
android:fillColor="#00000000" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:pathData="M39,0L39,108" <path android:fillColor="#00000000" android:pathData="M99,0L99,108"
android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M0,9L108,9"
<path android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:fillColor="#00000000" <path android:fillColor="#00000000" android:pathData="M0,19L108,19"
android:pathData="M49,0L49,108" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeWidth="0.8" <path android:fillColor="#00000000" android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path <path android:fillColor="#00000000" android:pathData="M0,39L108,39"
android:fillColor="#00000000" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:pathData="M59,0L59,108" <path android:fillColor="#00000000" android:pathData="M0,49L108,49"
android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M0,59L108,59"
<path android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:fillColor="#00000000" <path android:fillColor="#00000000" android:pathData="M0,69L108,69"
android:pathData="M69,0L69,108" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeWidth="0.8" <path android:fillColor="#00000000" android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path <path android:fillColor="#00000000" android:pathData="M0,89L108,89"
android:fillColor="#00000000" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:pathData="M79,0L79,108" <path android:fillColor="#00000000" android:pathData="M0,99L108,99"
android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M19,29L89,29"
<path android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:fillColor="#00000000" <path android:fillColor="#00000000" android:pathData="M19,39L89,39"
android:pathData="M89,0L89,108" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeWidth="0.8" <path android:fillColor="#00000000" android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path <path android:fillColor="#00000000" android:pathData="M19,59L89,59"
android:fillColor="#00000000" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:pathData="M99,0L99,108" <path android:fillColor="#00000000" android:pathData="M19,69L89,69"
android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M19,79L89,79"
<path android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:fillColor="#00000000" <path android:fillColor="#00000000" android:pathData="M29,19L29,89"
android:pathData="M0,9L108,9" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeWidth="0.8" <path android:fillColor="#00000000" android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path <path android:fillColor="#00000000" android:pathData="M49,19L49,89"
android:fillColor="#00000000" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:pathData="M0,19L108,19" <path android:fillColor="#00000000" android:pathData="M59,19L59,89"
android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M69,19L69,89"
<path android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:fillColor="#00000000" <path android:fillColor="#00000000" android:pathData="M79,19L79,89"
android:pathData="M0,29L108,29" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector> </vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="36dp"
android:height="36dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M480,880q-83,0 -156,-31.5T197,763q-54,-54 -85.5,-127T80,480q0,-83 31.5,-156T197,197q54,-54 127,-85.5T480,80q83,0 156,31.5T763,197q54,54 85.5,127T880,480q0,83 -31.5,156T763,763q-54,54 -127,85.5T480,880ZM480,800q64,0 123,-24t104,-69L480,480v-320q-134,0 -227,93t-93,227q0,134 93,227t227,93Z"
android:fillColor="#e8eaed"/>
</vector>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 982 B

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 900 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -6,5 +6,8 @@
<string name="title_activity_stundenplan">StundenplanActivity</string> <string name="title_activity_stundenplan">StundenplanActivity</string>
<string name="title_activity_noten">NotenActivity</string> <string name="title_activity_noten">NotenActivity</string>
<string name="title_activity_fach">FachActivity</string> <string name="title_activity_fach">FachActivity</string>
<string name="dialog_schließen">Schließen</string> <string name="title_activity_dsbactivity">DSBActivity</string>
<string name="title_activity_dsbday_view">DSBDayViewActivity</string>
<string name="title_activity_dsblogin">DSBLoginActivity</string>
<string name="title_activity_timetable_setup">TimetableSetupActivity</string>
</resources> </resources>

View File

@@ -5,7 +5,7 @@
--> -->
<data-extraction-rules> <data-extraction-rules>
<cloud-backup> <cloud-backup>
<!-- TODO: Use <include> and <exclude> to control what is backed up. <!-- Use <include> and <exclude> to control what is backed up.
<include .../> <include .../>
<exclude .../> <exclude .../>
--> -->