Skip to content

Commit 2c29c18

Browse files
author
alllexey
committed
Add QR Code activity
1 parent 7c38ba0 commit 2c29c18

File tree

8 files changed

+216
-0
lines changed

8 files changed

+216
-0
lines changed

app/src/main/AndroidManifest.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
android:roundIcon="@mipmap/ic_launcher_round"
1515
android:supportsRtl="true"
1616
android:theme="@style/AppTheme">
17+
<activity
18+
android:name=".ui.qr.QrCodeActivity"
19+
android:exported="false" />
1720
<activity
1821
android:name=".ui.schedule.ScheduleActivity"
1922
android:exported="false" />
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package me.alllexey123.itmowidgets.ui.qr
2+
3+
import android.os.Bundle
4+
import android.widget.ImageView
5+
import android.widget.Toast
6+
import androidx.activity.viewModels
7+
import androidx.appcompat.app.AppCompatActivity
8+
import com.google.android.material.floatingactionbutton.FloatingActionButton
9+
import me.alllexey123.itmowidgets.ItmoWidgetsApp
10+
import me.alllexey123.itmowidgets.R
11+
import me.alllexey123.itmowidgets.ui.widgets.QrCodeWidget
12+
13+
private const val PIXELS_PER_MODULE: Int =
14+
(QrCodeWidget.Companion.PIXELS_PER_MODULE * 1.5).toInt()
15+
16+
class QrCodeActivity : AppCompatActivity() {
17+
18+
private lateinit var qrBgImage: ImageView
19+
20+
private lateinit var qrCodeImage: ImageView
21+
22+
private lateinit var fabRefresh: FloatingActionButton
23+
24+
private val qrCodeViewModel: QrCodeViewModel by viewModels {
25+
val appContainer = (application as ItmoWidgetsApp).appContainer
26+
QrCodeViewModelFactory(appContainer.qrCodeRepository)
27+
}
28+
29+
30+
override fun onCreate(savedInstanceState: Bundle?) {
31+
super.onCreate(savedInstanceState)
32+
setContentView(R.layout.activity_qr_code)
33+
qrCodeImage = findViewById(R.id.qr_code_image)
34+
qrBgImage = findViewById(R.id.qr_bg_image)
35+
setupButtons()
36+
37+
observeUiState()
38+
updateQr()
39+
}
40+
41+
private fun setupButtons() {
42+
fabRefresh = findViewById(R.id.fab_refresh)
43+
fabRefresh.setOnClickListener {
44+
val appContainer = (applicationContext as ItmoWidgetsApp).appContainer
45+
appContainer.qrCodeRepository.clearCache()
46+
updateQr()
47+
}
48+
}
49+
50+
fun updateQr() {
51+
qrCodeViewModel.fetchQrCode()
52+
}
53+
54+
private fun observeUiState() {
55+
qrCodeViewModel.uiState.observe(this) { state ->
56+
val appContainer = (applicationContext as ItmoWidgetsApp).appContainer
57+
val generator = appContainer.qrCodeGenerator
58+
val renderer = appContainer.qrBitmapRenderer
59+
val storage = appContainer.storage
60+
61+
val dynamicColors = storage.getDynamicQrColorsState()
62+
63+
val bitmap = when (state) {
64+
is QrCodeUiState.Loading -> {
65+
renderer.renderFull(21 * PIXELS_PER_MODULE, PIXELS_PER_MODULE / 2F, dynamicColors)
66+
}
67+
68+
is QrCodeUiState.Success -> {
69+
val qrCode = generator.generate(state.qrCodeHex)
70+
renderer.render(qrCode, PIXELS_PER_MODULE, dynamicColors)
71+
}
72+
73+
is QrCodeUiState.Error -> {
74+
Toast.makeText(this, state.message, Toast.LENGTH_LONG).show()
75+
renderer.renderEmpty(21 * PIXELS_PER_MODULE, PIXELS_PER_MODULE / 2F, dynamicColors)
76+
}
77+
}
78+
79+
val colors = renderer.getQrColors(dynamicColors)
80+
qrBgImage.setColorFilter(colors.first)
81+
qrCodeImage.setImageBitmap(bitmap)
82+
}
83+
}
84+
85+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package me.alllexey123.itmowidgets.ui.qr
2+
3+
import androidx.lifecycle.LiveData
4+
import androidx.lifecycle.MutableLiveData
5+
import androidx.lifecycle.ViewModel
6+
import androidx.lifecycle.viewModelScope
7+
import kotlinx.coroutines.launch
8+
import me.alllexey123.itmowidgets.data.repository.QrCodeRepository
9+
10+
sealed class QrCodeUiState {
11+
object Loading : QrCodeUiState()
12+
data class Success(val qrCodeHex: String) : QrCodeUiState()
13+
data class Error(val message: String) : QrCodeUiState()
14+
}
15+
16+
class QrCodeViewModel(
17+
private val qrCodeRepository: QrCodeRepository
18+
) : ViewModel() {
19+
20+
private val _uiState = MutableLiveData<QrCodeUiState>()
21+
val uiState: LiveData<QrCodeUiState> = _uiState
22+
23+
fun fetchQrCode() {
24+
_uiState.value = QrCodeUiState.Loading
25+
26+
viewModelScope.launch {
27+
try {
28+
_uiState.value = QrCodeUiState.Loading
29+
val qrCodeHex = qrCodeRepository.getQrHex()
30+
_uiState.postValue(QrCodeUiState.Success(qrCodeHex))
31+
} catch (e: Exception) {
32+
val errorMessage = "Failed to update qr code: ${e.message}"
33+
_uiState.postValue(QrCodeUiState.Error(errorMessage))
34+
e.printStackTrace()
35+
}
36+
}
37+
}
38+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package me.alllexey123.itmowidgets.ui.qr
2+
3+
import androidx.lifecycle.ViewModel
4+
import androidx.lifecycle.ViewModelProvider
5+
import me.alllexey123.itmowidgets.data.repository.QrCodeRepository
6+
7+
class QrCodeViewModelFactory(
8+
private val qrCodeRepository: QrCodeRepository
9+
) : ViewModelProvider.Factory {
10+
11+
@Suppress("UNCHECKED_CAST")
12+
override fun <T : ViewModel> create(modelClass: Class<T>): T {
13+
if (modelClass.isAssignableFrom(QrCodeViewModel::class.java)) {
14+
return QrCodeViewModel(qrCodeRepository) as T
15+
}
16+
throw IllegalArgumentException("Unknown ViewModel class")
17+
}
18+
}

app/src/main/java/me/alllexey123/itmowidgets/ui/schedule/ScheduleActivity.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import androidx.recyclerview.widget.RecyclerView
1515
import com.google.android.material.floatingactionbutton.FloatingActionButton
1616
import me.alllexey123.itmowidgets.ItmoWidgetsApp
1717
import me.alllexey123.itmowidgets.R
18+
import me.alllexey123.itmowidgets.ui.qr.QrCodeActivity
1819
import me.alllexey123.itmowidgets.ui.settings.SettingsActivity
1920
import java.time.LocalDate
2021

@@ -25,6 +26,8 @@ class ScheduleActivity : AppCompatActivity() {
2526

2627
private lateinit var fabSettings: FloatingActionButton
2728

29+
private lateinit var fabQr: FloatingActionButton
30+
2831
private val snapHelper = PagerSnapHelper()
2932

3033
private val scheduleViewModel: ScheduleViewModel by viewModels {
@@ -50,6 +53,11 @@ class ScheduleActivity : AppCompatActivity() {
5053
val intent = Intent(this, SettingsActivity::class.java)
5154
startActivity(intent)
5255
}
56+
fabQr = findViewById(R.id.fab_qr)
57+
fabQr.setOnClickListener { view ->
58+
val intent = Intent(this, QrCodeActivity::class.java)
59+
startActivity(intent)
60+
}
5361
}
5462

5563
private fun setupRecyclerView() {
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
2+
android:width="24dp"
3+
android:height="24dp"
4+
android:viewportWidth="960"
5+
android:viewportHeight="960"
6+
android:tint="?colorOnPrimaryContainer">
7+
<path
8+
android:fillColor="#FF000000"
9+
android:pathData="M120,440v-320h320v320L120,440ZM200,360h160v-160L200,200v160ZM120,840v-320h320v320L120,840ZM200,760h160v-160L200,600v160ZM520,440v-320h320v320L520,440ZM600,360h160v-160L600,200v160ZM760,840v-80h80v80h-80ZM520,600v-80h80v80h-80ZM600,680v-80h80v80h-80ZM520,760v-80h80v80h-80ZM600,840v-80h80v80h-80ZM680,760v-80h80v80h-80ZM680,600v-80h80v80h-80ZM760,680v-80h80v80h-80Z"/>
10+
</vector>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:app="http://schemas.android.com/apk/res-auto"
4+
xmlns:tools="http://schemas.android.com/tools"
5+
android:id="@+id/main"
6+
android:layout_width="match_parent"
7+
android:layout_height="match_parent"
8+
tools:context=".ui.qr.QrCodeActivity">
9+
10+
<RelativeLayout
11+
android:layout_width="270dp"
12+
android:layout_height="270dp"
13+
android:gravity="center_vertical"
14+
android:padding="8dp"
15+
app:layout_constraintBottom_toBottomOf="parent"
16+
app:layout_constraintEnd_toEndOf="parent"
17+
app:layout_constraintStart_toStartOf="parent"
18+
app:layout_constraintTop_toTopOf="parent"
19+
android:layout_marginBottom="120dp">
20+
21+
<ImageView
22+
android:id="@+id/qr_bg_image"
23+
android:layout_width="match_parent"
24+
android:layout_height="match_parent"
25+
android:scaleType="fitXY"
26+
android:src="@drawable/widget_background_rounded" />
27+
28+
<ImageView
29+
android:id="@+id/qr_code_image"
30+
android:layout_width="match_parent"
31+
android:layout_height="match_parent"
32+
android:layout_margin="8dp"
33+
android:background="@android:color/transparent" />
34+
35+
</RelativeLayout>
36+
37+
<com.google.android.material.floatingactionbutton.FloatingActionButton
38+
android:id="@+id/fab_refresh"
39+
android:layout_width="wrap_content"
40+
android:layout_height="wrap_content"
41+
android:layout_margin="16dp"
42+
app:srcCompat="@drawable/ic_refresh"
43+
app:layout_constraintBottom_toBottomOf="parent"
44+
app:layout_constraintEnd_toEndOf="parent" />
45+
</androidx.constraintlayout.widget.ConstraintLayout>

app/src/main/res/layout/activity_schedule.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@
2828
app:layout_constraintEnd_toEndOf="parent"
2929
android:visibility="visible" />
3030

31+
<com.google.android.material.floatingactionbutton.FloatingActionButton
32+
android:id="@+id/fab_qr"
33+
android:layout_width="wrap_content"
34+
android:layout_height="wrap_content"
35+
android:layout_margin="16dp"
36+
app:srcCompat="@drawable/ic_qr_code"
37+
app:layout_constraintBottom_toTopOf="@id/fab_settings"
38+
app:layout_constraintEnd_toEndOf="parent" />
39+
3140

3241
<com.google.android.material.floatingactionbutton.FloatingActionButton
3342
android:id="@+id/fab_settings"

0 commit comments

Comments
 (0)