Skip to content

Commit 944316d

Browse files
committed
add onboarding activity
1 parent be42752 commit 944316d

23 files changed

+1510
-2
lines changed

app/src/main/AndroidManifest.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
android:roundIcon="@mipmap/ic_launcher_round"
1515
android:supportsRtl="true"
1616
android:theme="@style/AppTheme">
17+
1718
<activity
1819
android:name=".ui.main.MainActivity"
1920
android:exported="true">
@@ -23,6 +24,11 @@
2324
<category android:name="android.intent.category.LAUNCHER" />
2425
</intent-filter>
2526
</activity>
27+
28+
<activity
29+
android:name=".ui.onboarding.OnboardingActivity"
30+
android:exported="false" />
31+
2632
<activity
2733
android:name=".ui.login.LoginActivity"
2834
android:exported="false" />

app/src/main/java/dev/alllexey/itmowidgets/data/UserSettingsStorage.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,12 @@ class UserSettingsStorage(val prefs: SharedPreferences) {
140140
}
141141
}
142142

143+
fun setQrSpoilerState(qrSpoilerState: Boolean) {
144+
prefs.edit(commit = true) {
145+
putBoolean(QR_SPOILER_KEY, qrSpoilerState)
146+
}
147+
}
148+
143149
fun setDynamicQrColorsState(dynamicQrColors: Boolean) {
144150
prefs.edit(commit = true) {
145151
putBoolean(DYNAMIC_QR_COLORS_KEY, dynamicQrColors)

app/src/main/java/dev/alllexey/itmowidgets/data/UtilityStorage.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class UtilityStorage(val prefs: SharedPreferences, val context: Context) {
1515
const val QR_WIDGET_STATE_PREFIX = "qr_widget_state_"
1616
const val LESSON_WIDGET_STYLE_CHANGED_KEY = "lesson_widget_style_changed"
1717
const val SKIPPED_VERSION_KEY = "skipped_version"
18+
const val ONBOARDING_COMPLETED_KEY = "onboarding_completed"
1819
}
1920

2021
fun getFirebaseToken(): String? {
@@ -38,6 +39,10 @@ class UtilityStorage(val prefs: SharedPreferences, val context: Context) {
3839
return prefs.getString(SKIPPED_VERSION_KEY, null) ?: ContextCompat.getString(context, R.string.app_version)
3940
}
4041

42+
fun getOnboardingCompleted(): Boolean {
43+
return prefs.getBoolean(ONBOARDING_COMPLETED_KEY, false)
44+
}
45+
4146
fun setQrWidgetState(appWidgetId: Int, state: QrWidgetState) {
4247
prefs.edit(commit = true) {
4348
putString("$QR_WIDGET_STATE_PREFIX$appWidgetId", state.name)
@@ -67,4 +72,10 @@ class UtilityStorage(val prefs: SharedPreferences, val context: Context) {
6772
putString(SKIPPED_VERSION_KEY, skippedVersion)
6873
}
6974
}
75+
76+
fun setOnboardingCompleted(completed: Boolean) {
77+
prefs.edit(commit = true) {
78+
putBoolean(ONBOARDING_COMPLETED_KEY, completed)
79+
}
80+
}
7081
}

app/src/main/java/dev/alllexey/itmowidgets/ui/main/MainActivity.kt

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
1616
import dev.alllexey.itmowidgets.ItmoWidgetsApp
1717
import dev.alllexey.itmowidgets.R
1818
import dev.alllexey.itmowidgets.appContainer
19+
import dev.alllexey.itmowidgets.ui.onboarding.OnboardingActivity
1920
import dev.alllexey.itmowidgets.ui.qr.QrCodeFragment
2021
import dev.alllexey.itmowidgets.ui.schedule.ScheduleFragment
2122
import dev.alllexey.itmowidgets.ui.settings.SettingsFragment
@@ -41,6 +42,14 @@ class MainActivity : AppCompatActivity() {
4142

4243
override fun onCreate(savedInstanceState: Bundle?) {
4344
super.onCreate(savedInstanceState)
45+
46+
val appContainer = (applicationContext as ItmoWidgetsApp).appContainer
47+
if (!appContainer.storage.utility.getOnboardingCompleted()) {
48+
startActivity(Intent(this, OnboardingActivity::class.java))
49+
finish()
50+
return
51+
}
52+
4453
enableEdgeToEdge()
4554
setContentView(R.layout.activity_main)
4655

@@ -95,7 +104,16 @@ class MainActivity : AppCompatActivity() {
95104
}
96105

97106
resendFcmTokens()
98-
checkVersion()
107+
108+
if (intent.getBooleanExtra("onboarding", false)) {
109+
MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme_FilledButton)
110+
.setTitle("И напоследок...")
111+
.setMessage("Крайне советую посмотреть остальные настройки приложения в самой правой вкладке.\nВероятно, там есть что-то интересное для тебя.")
112+
.setPositiveButton("Хорошо") { dialog, which -> }
113+
.show()
114+
} else {
115+
checkVersion()
116+
}
99117
}
100118

101119
fun resendFcmTokens() {
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package dev.alllexey.itmowidgets.ui.onboarding
2+
3+
import android.Manifest
4+
import android.content.pm.PackageManager
5+
import android.os.Build
6+
import android.os.Bundle
7+
import android.view.View
8+
import android.widget.Toast
9+
import androidx.activity.result.contract.ActivityResultContracts
10+
import androidx.core.content.ContextCompat
11+
import androidx.fragment.app.Fragment
12+
import com.google.android.material.button.MaterialButton
13+
import dev.alllexey.itmowidgets.R
14+
15+
class NotificationPermissionFragment : Fragment(R.layout.fragment_onboarding_notifications) {
16+
17+
private lateinit var btnAllow: MaterialButton
18+
19+
private val requestPermissionLauncher = registerForActivityResult(
20+
ActivityResultContracts.RequestPermission()
21+
) { isGranted: Boolean ->
22+
updateUiState(isGranted)
23+
if (isGranted) {
24+
(activity as? OnboardingActivity)?.nextPage()
25+
} else {
26+
Toast.makeText(requireContext(), "Уведомления можно включить позже в настройках", Toast.LENGTH_SHORT).show()
27+
}
28+
}
29+
30+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
31+
super.onViewCreated(view, savedInstanceState)
32+
33+
btnAllow = view.findViewById(R.id.btn_allow_notifications)
34+
35+
val isGranted = checkPermissionStatus()
36+
updateUiState(isGranted)
37+
38+
btnAllow.setOnClickListener {
39+
if (Build.VERSION.SDK_INT >= 33) {
40+
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
41+
} else {
42+
Toast.makeText(requireContext(), "На вашей версии Android уведомления включены по умолчанию", Toast.LENGTH_SHORT).show()
43+
updateUiState(true)
44+
}
45+
}
46+
}
47+
48+
override fun onResume() {
49+
super.onResume()
50+
updateUiState(checkPermissionStatus())
51+
}
52+
53+
private fun checkPermissionStatus(): Boolean {
54+
return if (Build.VERSION.SDK_INT >= 33) {
55+
ContextCompat.checkSelfPermission(
56+
requireContext(),
57+
Manifest.permission.POST_NOTIFICATIONS
58+
) == PackageManager.PERMISSION_GRANTED
59+
} else {
60+
true
61+
}
62+
}
63+
64+
private fun updateUiState(isGranted: Boolean) {
65+
if (isGranted) {
66+
btnAllow.text = "Разрешено"
67+
btnAllow.isEnabled = false
68+
btnAllow.setIconResource(R.drawable.ic_check)
69+
} else {
70+
btnAllow.text = "Разрешить уведомления"
71+
btnAllow.isEnabled = true
72+
btnAllow.icon = null
73+
}
74+
}
75+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package dev.alllexey.itmowidgets.ui.onboarding
2+
3+
import android.content.Intent
4+
import android.os.Bundle
5+
import android.view.View
6+
import android.widget.Button
7+
import androidx.activity.enableEdgeToEdge
8+
import androidx.appcompat.app.AppCompatActivity
9+
import androidx.core.view.ViewCompat
10+
import androidx.core.view.WindowInsetsCompat
11+
import androidx.viewpager2.widget.ViewPager2
12+
import com.google.android.material.tabs.TabLayout
13+
import com.google.android.material.tabs.TabLayoutMediator
14+
import dev.alllexey.itmowidgets.ItmoWidgetsApp
15+
import dev.alllexey.itmowidgets.R
16+
import dev.alllexey.itmowidgets.ui.main.MainActivity
17+
18+
class OnboardingActivity : AppCompatActivity() {
19+
20+
private lateinit var viewPager: ViewPager2
21+
private lateinit var btnNext: Button
22+
private lateinit var btnSkip: Button
23+
24+
override fun onCreate(savedInstanceState: Bundle?) {
25+
super.onCreate(savedInstanceState)
26+
enableEdgeToEdge()
27+
setContentView(R.layout.activity_onboarding)
28+
29+
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.onboarding_root)) { v, insets ->
30+
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
31+
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
32+
insets
33+
}
34+
35+
viewPager = findViewById(R.id.view_pager)
36+
btnNext = findViewById(R.id.btn_next)
37+
btnSkip = findViewById(R.id.btn_skip)
38+
val tabLayout = findViewById<TabLayout>(R.id.tab_layout)
39+
40+
val adapter = OnboardingAdapter(this)
41+
viewPager.adapter = adapter
42+
viewPager.isUserInputEnabled = false
43+
44+
TabLayoutMediator(tabLayout, viewPager) { _, _ -> }.attach()
45+
46+
btnNext.setOnClickListener {
47+
val current = viewPager.currentItem
48+
val total = viewPager.adapter?.itemCount ?: 0
49+
if (!skippedToEnd && current < total - 1) {
50+
viewPager.currentItem = current + 1
51+
skippedToEnd = skippedToEnd || current == total - 2
52+
updateButtons()
53+
} else {
54+
finishOnboarding()
55+
}
56+
}
57+
58+
btnSkip.setOnClickListener {
59+
finishOnboarding()
60+
}
61+
62+
updateButtons()
63+
}
64+
65+
fun nextPage() {
66+
if (viewPager.currentItem < (viewPager.adapter?.itemCount ?: 0) - 1) {
67+
viewPager.currentItem = viewPager.currentItem + 1
68+
updateButtons()
69+
} else {
70+
finishOnboarding()
71+
}
72+
}
73+
74+
private var skippedToEnd = false
75+
76+
private fun updateButtons() {
77+
if (skippedToEnd) {
78+
btnNext.text = "Завершить"
79+
btnSkip.visibility = View.INVISIBLE
80+
}
81+
}
82+
83+
private fun finishOnboarding() {
84+
val appContainer = (applicationContext as ItmoWidgetsApp).appContainer
85+
appContainer.storage.utility.setOnboardingCompleted(true)
86+
87+
val mainActivityIntent = Intent(this, MainActivity::class.java).putExtra("onboarding", true)
88+
startActivity(mainActivityIntent)
89+
finish()
90+
}
91+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package dev.alllexey.itmowidgets.ui.onboarding
2+
3+
import android.os.Build
4+
import androidx.fragment.app.Fragment
5+
import androidx.fragment.app.FragmentActivity
6+
import androidx.viewpager2.adapter.FragmentStateAdapter
7+
8+
class OnboardingAdapter(activity: FragmentActivity) : FragmentStateAdapter(activity) {
9+
override fun getItemCount(): Int = if (Build.VERSION.SDK_INT >= 33) 5 else 4
10+
11+
override fun createFragment(position: Int): Fragment {
12+
return when (position) {
13+
0 -> WelcomeFragment()
14+
1 -> ScheduleSetupFragment()
15+
2 -> QrSetupFragment()
16+
3 -> ServicesFragment()
17+
4 -> NotificationPermissionFragment()
18+
else -> throw IllegalArgumentException()
19+
}
20+
}
21+
}

0 commit comments

Comments
 (0)