Jelajahi Sumber

Welcome Tutorials screen implemented

minhaj 1 bulan lalu
induk
melakukan
4e87a43dcc

+ 3 - 0
app/build.gradle.kts

@@ -75,6 +75,9 @@ dependencies {
     implementation(libs.okhttp3.logging.interceptor)
     implementation(libs.runtime.livedata)
     implementation(libs.androidx.fragment)
+    implementation(libs.accompanist.pager)
+    implementation(libs.accompanist.pager.indicators)
+
 
     testImplementation(libs.junit)
     androidTestImplementation(libs.androidx.junit)

+ 6 - 0
app/src/main/java/com/fastest/pass/splash/presentation/ui/SplashFragment.kt

@@ -11,6 +11,7 @@ import com.fastest.pass.splash.presentation.viewmodels.SplashViewModel
 import dagger.hilt.android.AndroidEntryPoint
 import androidx.fragment.app.viewModels
 import com.fastest.pass.splash.utils.SplashNavigation
+import com.fastest.pass.splash.utils.SplashRoute
 import com.fastest.pass.ui.theme.FastestPassTheme
 import javax.inject.Inject
 
@@ -33,6 +34,11 @@ class SplashFragment : Fragment() {
     ): View {
         return ComposeView(requireActivity()).apply {
             setContent {
+
+                viewModel.navigateToLogin.value?.let {
+                    viewModel.navigateTo(SplashRoute.OpenWelcome)
+                }
+
                 FastestPassTheme {
                     SplashScreen(viewModel)
                 }

+ 2 - 3
app/src/main/java/com/fastest/pass/splash/presentation/viewmodels/SplashViewModel.kt

@@ -18,15 +18,14 @@ class SplashViewModel @Inject constructor(var splashUseCase: SplashUseCase) : Vi
     private val _navigateToLogin = MutableLiveData<Boolean>(false)
     val navigateToLogin: LiveData<Boolean> = _navigateToLogin
 
-
     private val _router = MutableStateFlow<SplashRoute>(SplashRoute.NoneScreen)
     val router :MutableStateFlow<SplashRoute> = _router
 
     fun checkAuthentication() {
         viewModelScope.launch {
             delay(2000)
-            navigateTo(SplashRoute.OpenLogin)
-//            _navigateToLogin.value = true
+//            navigateTo(SplashRoute.OpenLogin)
+            _navigateToLogin.value = true
         }
     }
 

+ 4 - 0
app/src/main/java/com/fastest/pass/splash/utils/SplashNavigation.kt

@@ -15,6 +15,10 @@ class SplashNavigation {
                         splashFragment.findNavController().popBackStack()
                         splashFragment.findNavController().navigate(R.id.loginFragment)
                     }
+                    SplashRoute.OpenWelcome -> {
+                        splashFragment.findNavController().popBackStack()
+                        splashFragment.findNavController().navigate(R.id.welcomeFragment)
+                    }
                     SplashRoute.NoneScreen -> {}
                 }
 

+ 1 - 0
app/src/main/java/com/fastest/pass/splash/utils/SplashRoute.kt

@@ -2,5 +2,6 @@ package com.fastest.pass.splash.utils
 
 sealed class SplashRoute {
     data object OpenLogin : SplashRoute()
+    data object OpenWelcome : SplashRoute()
     data object NoneScreen : SplashRoute()
 }

+ 1 - 0
app/src/main/java/com/fastest/pass/ui/theme/Color.kt

@@ -3,6 +3,7 @@ package com.fastest.pass.ui.theme
 import androidx.compose.ui.graphics.Color
 
 val Gray_Splash = Color(0xFF404B69)
+val Welcome_BG = Color(0xFFDBEDF3)
 val PurpleGrey80 = Color(0xFFCCC2DC)
 val Pink80 = Color(0xFFEFB8C8)
 

+ 20 - 0
app/src/main/java/com/fastest/pass/welcome/di/WelcomeModule.kt

@@ -0,0 +1,20 @@
+package com.fastest.pass.welcome.di
+
+import com.fastest.pass.welcome.utils.WelcomeNavigation
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+object WelcomeModule {
+
+    @Provides
+    @Singleton
+    fun provideNavigation(): WelcomeNavigation {
+        return WelcomeNavigation()
+    }
+
+}

+ 7 - 0
app/src/main/java/com/fastest/pass/welcome/domain/model/Welcome.kt

@@ -0,0 +1,7 @@
+package com.fastest.pass.welcome.domain.model
+
+class Welcome(
+    var title:String,
+    var image:Int,
+    var description :String
+)

+ 188 - 0
app/src/main/java/com/fastest/pass/welcome/presentation/ui/component/WelcomeScreen.kt

@@ -0,0 +1,188 @@
+package com.fastest.pass.welcome.presentation.ui.component
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+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.WindowInsets
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.navigationBars
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.windowInsetsPadding
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.fastest.pass.R
+import com.fastest.pass.ui.theme.Gray_Splash
+import com.fastest.pass.ui.theme.Welcome_BG
+import com.fastest.pass.welcome.domain.model.Welcome
+import com.google.accompanist.pager.HorizontalPager
+import com.google.accompanist.pager.HorizontalPagerIndicator
+import com.google.accompanist.pager.rememberPagerState
+
+enum class ClickType {
+    LOGIN_CLICK,
+    SIGNUP_CLICK
+}
+
+@Composable
+fun WelcomeTutorial(onClick: (ClickType) -> Unit) {
+    val pagerState = rememberPagerState()
+    val pages = listOf(
+        Welcome(
+            "Welcome to LockMyPass",
+            R.drawable.welcome_1,
+            "LockMyPass securely stores, synchronizes, \nand manages your passwords and personal \ndata across all your devices.",
+        ),
+        Welcome(
+            "Never forget again",
+            R.drawable.welcome_2,
+            "With just one master password, you can \naccess all the websites you use.",
+        ),
+        Welcome(
+            "Trusted and secure",
+            R.drawable.welcome_3,
+            "Your vault is secured with top-tier security \nmeasures. It's so secure that even we\n cannot access your passwords.",
+        )
+    )
+
+    Column(
+        modifier = Modifier
+            .fillMaxWidth()
+            .windowInsetsPadding(WindowInsets.navigationBars) // Adds space for system navigation
+    ) {
+        Row(
+            modifier = Modifier
+                .fillMaxWidth()
+                .height(75.dp)
+                .background(colorResource(id = R.color.gray_splash))
+        ) { }
+
+        Box(modifier = Modifier.fillMaxSize()) {
+
+            HorizontalPager(
+                count = pages.size,
+                state = pagerState,
+                modifier = Modifier.fillMaxSize()
+            ) { page ->
+
+                WelcomePage(
+                    title = pages[page].title,
+                    description = pages[page].description,
+                    imageRes = pages[page].image,
+                    page
+                ) { type ->
+                    onClick(type)
+                }
+            }
+
+            // Pager Indicator
+            HorizontalPagerIndicator(
+                pagerState = pagerState,
+                modifier = Modifier
+                    .align(Alignment.BottomCenter)
+                    .padding(vertical = 25.dp),
+                activeColor = Color.White,
+                inactiveColor = Gray_Splash
+            )
+        }
+    }
+}
+
+
+@Composable
+fun WelcomePage(
+    title: String,
+    description: String,
+    imageRes: Int,
+    page: Int,
+    onClick: (ClickType) -> Unit
+) {
+    Box(
+        modifier = Modifier
+            .fillMaxSize()
+            .background(Welcome_BG)
+    ) {
+        Column(modifier = Modifier.align(Alignment.Center)) {
+            Text(
+                text = title,
+                style = TextStyle(fontSize = 24.sp, textAlign = TextAlign.Center),
+                color = Gray_Splash,
+                modifier = Modifier.fillMaxWidth()
+            )
+
+            Spacer(Modifier.height(120.dp))
+            // Image at the Center
+            Image(
+                painter = painterResource(id = imageRes),
+                contentDescription = null,
+                modifier = Modifier
+                    .padding(horizontal = 20.dp)
+                    .fillMaxWidth()
+                    .height(250.dp)
+
+            )
+            Spacer(Modifier.height(120.dp))
+            // Description at the Bottom
+            Text(
+                text = description,
+                style = TextStyle(fontSize = 15.sp, textAlign = TextAlign.Center),
+                color = Gray_Splash,
+                textAlign = TextAlign.Center,
+                modifier = Modifier
+                    .fillMaxWidth()
+
+            )
+            if (page == 2) {
+                Spacer(Modifier.height(50.dp))
+                Row(
+                    verticalAlignment = Alignment.CenterVertically,
+                    horizontalArrangement = Arrangement.Center,
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .padding(vertical = 10.dp)
+                ) {
+                    Text(
+                        text = "LOG IN",
+                        style = TextStyle(fontSize = 18.sp),
+                        color = Gray_Splash,
+                        modifier = Modifier.clickable {
+                            onClick.invoke(ClickType.LOGIN_CLICK)
+                        }
+                    )
+                    Spacer(modifier = Modifier.width(8.dp)) // Add spacing between "Login" and "|"
+                    Text(
+                        text = "|",
+                        style = TextStyle(fontSize = 18.sp),
+                        color = Gray_Splash
+                    )
+                    Spacer(modifier = Modifier.width(8.dp)) // Add spacing between "|" and "SignUp"
+                    Text(
+                        text = "SIGN UP",
+                        style = TextStyle(fontSize = 18.sp),
+                        color = Gray_Splash,
+                        modifier = Modifier.clickable {
+                            onClick.invoke(ClickType.SIGNUP_CLICK)
+                        }
+                    )
+                }
+            }
+
+        }
+    }
+}

+ 56 - 0
app/src/main/java/com/fastest/pass/welcome/presentation/ui/fragment/WelcomeFragment.kt

@@ -0,0 +1,56 @@
+package com.fastest.pass.welcome.presentation.ui.fragment
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.compose.ui.platform.ComposeView
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import com.fastest.pass.ui.theme.FastestPassTheme
+import com.fastest.pass.welcome.presentation.ui.component.ClickType.LOGIN_CLICK
+import com.fastest.pass.welcome.presentation.ui.component.ClickType.SIGNUP_CLICK
+import com.fastest.pass.welcome.presentation.ui.component.WelcomeTutorial
+import com.fastest.pass.welcome.presentation.viewmodel.WelcomeViewModel
+import com.fastest.pass.welcome.utils.WelcomeNavigation
+import com.fastest.pass.welcome.utils.WelcomeNavigationRoute
+import dagger.hilt.android.AndroidEntryPoint
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class WelcomeFragment : Fragment() {
+
+    val viewModel : WelcomeViewModel by viewModels()
+
+    @Inject
+    lateinit var navigation : WelcomeNavigation
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        navigation.navigateTo(this)
+    }
+
+    override fun onCreateView(
+        inflater: LayoutInflater, container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View {
+        return ComposeView(requireActivity()).apply {
+            setContent {
+                FastestPassTheme {
+                    WelcomeTutorial { clickType ->
+
+                        when (clickType) {
+                            LOGIN_CLICK -> {
+                                viewModel.navigateTo(WelcomeNavigationRoute.OpenLogin)
+                            }
+
+                            SIGNUP_CLICK -> {
+                                viewModel.navigateTo(WelcomeNavigationRoute.OpenSignup)
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}

+ 18 - 0
app/src/main/java/com/fastest/pass/welcome/presentation/viewmodel/WelcomeViewModel.kt

@@ -0,0 +1,18 @@
+package com.fastest.pass.welcome.presentation.viewmodel
+
+import androidx.lifecycle.ViewModel
+import com.fastest.pass.splash.utils.SplashRoute
+import com.fastest.pass.welcome.utils.WelcomeNavigationRoute
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class WelcomeViewModel : ViewModel() {
+
+    private val _router = MutableStateFlow<WelcomeNavigationRoute>(WelcomeNavigationRoute.NoneScreen)
+    val router: MutableStateFlow<WelcomeNavigationRoute> = _router
+
+
+    fun navigateTo(splashRoute: WelcomeNavigationRoute) {
+        _router.value = splashRoute
+    }
+
+}

+ 32 - 0
app/src/main/java/com/fastest/pass/welcome/utils/WelcomeNavigation.kt

@@ -0,0 +1,32 @@
+package com.fastest.pass.welcome.utils
+
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.fragment.findNavController
+import com.fastest.pass.R
+import com.fastest.pass.welcome.presentation.ui.fragment.WelcomeFragment
+
+class WelcomeNavigation {
+
+    fun navigateTo(welcomeFragment: WelcomeFragment) {
+        welcomeFragment.lifecycleScope.launchWhenStarted {
+            welcomeFragment.viewModel.router.collect {
+                when (it) {
+                    is WelcomeNavigationRoute.OpenLogin -> {
+
+                        welcomeFragment.findNavController().popBackStack()
+                        welcomeFragment.findNavController().navigate(R.id.loginFragment)
+
+                    }
+
+                    is WelcomeNavigationRoute.OpenSignup -> {
+
+                    }
+
+                    else -> {}
+                }
+
+                welcomeFragment.viewModel.navigateTo(WelcomeNavigationRoute.NoneScreen)
+            }
+        }
+    }
+}

+ 9 - 0
app/src/main/java/com/fastest/pass/welcome/utils/WelcomeNavigationRoute.kt

@@ -0,0 +1,9 @@
+package com.fastest.pass.welcome.utils
+
+sealed class WelcomeNavigationRoute {
+
+    object OpenLogin : WelcomeNavigationRoute()
+    object OpenSignup : WelcomeNavigationRoute()
+    object NoneScreen  : WelcomeNavigationRoute()
+
+}

TEMPAT SAMPAH
app/src/main/res/drawable/lockpasslogo.png


File diff ditekan karena terlalu besar
+ 39 - 0
app/src/main/res/drawable/lockpasslogo.xml


TEMPAT SAMPAH
app/src/main/res/drawable/welcome_1.png


TEMPAT SAMPAH
app/src/main/res/drawable/welcome_2.png


TEMPAT SAMPAH
app/src/main/res/drawable/welcome_3.png


+ 4 - 0
app/src/main/res/navigation/nav_graph.xml

@@ -11,4 +11,8 @@
         android:id="@+id/loginFragment"
         android:name="com.fastest.pass.login.presentation.ui.LoginFragment"
         android:label="LoginFragment" />
+    <fragment
+        android:id="@+id/welcomeFragment"
+        android:name="com.fastest.pass.welcome.presentation.ui.fragment.WelcomeFragment"
+        android:label="WelcomeFragment" />
 </navigation>

+ 4 - 0
gradle/libs.versions.toml

@@ -19,6 +19,7 @@ navigation-compose = "2.7.7"
 runtime-livedata = "1.7.5"
 androidx-fragment = "1.8.5"
 navigationFragment = "2.8.4"
+accompanist-pager = "0.31.3-beta"
 
 [libraries]
 androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -48,6 +49,9 @@ navigation-compose = { module = "androidx.navigation:navigation-compose", versio
 runtime-livedata = { module = "androidx.compose.runtime:runtime-livedata", version.ref = "runtime-livedata"}
 androidx-fragment = { module = "androidx.fragment:fragment-ktx", version.ref = "androidx-fragment"}
 androidx-navigation-fragment = { group = "androidx.navigation", name = "navigation-fragment", version.ref = "navigationFragment" }
+accompanist-pager= { module = "com.google.accompanist:accompanist-pager", version.ref = "accompanist-pager" }
+accompanist-pager-indicators = { module = "com.google.accompanist:accompanist-pager-indicators", version.ref = "accompanist-pager" }
+
 
 [plugins]
 android-application = { id = "com.android.application", version.ref = "agp" }