commit e63250e25bbf67acacf57152284fd8f841e09f6f Author: purifetchi <0xlunaric@gmail.com> Date: Tue Jun 10 23:36:41 2025 +0200 android diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7914c3d --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +#built application files +*.apk +*.ap_ +*.aab + +# files for the dex VM +*.dex + +# Java class files +*.class + +# generated files +bin/ +gen/ + +# Local configuration file (sdk path, etc) +local.properties + +# Windows thumbnail db +Thumbs.db + +# OSX files +.DS_Store + +# Android Studio +*.iml +.idea +#.idea/workspace.xml - remove # and delete .idea if it better suit your needs. +.gradle +build/ +.navigation +captures/ +output.json + +#NDK +obj/ +.externalNativeBuild \ No newline at end of file diff --git a/lab1/.gitignore b/lab1/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/lab1/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/lab1/.kotlin/errors/errors-1749574013288.log b/lab1/.kotlin/errors/errors-1749574013288.log new file mode 100644 index 0000000..1219b50 --- /dev/null +++ b/lab1/.kotlin/errors/errors-1749574013288.log @@ -0,0 +1,4 @@ +kotlin version: 2.0.21 +error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output: + 1. Kotlin compile daemon is ready + diff --git a/lab1/app/.gitignore b/lab1/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/lab1/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lab1/app/build.gradle.kts b/lab1/app/build.gradle.kts new file mode 100644 index 0000000..d39e277 --- /dev/null +++ b/lab1/app/build.gradle.kts @@ -0,0 +1,63 @@ +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.compose) +} + +android { + namespace = "com.example.lab1" + compileSdk = 35 + + defaultConfig { + applicationId = "com.example.lab1" + minSdk = 27 + targetSdk = 35 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + kotlinOptions { + jvmTarget = "11" + } + buildFeatures { + compose = true + } +} + +dependencies { + + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.activity.compose) + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.ui) + implementation(libs.androidx.ui.graphics) + implementation(libs.androidx.ui.tooling.preview) + implementation(libs.androidx.material3) + implementation(libs.androidx.appcompat) + implementation(libs.material) + implementation(libs.androidx.activity) + implementation(libs.androidx.constraintlayout) + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + androidTestImplementation(platform(libs.androidx.compose.bom)) + androidTestImplementation(libs.androidx.ui.test.junit4) + debugImplementation(libs.androidx.ui.tooling) + debugImplementation(libs.androidx.ui.test.manifest) +} \ No newline at end of file diff --git a/lab1/app/proguard-rules.pro b/lab1/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/lab1/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/lab1/app/src/androidTest/java/com/example/lab1/ExampleInstrumentedTest.kt b/lab1/app/src/androidTest/java/com/example/lab1/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..d5d5740 --- /dev/null +++ b/lab1/app/src/androidTest/java/com/example/lab1/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.example.lab1 + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.example.lab1", appContext.packageName) + } +} \ No newline at end of file diff --git a/lab1/app/src/main/AndroidManifest.xml b/lab1/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..bcbfa47 --- /dev/null +++ b/lab1/app/src/main/AndroidManifest.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lab1/app/src/main/java/com/example/lab1/GradeAdapter.java b/lab1/app/src/main/java/com/example/lab1/GradeAdapter.java new file mode 100644 index 0000000..708399d --- /dev/null +++ b/lab1/app/src/main/java/com/example/lab1/GradeAdapter.java @@ -0,0 +1,75 @@ +package com.example.lab1; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.RadioButton; +import android.widget.RadioGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.List; + +public class GradeAdapter extends RecyclerView.Adapter { + public static class GradeViewHolder extends RecyclerView.ViewHolder { + TextView tvSubject; + RadioGroup rgGrade; + + GradeViewHolder(@NonNull View itemView) { + super(itemView); + tvSubject = itemView.findViewById(R.id.tvSubject); + rgGrade = itemView.findViewById(R.id.rgGrade); + } + } + + private final List subjects; + private final int[] gradeOptions = {2,3,4,5}; + + public GradeAdapter(List subjects) { + this.subjects = subjects; + } + + @NonNull + @Override + public GradeViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.grades_row, parent, false); + return new GradeViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull GradeViewHolder holder, int position) { + Subject item = subjects.get(position); + holder.tvSubject.setText(item.name); + holder.rgGrade.removeAllViews(); + + for (double grade : gradeOptions) { + RadioButton rb = new RadioButton(holder.itemView.getContext()); + rb.setText(String.valueOf(grade)); + rb.setId(View.generateViewId()); + rb.setTag(grade); + + rb.setChecked(item.grade == grade); + + holder.rgGrade.addView(rb); + } + + holder.rgGrade.setOnCheckedChangeListener((group, checkedId) -> { + RadioButton selected = group.findViewById(checkedId); + if (selected != null) { + item.grade = (double)selected.getTag(); + } + }); + } + + @Override + public int getItemCount() { + return subjects.size(); + } + + public List getGradeItems() { + return subjects; + } +} diff --git a/lab1/app/src/main/java/com/example/lab1/GradesActivity.java b/lab1/app/src/main/java/com/example/lab1/GradesActivity.java new file mode 100644 index 0000000..e266aa8 --- /dev/null +++ b/lab1/app/src/main/java/com/example/lab1/GradesActivity.java @@ -0,0 +1,100 @@ +package com.example.lab1; + +import android.content.Intent; +import android.os.Bundle; + +import androidx.activity.EdgeToEdge; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.graphics.Insets; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.ArrayList; +import java.util.List; + +public class GradesActivity extends AppCompatActivity { + + private GradeAdapter gradeAdapter; + private List subjects; + private RecyclerView recyclerView; + + private static final String KEY_GRADES = "key_grades"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + EdgeToEdge.enable(this); + setContentView(R.layout.activity_grades); + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.mainGrades), (v, insets) -> { + Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); + v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); + return insets; + }); + + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + subjects = new ArrayList<>(); + var subjectArray = getResources().getStringArray(R.array.przedmioty); + var subjectCount = getIntent().getIntExtra("gradeCount", 5); + for (var i = 0; i < subjectCount; i++) { + subjects.add(new Subject(subjectArray[i])); + } + + if (savedInstanceState != null && savedInstanceState.containsKey(KEY_GRADES)) { + double[] savedGrades = savedInstanceState.getDoubleArray(KEY_GRADES); + if (savedGrades != null) { + for (int i = 0; i < savedGrades.length && i < subjects.size(); i++) { + subjects.get(i).grade = savedGrades[i]; + } + } + } + + recyclerView = findViewById(R.id.rvListaOcen); + recyclerView.setLayoutManager(new LinearLayoutManager(this)); + gradeAdapter = new GradeAdapter(subjects); + recyclerView.setAdapter(gradeAdapter); + + var calculateButton = findViewById(R.id.btOblicz); + calculateButton.setOnClickListener(v -> calculateAverage()); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + double[] savedGrades = new double[subjects.size()]; + for (int i = 0; i < subjects.size(); i++) { + Double grade = subjects.get(i).grade; + savedGrades[i] = grade; + } + outState.putDoubleArray(KEY_GRADES, savedGrades); + } + + @Override + public void onBackPressed() { + Intent resultIntent = new Intent(); + setResult(RESULT_CANCELED, resultIntent); + super.onBackPressed(); + } + + @Override + public boolean onSupportNavigateUp() { + onBackPressed(); // ustawia wynik i kończy aktywność + return true; + } + + private void calculateAverage() { + double sum = 0; + var items = gradeAdapter.getGradeItems(); + for (Subject item : items) { + sum += item.grade; + } + + double average = sum / items.size(); + Intent resultIntent = new Intent(); + resultIntent.putExtra("average", average); + setResult(RESULT_OK, resultIntent); + finish(); + } +} diff --git a/lab1/app/src/main/java/com/example/lab1/MainActivity.java b/lab1/app/src/main/java/com/example/lab1/MainActivity.java new file mode 100644 index 0000000..3d71b0b --- /dev/null +++ b/lab1/app/src/main/java/com/example/lab1/MainActivity.java @@ -0,0 +1,149 @@ +package com.example.lab1; + +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.View; +import android.widget.*; +import android.content.Intent; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; + +public class MainActivity extends AppCompatActivity { + + final int GRADE_ACTIVITY_REQUEST_CODE = 2420; + + EditText etImie, etNazwisko, etOceny; + Button btOceny; + + boolean isNameValid = false, isSurnameValid = false, isGradeValid = false, isResults = false; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + etImie = findViewById(R.id.etImie); + etNazwisko = findViewById(R.id.etNazwisko); + etOceny = findViewById(R.id.etOceny); + btOceny = findViewById(R.id.btOceny); + + setupValidation(etImie, getString(R.string.imie_invalid), (s) -> { + isNameValid = !s.trim().isEmpty(); + return isNameValid; + }); + + setupValidation(etNazwisko, getString(R.string.nazwisko_invalid), (s) -> { + isSurnameValid = !s.trim().isEmpty(); + return isSurnameValid; + }); + + setupValidation(etOceny, getString(R.string.oceny_invalid), (s) -> { + try { + int num = Integer.parseInt(s); + isGradeValid = num >= 5 && num <= 15; + } catch (NumberFormatException e) { + isGradeValid = false; + } + return isGradeValid; + }); + + btOceny.setOnClickListener(v -> { + Toast.makeText(this, getString(R.string.dane_poprawne), Toast.LENGTH_SHORT).show(); + + Intent intent = new Intent(MainActivity.this, GradesActivity.class); + intent.putExtra("gradeCount", Integer.parseInt(etOceny.getText().toString())); + startActivityForResult(intent, GRADE_ACTIVITY_REQUEST_CODE); + }); + } + + private void setupValidation(EditText editText, String errorMsg, Validator validator) { + editText.setOnFocusChangeListener((v, hasFocus) -> { + if (!hasFocus) { + String value = editText.getText().toString(); + if (!validator.validate(value)) { + editText.setError(errorMsg); + Toast.makeText(this, errorMsg, Toast.LENGTH_SHORT).show(); + } + updateButtonState(); + } + }); + + editText.addTextChangedListener(new TextWatcher() { + public void afterTextChanged(Editable s) { + validator.validate(s.toString()); + updateButtonState(); + } + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + public void onTextChanged(CharSequence s, int start, int before, int count) {} + }); + } + + private void updateButtonState() { + var visibility = isNameValid && isSurnameValid && isGradeValid && !isResults + ? View.VISIBLE + : View.GONE; + + btOceny.setVisibility(visibility); + } + + @Override + protected void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + outState.putString("name", etImie.getText().toString()); + outState.putString("surname", etNazwisko.getText().toString()); + outState.putString("gradeCount", etOceny.getText().toString()); + } + + @Override + protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + etImie.setText(savedInstanceState.getString("name", "")); + etNazwisko.setText(savedInstanceState.getString("surname", "")); + etOceny.setText(savedInstanceState.getString("gradeCount", "")); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + if (requestCode == GRADE_ACTIVITY_REQUEST_CODE && resultCode == RESULT_OK) { + isResults = true; + updateButtonState(); + + var average = data.getDoubleExtra("average", 0.0); + showAverageResult(average); + } + } + + private void showAverageResult(double average) { + TextView tvAverage = findViewById(R.id.tvAverage); + tvAverage.setText(String.format(getString(R.string.srednia_format), average)); + tvAverage.setVisibility(View.VISIBLE); + + var averageAboveThree = average >= 3.0; + Button btAverage = findViewById(R.id.btAverage); + if (averageAboveThree) { + btAverage.setText(R.string.superasnie); + } else { + btAverage.setText(R.string.nie_poszlo); + } + + btAverage.setVisibility(View.VISIBLE); + btAverage.setOnClickListener(v -> { + String message; + if (averageAboveThree) { + message = getString(R.string.gratulacje_zaliczenie); + } else { + message = getString(R.string.zaliczenie_warunkowe); + } + Toast.makeText(this, message, Toast.LENGTH_LONG).show(); + finish(); + }); + } + + interface Validator { + boolean validate(String input); + } +} diff --git a/lab1/app/src/main/java/com/example/lab1/Subject.java b/lab1/app/src/main/java/com/example/lab1/Subject.java new file mode 100644 index 0000000..f291eb8 --- /dev/null +++ b/lab1/app/src/main/java/com/example/lab1/Subject.java @@ -0,0 +1,10 @@ +package com.example.lab1; +public class Subject { + public String name; + public Double grade; + + public Subject(String subjectName) { + this.name = subjectName; + this.grade = 2.0; + } +} diff --git a/lab1/app/src/main/java/com/example/lab1/ui/theme/Color.kt b/lab1/app/src/main/java/com/example/lab1/ui/theme/Color.kt new file mode 100644 index 0000000..9916ecc --- /dev/null +++ b/lab1/app/src/main/java/com/example/lab1/ui/theme/Color.kt @@ -0,0 +1,11 @@ +package com.example.lab1.ui.theme + +import androidx.compose.ui.graphics.Color + +val Purple80 = Color(0xFFD0BCFF) +val PurpleGrey80 = Color(0xFFCCC2DC) +val Pink80 = Color(0xFFEFB8C8) + +val Purple40 = Color(0xFF6650a4) +val PurpleGrey40 = Color(0xFF625b71) +val Pink40 = Color(0xFF7D5260) \ No newline at end of file diff --git a/lab1/app/src/main/java/com/example/lab1/ui/theme/Theme.kt b/lab1/app/src/main/java/com/example/lab1/ui/theme/Theme.kt new file mode 100644 index 0000000..8cf2ba2 --- /dev/null +++ b/lab1/app/src/main/java/com/example/lab1/ui/theme/Theme.kt @@ -0,0 +1,58 @@ +package com.example.lab1.ui.theme + +import android.app.Activity +import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext + +private val DarkColorScheme = darkColorScheme( + primary = Purple80, + secondary = PurpleGrey80, + tertiary = Pink80 +) + +private val LightColorScheme = lightColorScheme( + primary = Purple40, + secondary = PurpleGrey40, + tertiary = Pink40 + + /* Other default colors to override + background = Color(0xFFFFFBFE), + surface = Color(0xFFFFFBFE), + onPrimary = Color.White, + onSecondary = Color.White, + onTertiary = Color.White, + onBackground = Color(0xFF1C1B1F), + onSurface = Color(0xFF1C1B1F), + */ +) + +@Composable +fun Lab1Theme( + darkTheme: Boolean = isSystemInDarkTheme(), + // Dynamic color is available on Android 12+ + dynamicColor: Boolean = true, + content: @Composable () -> Unit +) { + val colorScheme = when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + + darkTheme -> DarkColorScheme + else -> LightColorScheme + } + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content + ) +} \ No newline at end of file diff --git a/lab1/app/src/main/java/com/example/lab1/ui/theme/Type.kt b/lab1/app/src/main/java/com/example/lab1/ui/theme/Type.kt new file mode 100644 index 0000000..6a3879a --- /dev/null +++ b/lab1/app/src/main/java/com/example/lab1/ui/theme/Type.kt @@ -0,0 +1,34 @@ +package com.example.lab1.ui.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) + /* Other default text styles to override + titleLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 22.sp, + lineHeight = 28.sp, + letterSpacing = 0.sp + ), + labelSmall = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 11.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp + ) + */ +) \ No newline at end of file diff --git a/lab1/app/src/main/res/drawable/ic_launcher_background.xml b/lab1/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/lab1/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lab1/app/src/main/res/drawable/ic_launcher_foreground.xml b/lab1/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/lab1/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/lab1/app/src/main/res/layout/activity_grades.xml b/lab1/app/src/main/res/layout/activity_grades.xml new file mode 100644 index 0000000..953142e --- /dev/null +++ b/lab1/app/src/main/res/layout/activity_grades.xml @@ -0,0 +1,31 @@ + + + + + +