Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jetpack Compose Migration: WelcomeActivity #1692

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 30 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ android {
minSdkVersion 23
targetSdkVersion 34
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary true
}
}

signingConfigs {
Expand Down Expand Up @@ -101,18 +104,22 @@ android {
buildFeatures {
viewBinding true
buildConfig true
compose true
}

compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}

packagingOptions {
jniLibs {
keepDebugSymbols += ['**/*.so']
}
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}

variantFilter { variant ->
Expand Down Expand Up @@ -154,6 +161,12 @@ android {
lint {
abortOnError false
}
kotlinOptions {
jvmTarget = '1.8'
}
composeOptions {
kotlinCompilerExtensionVersion '1.5.13'
}

tasks.configureEach { task ->
if (task.name.toLowerCase().contains('headless')) {
Expand Down Expand Up @@ -295,6 +308,21 @@ dependencies {
// not in fdroid or website
playImplementation 'com.google.firebase:firebase-crashlytics:19.0.0'
playImplementation 'com.google.firebase:firebase-crashlytics-ndk:19.0.0'

// jetpack compose
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.5'
implementation 'androidx.activity:activity-compose:1.9.2'
implementation platform('androidx.compose:compose-bom:2024.09.00')
implementation 'androidx.compose.ui:ui'
implementation 'androidx.compose.ui:ui-graphics'
implementation 'androidx.compose.ui:ui-tooling-preview'
implementation 'androidx.compose.material3:material3'

debugImplementation 'androidx.compose.ui:ui-tooling'
debugImplementation 'androidx.compose.ui:ui-test-manifest'

androidTestImplementation platform('androidx.compose:compose-bom:2024.09.00')
androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
}

// github.com/michel-kraemer/gradle-download-task/issues/131#issuecomment-464476903
Expand Down
287 changes: 163 additions & 124 deletions app/src/full/java/com/celzero/bravedns/ui/activity/WelcomeActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,162 +18,201 @@ package com.celzero.bravedns.ui.activity
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.graphics.Color
import android.os.Bundle
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.Window
import android.view.WindowManager
import android.widget.LinearLayout
import android.widget.TextView
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AppCompatActivity
import androidx.core.text.HtmlCompat
import androidx.viewpager.widget.PagerAdapter
import androidx.viewpager.widget.ViewPager
import by.kirich1409.viewbindingdelegate.viewBinding
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
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.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.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.celzero.bravedns.R
import com.celzero.bravedns.databinding.ActivityWelcomeBinding
import com.celzero.bravedns.service.PersistentState
import com.celzero.bravedns.ui.HomeScreenActivity
import com.celzero.bravedns.ui.ui.theme.BravednsTheme
import com.celzero.bravedns.util.Themes
import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject

class WelcomeActivity : AppCompatActivity(R.layout.activity_welcome) {
private val b by viewBinding(ActivityWelcomeBinding::bind)
private lateinit var dots: Array<TextView?>
internal val layout: IntArray = intArrayOf(R.layout.welcome_slide2, R.layout.welcome_slide1)

private lateinit var myPagerAdapter: PagerAdapter
class WelcomeActivity : ComponentActivity() {
private val persistentState by inject<PersistentState>()

override fun onCreate(savedInstanceState: Bundle?) {
setTheme(Themes.getCurrentTheme(isDarkThemeOn(), persistentState.theme))
super.onCreate(savedInstanceState)

addBottomDots(0)
changeStatusBarColor()

myPagerAdapter = MyPagerAdapter()

b.viewPager.adapter = myPagerAdapter

b.btnSkip.setOnClickListener { launchHomeScreen() }

b.btnNext.setOnClickListener {
val currentItem = getItem()
// size and count() are almost always equivalent. However some lazy Seq cannot know
// their size until being fulfilled so size will be undefined for those cases and
// calling count() will fulfill the lazy Seq to determine its size.
if (currentItem + 1 >= layout.count()) {
launchHomeScreen()
} else {
b.viewPager.currentItem = currentItem + 1
}
}
enableEdgeToEdge()
setContent {
BravednsTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { padding ->

val coroutineScope = rememberCoroutineScope()
val pagerState = rememberPagerState(pageCount = { 2 })

val slideContents = listOf(
Triple(
R.string.slide_1_title,
R.string.slide_1_desc,
R.drawable.illustrations_welcome_1
), Triple(
R.string.slide_2_title,
R.string.slide_2_desc,
R.drawable.illustrations_welcome_2
)
)

Column(Modifier.padding(padding)) {

HorizontalPager(
modifier = Modifier
.fillMaxSize()
.weight(1f)
.padding(16.dp),
state = pagerState
) { _ ->

val triple = slideContents[pagerState.currentPage]

Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth(),
text = getString(triple.first),
textAlign = TextAlign.Center,
style = MaterialTheme.typography.headlineLarge
)
Text(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth(),
text = getString(triple.second),
textAlign = TextAlign.Center,
style = MaterialTheme.typography.bodyLarge
)

Image(
modifier = Modifier.padding(32.dp),
painter = painterResource(triple.third),
contentDescription = ""
)
}
}

Spacer(
modifier = Modifier
.height(1.dp)
.fillMaxWidth()
.background(MaterialTheme.colorScheme.onSurface)
)

Row(
modifier = Modifier,
verticalAlignment = Alignment.CenterVertically,
) {
TextButton(modifier = Modifier
.weight(1f)
.padding(8.dp), onClick = {
launchHomeScreen()
}) {
Text(
text = "SKIP",
style = MaterialTheme.typography.titleMedium,
)
}

Row(
Modifier
.weight(1f)
.padding(8.dp),
horizontalArrangement = Arrangement.Center
) {
repeat(pagerState.pageCount) { iteration ->
val color =
if (pagerState.currentPage == iteration) MaterialTheme.colorScheme.primary else Color.Transparent
val borderColor =
if (pagerState.currentPage == iteration) Color.Transparent else MaterialTheme.colorScheme.onBackground
Box(
modifier = Modifier
.padding(2.dp)
.clip(CircleShape)
.border(BorderStroke(1.dp, borderColor), CircleShape)
.background(color)
.size(8.dp)
)
}
}

TextButton(modifier = Modifier
.weight(1f)
.padding(8.dp), onClick = {
coroutineScope.launch {
val currentPage = pagerState.currentPage

if (currentPage == slideContents.size - 1) {
launchHomeScreen()
} else {
pagerState.scrollToPage(currentPage + 1)
}
}
}) {
Text(
text = "NEXT", style = MaterialTheme.typography.titleMedium
)
}
}

b.viewPager.addOnPageChangeListener(
object : ViewPager.OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) {}

override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {}

override fun onPageSelected(position: Int) {
addBottomDots(position)
if (position >= layout.count() - 1) {
b.btnNext.text = getString(R.string.finish)
b.btnNext.visibility = View.VISIBLE
b.btnSkip.visibility = View.INVISIBLE
} else {
b.btnSkip.visibility = View.VISIBLE
b.btnNext.visibility = View.INVISIBLE
}
}
}
)

// Note that you shouldn't override the onBackPressed() as that will make the
// onBackPressedDispatcher callback not to fire
onBackPressedDispatcher.addCallback(
this /* lifecycle owner */,
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
// Back is pressed...
return
}
}
)
}
}

private fun Context.isDarkThemeOn(): Boolean {
return resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
Configuration.UI_MODE_NIGHT_YES
return resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
}

private fun changeStatusBarColor() {
val window: Window = window
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
window.statusBarColor = Color.TRANSPARENT
}

private fun addBottomDots(currentPage: Int) {
dots = arrayOfNulls(layout.count())

val colorActive = resources.getIntArray(R.array.array_dot_active)
val colorInActive = resources.getIntArray(R.array.array_dot_inactive)

b.layoutDots.removeAllViews()
for (i in dots.indices) {
dots[i] = TextView(this)
dots[i]?.layoutParams =
LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
dots[i]?.text = HtmlCompat.fromHtml("&#8226;", HtmlCompat.FROM_HTML_MODE_LEGACY)
dots[i]?.setTextSize(TypedValue.COMPLEX_UNIT_SP, 30F)
dots[i]?.setTextColor(colorInActive[currentPage])
b.layoutDots.addView(dots[i])
}
if (dots.isNotEmpty()) {
dots[currentPage]?.setTextColor(colorActive[currentPage])
}
}

private fun getItem(): Int {
return b.viewPager.currentItem
window.statusBarColor = Color.Transparent.toArgb()
}

private fun launchHomeScreen() {
persistentState.firstTimeLaunch = false
startActivity(Intent(this, HomeScreenActivity::class.java))
finish()
}

inner class MyPagerAdapter : PagerAdapter() {
private lateinit var layoutInflater: LayoutInflater

override fun isViewFromObject(view: View, `object`: Any): Boolean {
return view == `object`
}

override fun getCount(): Int {
return layout.count()
}

override fun instantiateItem(container: ViewGroup, position: Int): Any {
layoutInflater = getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val view: View = layoutInflater.inflate(layout[position], container, false)
container.addView(view)
return view
}

override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {}
}
}
}
Loading
Loading