diff --git a/app/build.gradle b/app/build.gradle index d230b3cea..e2596bf75 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -57,6 +57,9 @@ android { minSdkVersion 23 targetSdkVersion 34 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables { + useSupportLibrary true + } } signingConfigs { @@ -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 -> @@ -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')) { @@ -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 diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/WelcomeActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/WelcomeActivity.kt index e0a1238e0..5e558c098 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/WelcomeActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/WelcomeActivity.kt @@ -18,136 +18,196 @@ 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 - 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() 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("•", 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() { @@ -155,25 +215,4 @@ class WelcomeActivity : AppCompatActivity(R.layout.activity_welcome) { 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) {} - } -} +} \ No newline at end of file diff --git a/app/src/full/java/com/celzero/bravedns/ui/ui/theme/Color.kt b/app/src/full/java/com/celzero/bravedns/ui/ui/theme/Color.kt new file mode 100644 index 000000000..ccf40439d --- /dev/null +++ b/app/src/full/java/com/celzero/bravedns/ui/ui/theme/Color.kt @@ -0,0 +1,11 @@ +package com.celzero.bravedns.ui.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/app/src/full/java/com/celzero/bravedns/ui/ui/theme/Theme.kt b/app/src/full/java/com/celzero/bravedns/ui/ui/theme/Theme.kt new file mode 100644 index 000000000..6e9816025 --- /dev/null +++ b/app/src/full/java/com/celzero/bravedns/ui/ui/theme/Theme.kt @@ -0,0 +1,59 @@ +package com.celzero.bravedns.ui.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.graphics.Color +import androidx.compose.ui.platform.LocalContext + +private val DarkColorScheme = darkColorScheme( + primary = Color(0xFF636363), + secondary = Color(0xFF444444), + tertiary = Color(0xFF18ffff) +) + +private val LightColorScheme = lightColorScheme( + primary = Color(0xFF636363), + secondary = Color(0xFF444444), + tertiary = Color(0xFF18ffff) + + /* 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 BravednsTheme( + 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/app/src/full/java/com/celzero/bravedns/ui/ui/theme/Type.kt b/app/src/full/java/com/celzero/bravedns/ui/ui/theme/Type.kt new file mode 100644 index 000000000..47e8b95a6 --- /dev/null +++ b/app/src/full/java/com/celzero/bravedns/ui/ui/theme/Type.kt @@ -0,0 +1,34 @@ +package com.celzero.bravedns.ui.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/app/src/main/res/layout/welcome_slide1.xml b/app/src/main/res/layout/welcome_slide1.xml deleted file mode 100644 index e9d031aa3..000000000 --- a/app/src/main/res/layout/welcome_slide1.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/welcome_slide2.xml b/app/src/main/res/layout/welcome_slide2.xml deleted file mode 100644 index 234c515ec..000000000 --- a/app/src/main/res/layout/welcome_slide2.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - diff --git a/build.gradle b/build.gradle index 5f9e8e2ef..ae674e5c7 100644 --- a/build.gradle +++ b/build.gradle @@ -20,6 +20,7 @@ buildscript { plugins { id 'com.google.devtools.ksp' version '1.9.23-1.0.20' apply false + id 'org.jetbrains.kotlin.android' version '1.9.0' apply false } allprojects {