├── .gitignore ├── .idea ├── .gitignore ├── compiler.xml ├── gradle.xml ├── kotlinc.xml ├── misc.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── google-services.json ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── adilegungor │ │ └── gungorecommerce │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── cartt-playstore.png │ ├── ic_launcher-playstore.png │ ├── java │ │ └── com │ │ │ └── adilegungor │ │ │ ├── MainApplication.kt │ │ │ └── gungorecommerce │ │ │ ├── MainActivity.kt │ │ │ ├── common │ │ │ ├── Constants.kt │ │ │ ├── Extensions.kt │ │ │ ├── Resource.kt │ │ │ └── ViewBindingExtensions.kt │ │ │ ├── data │ │ │ ├── model │ │ │ │ ├── AddToCartRequest.kt │ │ │ │ ├── CRUDResponse.kt │ │ │ │ ├── ClearCartRequest.kt │ │ │ │ ├── DeleteFromCartRequest.kt │ │ │ │ ├── GetProductDetailResponse.kt │ │ │ │ ├── GetProductsResponse.kt │ │ │ │ ├── Product.kt │ │ │ │ ├── ProductEntity.kt │ │ │ │ └── ProductUI.kt │ │ │ ├── repository │ │ │ │ └── ProductRepository.kt │ │ │ └── source │ │ │ │ ├── local │ │ │ │ ├── ProductDao.kt │ │ │ │ └── ProductRoomDB.kt │ │ │ │ └── remote │ │ │ │ └── ProductService.kt │ │ │ ├── di │ │ │ ├── NetworkModule.kt │ │ │ ├── RepositoryModule.kt │ │ │ └── RoomDBModule.kt │ │ │ └── ui │ │ │ ├── anasayfa │ │ │ ├── HomeFragment.kt │ │ │ ├── HomeViewModel.kt │ │ │ ├── ProductAdapter.kt │ │ │ └── SalesProductAdapter.kt │ │ │ ├── arama │ │ │ ├── SearchAdapter.kt │ │ │ ├── SearchFragment.kt │ │ │ └── SearchViewModel.kt │ │ │ ├── detay │ │ │ ├── ProductDetailFragment.kt │ │ │ └── ProductDetailViewModel.kt │ │ │ ├── favori │ │ │ ├── FavoriteAdapter.kt │ │ │ ├── FavoriteFragment.kt │ │ │ └── FavoriteViewModel.kt │ │ │ ├── giris │ │ │ └── SignInFragment.kt │ │ │ ├── kayit │ │ │ └── SignUpFragment.kt │ │ │ ├── odeme │ │ │ ├── PaymentFragment.kt │ │ │ └── ResultFragment.kt │ │ │ ├── profil │ │ │ ├── ProfileFragment.kt │ │ │ └── ProfileViewModel.kt │ │ │ ├── sepet │ │ │ ├── CartFragment.kt │ │ │ ├── CartProductsAdapter.kt │ │ │ └── CartViewModel.kt │ │ │ ├── splash │ │ │ └── SplashFragment.kt │ │ │ └── viewmodel │ │ │ └── BaseViewModel.kt │ └── res │ │ ├── drawable │ │ ├── avatarboy.png │ │ ├── avatargirl.png │ │ ├── back.jpg │ │ ├── backg.jpg │ │ ├── baseline_add_24.xml │ │ ├── baseline_add_circle_24.xml │ │ ├── baseline_arrow_circle_left_24.xml │ │ ├── baseline_delete_24.xml │ │ ├── baseline_favorite_24.xml │ │ ├── baseline_home_24.xml │ │ ├── baseline_indeterminate_check_box_24.xml │ │ ├── baseline_keyboard_double_arrow_left_24.xml │ │ ├── baseline_person_24.xml │ │ ├── baseline_shopping_cart_24.xml │ │ ├── baseline_youtube_searched_for_24.xml │ │ ├── bg_rectangle.xml │ │ ├── cargo.gif │ │ ├── cartt_background.xml │ │ ├── cartt_foreground.xml │ │ ├── file.png │ │ ├── ic_calendar.xml │ │ ├── ic_card.xml │ │ ├── ic_cart.xml │ │ ├── ic_delete.xml │ │ ├── ic_error.xml │ │ ├── ic_favorite.xml │ │ ├── ic_favorite_white.xml │ │ ├── ic_home.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_launcher_foreground.xml │ │ ├── ic_lock.xml │ │ ├── ic_payment.xml │ │ ├── ic_profile.xml │ │ └── splas.jpg │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── fragment_cart.xml │ │ ├── fragment_favorite.xml │ │ ├── fragment_home.xml │ │ ├── fragment_payment.xml │ │ ├── fragment_product_detail.xml │ │ ├── fragment_profile.xml │ │ ├── fragment_result.xml │ │ ├── fragment_search.xml │ │ ├── fragment_sign_in.xml │ │ ├── fragment_sign_up.xml │ │ ├── fragment_splash.xml │ │ ├── item_cart_product.xml │ │ ├── item_favorite.xml │ │ ├── item_product.xml │ │ ├── item_sales_product.xml │ │ └── item_search.xml │ │ ├── menu │ │ ├── bottom_menu.xml │ │ └── menu_toolbar.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── cartt.xml │ │ ├── cartt_round.xml │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── app_icon.png │ │ ├── app_icond.png │ │ ├── app_icons.png │ │ ├── cartt.png │ │ ├── cartt_foreground.png │ │ ├── cartt_round.png │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ └── iconagapp.png │ │ ├── mipmap-mdpi │ │ ├── app_icon.png │ │ ├── app_icond.png │ │ ├── app_icons.png │ │ ├── cartt.png │ │ ├── cartt_foreground.png │ │ ├── cartt_round.png │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ └── iconagapp.png │ │ ├── mipmap-xhdpi │ │ ├── app_icon.png │ │ ├── app_icond.png │ │ ├── app_icons.png │ │ ├── cartt.png │ │ ├── cartt_foreground.png │ │ ├── cartt_round.png │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ └── iconagapp.png │ │ ├── mipmap-xxhdpi │ │ ├── app_icon.png │ │ ├── app_icond.png │ │ ├── app_icons.png │ │ ├── cartt.png │ │ ├── cartt_foreground.png │ │ ├── cartt_round.png │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ └── iconagapp.png │ │ ├── mipmap-xxxhdpi │ │ ├── app_icon.png │ │ ├── app_icond.png │ │ ├── app_icons.png │ │ ├── cartt.png │ │ ├── cartt_foreground.png │ │ ├── cartt_round.png │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ └── iconagapp.png │ │ ├── navigation │ │ └── nav_graph.xml │ │ ├── values-night │ │ └── themes.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ └── data_extraction_rules.xml │ └── test │ └── java │ └── com │ └── adilegungor │ └── gungorecommerce │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GungorEcommerce 2 | Final assignment | Android Programming with Kotlin Academy - Sisterslab 3 | 4 | ## Açıklama: 5 | Yeni mimariler, eksik kullanımlar veya herhangi bir öneriniz için bana profilimdeki adreslerden ulaşmanız mümkün. Mutlu kodlamalar! 6 | 7 | ## Özellikler 8 | 9 | Uygulama aşağıdaki temel özelliklere sahiptir: 10 | 11 | - **MVVM (Model-View-ViewModel)**: Uygulama, Model-View-ViewModel tasarım deseni kullanılarak geliştirilmiştir. Bu, uygulamanın daha iyi modüler ve bakımı kolay bir şekilde oluşturulmasına yardımcı olur. 12 | 13 | - **Hilt**: Hilt, bağımlılık enjeksiyonu için kullanılan bir kütüphanedir. Bu sayede uygulamada bağımlılıkların yönetimi kolaylaşır. 14 | 15 | - **Coroutines**: Kotlin Coroutines, uygulamanın asenkron işlemlerini kolayca yönetmesine yardımcı olan bir özelliktir. Bu, arka planda ağ istekleri ve uzun süreli işlemler için idealdir. 16 | 17 | - **Navigation Component**: Navigation Component, uygulamanın gezinme (navigation) mantığını basitleştirmek için kullanılır. Bu sayede farklı ekranlar arasında geçiş yapmak daha kolay hale gelir. 18 | 19 | - **Retrofit**: Retrofit, RESTful API'lar ile iletişim kurmak için kullanılan bir HTTP istemcisidir. Uygulama, bu kütüphane aracılığıyla API verilerini alır. 20 | 21 | - **Room & Shared Preferences**: Room, yerel veritabanı işlemleri için kullanılırken, Shared Preferences küçük verilerin depolanmasında kullanılır. Bu sayede verilerin depolanması ve erişimi daha kolaydır. 22 | 23 | - **FirebaseAuth**: Firebase Authentication, kullanıcıların kayıt olması ve giriş yapması için kullanılır. Bu sayede kullanıcı yönetimi sağlanır. 24 | 25 | - **Glide**: Glide, görüntülerin yüklenmesi ve görüntülenmesi için kullanılan bir kütüphanedir. Bu, ürün resimlerinin ve kullanıcı profil resimlerinin gösterilmesinde kullanılır. 26 | 27 | - **Chucker**: Chucker, ağ isteklerini ve yanıtlarını izlemek ve hata ayıklamak için kullanılır. Bu sayede ağ isteklerinin izlenmesi ve hata ayıklanması daha kolay hale gelir. 28 | 29 | ## Kullanım 30 | 31 | Uygulamanın kullanımı oldukça basittir: 32 | 33 | 1. Uygulamayı cihazınıza yükleyin. 34 | 2. Kayıt olun veya giriş yapın. 35 | 3. Ürünleri kategorilere göre göz atın ve arama yapın. 36 | 4. Ürünleri sepetinize ekleyin ve favori ürünlerinizi listenize ekleyin. 37 | 5. Ödeme sayfasına giderek alışverişi tamamlayın. 38 | 6. Sonuçlar sayfasında sipariş detaylarını görüntüleyin. 39 | 7. Profil sayfasında kullanıcı bilgilerinizi düzenleyin. 40 | 41 | ## Kurulum 42 | 43 | Uygulamanın yerel olarak çalıştırılması için aşağıdaki adımları takip edebilirsiniz: 44 | 45 | 1. Depoyu klonlayın. 46 | 2. Android Studio veya başka bir uygun IDE kullanarak projeyi açın. 47 | 3. API anahtarları, Firebase yapılandırmaları ve diğer gerekli ayarları yapılandırın. 48 | 4. Uygulamayı bir Android cihazı veya emülatörü üzerinde çalıştırın. 49 | 50 | Uygulamayı geliştirmek veya özelleştirmek için dökümantasyon ve kaynak kodu inceleyebilirsiniz. 51 | 52 | ## Modül 53 | Proje akışı ve modüllerden kısaca bahsettiğim şu mini yazıya bakın lütfen. 54 | https://adilegungor.medium.com/e-ticaret-uygulamas%C4%B1-ba078fec348d 55 | 56 | ## uygulama demo: 57 | 58 | https://github.com/Adl1coder/GungorEcommerce/assets/93915867/58ef3023-afd1-4123-acdf-19a95992ae78 59 | 60 | ## Sunum: 61 | https://www.canva.com/design/DAFv5_8tzZs/olTATfYxKP9uBoToJ1YFRg/edit?utm_content=DAFv5_8tzZs&utm_campaign=designshare&utm_medium=link2&utm_source=sharebutton 62 | 63 | 64 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'org.jetbrains.kotlin.android' 4 | id 'androidx.navigation.safeargs' 5 | id("com.google.gms.google-services") 6 | id("kotlin-parcelize") 7 | id 'kotlin-kapt' 8 | id 'dagger.hilt.android.plugin' 9 | } 10 | 11 | android { 12 | namespace 'com.adilegungor.gungorecommerce' 13 | compileSdk 34 14 | 15 | defaultConfig { 16 | applicationId "com.adilegungor.gungorecommerce" 17 | minSdk 24 18 | targetSdk 34 19 | versionCode 1 20 | versionName "1.0" 21 | 22 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 23 | } 24 | buildFeatures { 25 | viewBinding true 26 | } 27 | buildTypes { 28 | release { 29 | minifyEnabled false 30 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 31 | } 32 | } 33 | compileOptions { 34 | sourceCompatibility JavaVersion.VERSION_17 35 | targetCompatibility JavaVersion.VERSION_17 36 | } 37 | kotlinOptions { 38 | jvmTarget = '17' 39 | } 40 | } 41 | 42 | dependencies { 43 | implementation 'androidx.core:core-ktx:1.12.0' 44 | implementation 'androidx.appcompat:appcompat:1.6.1' 45 | implementation 'com.google.android.material:material:1.9.0' 46 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 47 | testImplementation 'junit:junit:4.13.2' 48 | androidTestImplementation 'androidx.test.ext:junit:1.1.5' 49 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' 50 | //gezinme işlemleri 51 | implementation "androidx.navigation:navigation-fragment-ktx:2.7.3" 52 | //gezinme düğmeleri 53 | implementation "androidx.navigation:navigation-ui-ktx:2.7.3" 54 | 55 | //restful api ile iletişim 56 | implementation "com.squareup.retrofit2:retrofit:2.9.0" 57 | 58 | //Json verilerini nesnelere dönüştürür 59 | implementation "com.squareup.retrofit2:converter-gson:2.9.0" 60 | 61 | //web veya localde alınan verilerin yüklenmesi 62 | implementation "com.github.bumptech.glide:glide:4.15.1" 63 | 64 | //yuvarlatılmış köşeli arayüz için 65 | implementation 'com.github.zladnrms:RoundableLayout:1.1.4' 66 | 67 | //ağ trafiğini izlemek ve hata ayıklamak için kullanılır 68 | implementation "com.github.chuckerteam.chucker:library:4.0.0" 69 | 70 | //bağımlılıkları enjekte eder 71 | implementation 'com.google.dagger:hilt-android:2.47' 72 | //hiltin kodlarını işler 73 | kapt 'com.google.dagger:hilt-compiler:2.47' 74 | 75 | //viewModel:ui ile veri arasındaki bağlantı 76 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2" 77 | 78 | //liveData: veri değşimini izler ve ui'a aktarır 79 | implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.2" 80 | 81 | //Coroutines: arka plan işlemleri ve çoklu iş parçacıkları 82 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1") 83 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1") 84 | 85 | //Glide 86 | implementation 'com.github.bumptech.glide:glide:4.15.1' 87 | annotationProcessor 'com.github.bumptech.glide:compiler:4.14.2' 88 | 89 | 90 | implementation(platform("com.google.firebase:firebase-bom:32.2.2")) 91 | //firebase auth: kullanıcı kimlik doğrulama 92 | implementation("com.google.firebase:firebase-auth-ktx") 93 | //firebase firestore: db ve clloud storage 94 | implementation("com.google.firebase:firebase-firestore-ktx") 95 | 96 | //SQL Lite veritabanlarına erişim 97 | implementation "androidx.room:room-runtime:2.5.2" 98 | kapt "androidx.room:room-compiler:2.5.2" 99 | implementation "androidx.room:room-ktx:2.5.2" 100 | 101 | // GIF yüklemek ve göstermek için 102 | implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.22' 103 | 104 | 105 | } -------------------------------------------------------------------------------- /app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "1040336219074", 4 | "firebase_url": "https://personelkaydet-b1b25-default-rtdb.firebaseio.com", 5 | "project_id": "personelkaydet-b1b25", 6 | "storage_bucket": "personelkaydet-b1b25.appspot.com" 7 | }, 8 | "client": [ 9 | { 10 | "client_info": { 11 | "mobilesdk_app_id": "1:1040336219074:android:41244fc438fa356a29c45b", 12 | "android_client_info": { 13 | "package_name": "com.adilegungor.gungorecommerce" 14 | } 15 | }, 16 | "oauth_client": [ 17 | { 18 | "client_id": "1040336219074-qc522fe5naaf7csa4mrfbtdlqq81ta7l.apps.googleusercontent.com", 19 | "client_type": 3 20 | } 21 | ], 22 | "api_key": [ 23 | { 24 | "current_key": "AIzaSyABPWPT5_WByX1iDXA_ORbObEF1BlU83sk" 25 | } 26 | ], 27 | "services": { 28 | "appinvite_service": { 29 | "other_platform_oauth_client": [ 30 | { 31 | "client_id": "1040336219074-qc522fe5naaf7csa4mrfbtdlqq81ta7l.apps.googleusercontent.com", 32 | "client_type": 3 33 | } 34 | ] 35 | } 36 | } 37 | } 38 | ], 39 | "configuration_version": "1" 40 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/adilegungor/gungorecommerce/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.adilegungor.gungorecommerce", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/cartt-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Adl1coder/GungorEcommerce/c1b4fdc795510031dad544d970e179709f9d8182/app/src/main/cartt-playstore.png -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Adl1coder/GungorEcommerce/c1b4fdc795510031dad544d970e179709f9d8182/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/MainApplication.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor 2 | import android.app.Application 3 | import dagger.hilt.android.HiltAndroidApp 4 | 5 | @HiltAndroidApp 6 | class MainApplication : Application() -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.navigation.fragment.NavHostFragment 6 | import androidx.navigation.ui.NavigationUI 7 | import com.adilegungor.gungorecommerce.R 8 | import com.adilegungor.gungorecommerce.common.viewBinding 9 | import com.adilegungor.gungorecommerce.databinding.ActivityMainBinding 10 | import dagger.hilt.android.AndroidEntryPoint 11 | 12 | @AndroidEntryPoint 13 | class MainActivity : AppCompatActivity() { 14 | 15 | private val binding by viewBinding(ActivityMainBinding::inflate) 16 | 17 | override fun onCreate(savedInstanceState: Bundle?) { 18 | super.onCreate(savedInstanceState) 19 | 20 | with(binding) { 21 | val navHostFragment = supportFragmentManager.findFragmentById(R.id.fragmentContainerView) as NavHostFragment 22 | NavigationUI.setupWithNavController(bottomNavigationView, navHostFragment.navController) 23 | } 24 | 25 | setContentView(binding.root) 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/common/Constants.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.common 2 | 3 | object Constants { 4 | //temel url 5 | const val BASE_URL = "https://api.canerture.com/ecommerce/" 6 | 7 | object Endpoint { 8 | //istekleri getiren endpoint noktalarımız 9 | const val ADD_CART_PRODUCTS = "add_to_cart.php" 10 | const val DELETE_CART_PRODUCTS = "delete_from_cart.php" 11 | const val GET_PRODUCTS = "get_products.php" 12 | const val GET_PRODUCT_DETAIL = "get_product_detail.php" 13 | const val GET_SALE_PRODUCTS = "get_sale_products.php"//indirimdekiler 14 | const val CLEAR_CART_PRODUCTS = "clear_cart.php" 15 | const val GET_PRODUCTS_BY_CATEGORY = "get_products_by_category.php" 16 | const val GET_CART_PRODUCTS = "get_cart_products.php" 17 | const val GET_SEARCH_PRODUCT = "search_product.php" 18 | 19 | 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/common/Extensions.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.common 2 | 3 | import android.widget.ImageView 4 | import com.bumptech.glide.Glide 5 | import android.view.View 6 | 7 | // Resim yükleme işlevi 8 | fun ImageView.loadImage(url: String?) { 9 | Glide.with(this.context).load(url).into(this) 10 | } 11 | 12 | // Görünürlüğü gizleme işlevi 13 | fun View.gone() { 14 | visibility = View.GONE // View'ı görünmez yapar 15 | } 16 | 17 | // Görünürlüğü gösterme işlevi 18 | fun View.visible() { 19 | visibility = View.VISIBLE // View'ı görünür yapar 20 | //buraya yeni eklemeler yapılabilir kod geliştirilebilir 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/common/Resource.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.common 2 | 3 | // kaynak sınıfı 4 | 5 | // veri almayı kolaylaştırma 6 | 7 | sealed class Resource { 8 | // Başarılı durum - alt sınıf 9 | data class Success(val data: T) : Resource() 10 | 11 | // Hata durumu - alt sınıf 12 | data class Error(val throwable: Throwable) : Resource() 13 | } 14 | /*val result: Resource = when (someCondition) { 15 | true -> Resource.Success("Başarılı sonuç") 16 | false -> Resource.Error(Exception("Hata oluştu")) 17 | } 18 | */ 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/common/ViewBindingExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.common 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import androidx.appcompat.app.AppCompatActivity 6 | import androidx.fragment.app.Fragment 7 | import androidx.lifecycle.DefaultLifecycleObserver 8 | import androidx.lifecycle.Lifecycle 9 | import androidx.lifecycle.LifecycleOwner 10 | import androidx.viewbinding.ViewBinding 11 | import kotlin.properties.ReadOnlyProperty 12 | import kotlin.reflect.KProperty 13 | 14 | // AppCompatActivity için bir extension fonksiyonu. 15 | // Verilen bir LayoutInflater işlevi kullanarak ViewBinding oluşturur. 16 | inline fun AppCompatActivity.viewBinding( 17 | crossinline factory: (LayoutInflater) -> T 18 | //lazy: viewbindng nesnesini sadece ilk kez ihtiyaç olunca oluştur. 19 | ) = lazy(LazyThreadSafetyMode.NONE) { // bu işlemi tek bir iş parçacığında gerçekleştir 20 | factory(layoutInflater) 21 | } 22 | 23 | // Fragment için bir extension fonksiyonu. ViewBinding'i oluştururken bir view oluşturucu işlevini kullanır. 24 | fun Fragment.viewBinding(factory: (View) -> T): ReadOnlyProperty = 25 | object : ReadOnlyProperty, DefaultLifecycleObserver { 26 | 27 | private var binding: T? = null 28 | 29 | // ViewBinding'i temsil eden değeri döndürür. Eğer henüz oluşturulmamışsa oluşturur. 30 | override fun getValue(thisRef: Fragment, property: KProperty<*>): T = 31 | binding ?: factory(requireView()).also { 32 | if (viewLifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) { 33 | // ViewLifecycleOwner'ın yaşam döngüsünü izler ve bağlantıyı korur. 34 | viewLifecycleOwner.lifecycle.addObserver(this) 35 | binding = it 36 | } 37 | } 38 | 39 | // Fragment öldüğünde, ViewBinding'i temizler. 40 | override fun onDestroy(owner: LifecycleOwner) { 41 | binding = null 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/data/model/AddToCartRequest.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.data.model 2 | //JSON formatına göre classlar. 3 | data class AddToCartRequest( 4 | val userId: String, 5 | val productId: Int 6 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/data/model/CRUDResponse.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.data.model 2 | //CRUD işl. için ol. cl 3 | data class CRUDResponse( 4 | val status: Int?, 5 | val message: String?, 6 | ) 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/data/model/ClearCartRequest.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.data.model 2 | //sepeeti temizle-kull id. gr 3 | data class ClearCartRequest( 4 | val userId: String, 5 | ) 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/data/model/DeleteFromCartRequest.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.data.model 2 | //id il sl 3 | data class DeleteFromCartRequest ( 4 | val id: Int 5 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/data/model/GetProductDetailResponse.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.data.model 2 | //seçilen ürün detayı 3 | data class GetProductDetailResponse( 4 | val status: Int?, 5 | val message: String?, 6 | val product: Product? //nesne 7 | ) 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/data/model/GetProductsResponse.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.data.model 2 | 3 | data class GetProductsResponse( 4 | val status: Int?, 5 | val message: String?, 6 | val products: List?//tüm liste(ürün) 7 | ) 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/data/model/Product.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.data.model 2 | 3 | import android.os.Parcelable 4 | import kotlinx.parcelize.Parcelize 5 | //dönüşüm 6 | @Parcelize 7 | data class Product( 8 | val id: Int?, 9 | val title: String?, 10 | val price: Double?, 11 | val salePrice: Double?, 12 | val description: String?, 13 | val category: String?, 14 | val imageOne: String?, 15 | val imageTwo: String?, 16 | val imageThree: String?, 17 | val rate: Double?, 18 | val count: Int?, 19 | val saleState: Boolean? //indirim durumu 20 | // Product sınıfını ProductUI sınıfına dönüştüren işlev 21 | ): Parcelable { 22 | //verileri kullanıcı aryüzünde göstermek için 23 | fun mapToProductUI(): ProductUI { 24 | return ProductUI( 25 | id = id ?: 1, 26 | title = title.orEmpty(), 27 | price = price ?: 0.0, 28 | salePrice = salePrice ?: 0.0, 29 | description = description.orEmpty(), 30 | category = category.orEmpty(), 31 | imageOne = imageOne.orEmpty(), 32 | imageTwo = imageTwo.orEmpty(), 33 | imageThree = imageThree.orEmpty(), 34 | rate = rate ?: 0.0, 35 | count = count ?: 1, 36 | saleState = saleState ?: false 37 | ) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/data/model/ProductEntity.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.data.model 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | // yerel veritabanı kullanımı 7 | @Entity(tableName = "cart_products") // 8 | data class ProductEntity( 9 | //autogenerate: otomatik artış 10 | @PrimaryKey(autoGenerate = true) 11 | @ColumnInfo(name = "id") 12 | val id: Int?, 13 | 14 | @ColumnInfo(name = "title") 15 | val title: String?, 16 | 17 | @ColumnInfo(name = "price") 18 | val price: Double?, 19 | 20 | @ColumnInfo(name = "salePrice") 21 | val salePrice: Double?, 22 | 23 | @ColumnInfo(name = "description") 24 | val description: String?, 25 | 26 | @ColumnInfo(name = "category") 27 | val category: String?, 28 | 29 | @ColumnInfo(name = "imageOne") 30 | val imageOne: String?, 31 | 32 | @ColumnInfo(name = "imageTwo") 33 | val imageTwo: String?, 34 | 35 | @ColumnInfo(name = "imageThree") 36 | val imageThree: String?, 37 | 38 | @ColumnInfo(name = "rate") 39 | val rate: Double?, 40 | 41 | @ColumnInfo(name = "count") 42 | val count: Int?, 43 | 44 | @ColumnInfo(name = "saleState") 45 | val saleState: Boolean? 46 | ) { 47 | //bu fonksiyon product entitiy i product ui a çeviriyor. 48 | fun mapToProductUI(): ProductUI { 49 | return ProductUI( 50 | id = id ?: 1, 51 | title = title.orEmpty(), 52 | price = price ?: 0.0, 53 | salePrice = salePrice ?: 0.0, 54 | description = description.orEmpty(), 55 | category = category.orEmpty(), 56 | imageOne = imageOne.orEmpty(), 57 | imageTwo = imageTwo.orEmpty(), 58 | imageThree = imageThree.orEmpty(), 59 | rate = rate ?: 0.0, 60 | count = count ?: 1, 61 | saleState = saleState ?: false 62 | ) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/data/model/ProductUI.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.data.model 2 | 3 | data class ProductUI( 4 | val id: Int, 5 | val title: String, 6 | val price: Double, 7 | val salePrice: Double, 8 | val description: String, 9 | val category: String, 10 | val imageOne: String, 11 | val imageTwo: String, 12 | val imageThree: String, 13 | val rate: Double, 14 | val count: Int, 15 | val saleState: Boolean 16 | ) { 17 | //bu fonksiyon da product ui deki yani kull. dan ald. verileri product entitye e çeviriyor, 18 | // işleme kolaylığı 19 | fun mapToProductEntity(): ProductEntity { 20 | return ProductEntity( 21 | id = id, 22 | title = title, 23 | price = price, 24 | salePrice = salePrice, 25 | description = description, 26 | category = category, 27 | imageOne = imageOne, 28 | imageTwo = imageTwo, 29 | imageThree = imageThree, 30 | rate = rate, 31 | count = count, 32 | saleState = saleState 33 | ) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/data/repository/ProductRepository.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.data.repository 2 | 3 | import com.adilegungor.gungorecommerce.common.Resource 4 | import com.adilegungor.gungorecommerce.data.model.AddToCartRequest 5 | import com.adilegungor.gungorecommerce.data.model.CRUDResponse 6 | import com.adilegungor.gungorecommerce.data.model.ClearCartRequest 7 | import com.adilegungor.gungorecommerce.data.model.DeleteFromCartRequest 8 | import com.adilegungor.gungorecommerce.data.model.ProductUI 9 | import com.adilegungor.gungorecommerce.data.source.local.ProductDao 10 | import com.adilegungor.gungorecommerce.data.source.remote.ProductService 11 | import javax.inject.Inject 12 | //try-catch blokları 13 | //response and process 14 | //hilt 15 | class ProductRepository @Inject constructor( 16 | //uzak ve yerel veritabanı erişimi 17 | private val productService: ProductService, 18 | private val productDao: ProductDao, 19 | ) { 20 | 21 | suspend fun getProducts(): Resource> { 22 | //uzaktan veri kayn. erişir verileri dönüştürür(product ui obj)sonra resource u kull. 23 | return try { 24 | Resource.Success(productService.getProducts().products?.map { it.mapToProductUI() }.orEmpty()) 25 | } catch (e: Exception) { 26 | Resource.Error(e) 27 | } 28 | } 29 | //tüm ürünler 30 | suspend fun getSaleProducts(): Resource> { 31 | return try { 32 | Resource.Success(productService.getSaleProducts().products?.map { it.mapToProductUI() }.orEmpty()) 33 | } catch (e: Exception) { 34 | Resource.Error(e) 35 | } 36 | } 37 | //belli kategoriye aiy ürünler 38 | suspend fun getProductsByCategory(category: String): Resource> { 39 | return try { 40 | Resource.Success(productService.getProductsByCategory(category).products?.map { it.mapToProductUI() }.orEmpty()) 41 | } catch (e: Exception) { 42 | Resource.Error(e) 43 | } 44 | } 45 | //ürün detay 46 | suspend fun getProductsDetail(id: Int): Resource { 47 | return try { 48 | productService.getProductDetail(id).product?.let { 49 | Resource.Success(it.mapToProductUI()) 50 | } ?: kotlin.run { 51 | Resource.Error(Exception("Product not found")) 52 | } 53 | } catch (e: Exception) { 54 | Resource.Error(e) 55 | } 56 | } 57 | 58 | suspend fun getSearchProduct(query: String): Resource> { 59 | return try { 60 | val response = productService.getSearchProduct(query) 61 | Resource.Success(response.products?.map { it.mapToProductUI() }.orEmpty()) 62 | } catch (e: Exception) { 63 | Resource.Error(e) 64 | } 65 | } 66 | 67 | suspend fun deleteProductFromFav(product: ProductUI) { 68 | productDao.deleteProduct(product.mapToProductEntity()) 69 | } 70 | 71 | suspend fun getFavProducts(): Resource> { 72 | return try { 73 | Resource.Success(productDao.getProducts().map { 74 | it.mapToProductUI() 75 | }) 76 | } catch (e: Exception) { 77 | Resource.Error(e) 78 | } 79 | } 80 | //favoriye ekle 81 | suspend fun addProductToFav(product: ProductUI) { 82 | productDao.addProduct(product.mapToProductEntity()) 83 | } 84 | //sepete ekle 85 | suspend fun addProductToCart(addToCartRequest: AddToCartRequest): CRUDResponse { 86 | return productService.addProductToCart(addToCartRequest) 87 | } 88 | //sepettekşleri listele 89 | suspend fun getCartProduct(userId: String): Resource> { 90 | return try { 91 | val response = productService.getCartProducts(userId) 92 | Resource.Success(response.products?.map { it.mapToProductUI() }.orEmpty()) 93 | } catch (e: Exception) { 94 | Resource.Error(e) 95 | } 96 | } 97 | //sepetten ürün sil 98 | suspend fun deleteProductFromCart(request: DeleteFromCartRequest): CRUDResponse { 99 | return productService.deleteProductFromCart(request) 100 | } 101 | //sepeti boşalt 102 | //product service ile uzak veri kaynağına req atar ve sonucu resource formda döndürür. 103 | suspend fun clearProductFromCart(request: ClearCartRequest): Resource { 104 | return try { 105 | val response = productService.clearProductFromCart(request) 106 | Resource.Success(response) 107 | } catch (e: Exception) { 108 | Resource.Error(e) 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/data/source/local/ProductDao.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.data.source.local 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Delete 5 | import androidx.room.Insert 6 | import androidx.room.OnConflictStrategy 7 | import androidx.room.Query 8 | import com.adilegungor.gungorecommerce.data.model.ProductEntity 9 | 10 | 11 | @Dao //sınıf:roomdao 12 | interface ProductDao { 13 | 14 | @Query("SELECT * FROM cart_products")//get all pro 15 | suspend fun getProducts(): List 16 | //product entity veri listesi 17 | //askıya alınır işlem- tüm ürünleri al. 18 | 19 | @Insert(onConflict = OnConflictStrategy.REPLACE) 20 | //onflict:çakışma=>yeniyi ekle 21 | suspend fun addProduct(product: ProductEntity) 22 | 23 | @Delete 24 | suspend fun deleteProduct(product: ProductEntity) 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/data/source/local/ProductRoomDB.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.data.source.local 2 | 3 | 4 | import androidx.room.Database 5 | import androidx.room.RoomDatabase 6 | import com.adilegungor.gungorecommerce.data.model.ProductEntity 7 | 8 | //favoriler için @Database(entities = [ProductEntity::class, ProductEntity::class], version = 1) 9 | //veritabanısınıf:local dbleri yönet. 10 | //[ProductEntity::class] hangi db kull. tanım 11 | @Database(entities = [ProductEntity::class], version = 1) 12 | //db değiş. sürüm upd. et. vers. 13 | 14 | //roomdb local db işl. için. 15 | //kalıtım 16 | abstract class ProductRoomDB : RoomDatabase(){ 17 | 18 | abstract fun productsDao(): ProductDao 19 | //db işlemlerinin nin olduğu daoya erişim 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/data/source/remote/ProductService.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.data.source.remote 2 | 3 | import com.adilegungor.gungorecommerce.common.Constants.Endpoint.ADD_CART_PRODUCTS 4 | import com.adilegungor.gungorecommerce.common.Constants.Endpoint.CLEAR_CART_PRODUCTS 5 | import com.adilegungor.gungorecommerce.common.Constants.Endpoint.DELETE_CART_PRODUCTS 6 | import com.adilegungor.gungorecommerce.common.Constants.Endpoint.GET_CART_PRODUCTS 7 | import com.adilegungor.gungorecommerce.common.Constants.Endpoint.GET_PRODUCTS 8 | import com.adilegungor.gungorecommerce.common.Constants.Endpoint.GET_PRODUCTS_BY_CATEGORY 9 | import com.adilegungor.gungorecommerce.common.Constants.Endpoint.GET_PRODUCT_DETAIL 10 | import com.adilegungor.gungorecommerce.common.Constants.Endpoint.GET_SALE_PRODUCTS 11 | import com.adilegungor.gungorecommerce.common.Constants.Endpoint.GET_SEARCH_PRODUCT 12 | import com.adilegungor.gungorecommerce.data.model.AddToCartRequest 13 | import com.adilegungor.gungorecommerce.data.model.CRUDResponse 14 | import com.adilegungor.gungorecommerce.data.model.ClearCartRequest 15 | import com.adilegungor.gungorecommerce.data.model.DeleteFromCartRequest 16 | import com.adilegungor.gungorecommerce.data.model.GetProductDetailResponse 17 | import com.adilegungor.gungorecommerce.data.model.GetProductsResponse 18 | import retrofit2.http.Body 19 | import retrofit2.http.GET 20 | import retrofit2.http.POST 21 | import retrofit2.http.Query 22 | //http istekleri 23 | interface ProductService { 24 | @GET(GET_PRODUCTS) 25 | 26 | //retrofitin coroutine desteği 27 | suspend fun getProducts(): GetProductsResponse 28 | 29 | @GET(GET_PRODUCT_DETAIL) 30 | suspend fun getProductDetail(@Query("id") id: Int): GetProductDetailResponse 31 | 32 | @GET(GET_SALE_PRODUCTS) 33 | suspend fun getSaleProducts(): GetProductsResponse 34 | 35 | @GET(GET_PRODUCTS_BY_CATEGORY) 36 | suspend fun getProductsByCategory(@Query("category") categoryValue: String): GetProductsResponse 37 | 38 | @GET(GET_SEARCH_PRODUCT) 39 | suspend fun getSearchProduct(@Query("query") queryValue: String): GetProductsResponse 40 | 41 | @GET(GET_CART_PRODUCTS) 42 | suspend fun getCartProducts(@Query("userId") userId: String): GetProductsResponse 43 | 44 | @POST(ADD_CART_PRODUCTS) 45 | suspend fun addProductToCart(@Body request: AddToCartRequest): CRUDResponse 46 | 47 | @POST(DELETE_CART_PRODUCTS) 48 | suspend fun deleteProductFromCart(@Body request: DeleteFromCartRequest): CRUDResponse//yanıt 49 | //body type: veriyi gövdeye koy 50 | @POST(CLEAR_CART_PRODUCTS) 51 | suspend fun clearProductFromCart(@Body request: ClearCartRequest): CRUDResponse 52 | } -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/di/NetworkModule.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.di 2 | 3 | import android.content.Context 4 | import com.chuckerteam.chucker.api.ChuckerInterceptor 5 | import com.adilegungor.gungorecommerce.common.Constants.BASE_URL 6 | import com.adilegungor.gungorecommerce.data.source.remote.ProductService 7 | import dagger.Module 8 | import dagger.Provides 9 | import dagger.hilt.InstallIn 10 | import dagger.hilt.android.qualifiers.ApplicationContext 11 | import dagger.hilt.components.SingletonComponent 12 | import okhttp3.OkHttpClient 13 | import retrofit2.Retrofit 14 | import retrofit2.converter.gson.GsonConverterFactory 15 | import retrofit2.create 16 | import java.util.concurrent.TimeUnit 17 | import javax.inject.Singleton 18 | //dagger ile ağ işlemleri bağımlılıkları vs. 19 | //aş. etiketler modülün dagger olduğunu ve bağımlıkların singletona kurulcağını gösterir. isimden bariz. 20 | //yaşam döngüsü boyunca 1 kez kur bağ.ın yön. 21 | @Module 22 | @InstallIn(SingletonComponent::class) 23 | //hilt bağ. tems. eden modülümüz 24 | object NetworkModule { 25 | //zaman aşımı ağ isteklerinin ne kadar bekleyeceiği hk. 26 | private const val TIMEOUT = 60L 27 | 28 | @Provides 29 | @Singleton 30 | fun provideChuckerInterceptor(@ApplicationContext context:Context) = ChuckerInterceptor.Builder(context).build() 31 | //chucker ı burada kullandım. Ağ isteklerini uygulama çalışma esnasında izlemek için. 32 | @Provides 33 | @Singleton 34 | fun provideOkHttpClient(chuckerInterceptor: ChuckerInterceptor) = OkHttpClient.Builder().apply { 35 | addInterceptor { chain -> 36 | val originalRequest = chain.request() 37 | val modifiedRequest = originalRequest.newBuilder() 38 | .addHeader("store", "canerture") 39 | .build() 40 | chain.proceed(modifiedRequest) 41 | } 42 | //addıntercopter ile ağ isteklerine özel başlık ekkl. 43 | addInterceptor(chuckerInterceptor) 44 | 45 | //zaman aşımı süreleri belirleniyor 46 | readTimeout(TIMEOUT, TimeUnit.SECONDS) 47 | connectTimeout(TIMEOUT, TimeUnit.SECONDS) 48 | writeTimeout(TIMEOUT, TimeUnit.SECONDS) 49 | }.build() 50 | 51 | @Provides 52 | @Singleton 53 | 54 | //okhhttp ile retrofit istemcisi oluşturur ve gson ekler, gson eklenmiş: JSON verileri nesnelere çeviriyor 55 | fun provideRetrofit(okHttpClient: OkHttpClient) = Retrofit.Builder() 56 | .baseUrl(BASE_URL) 57 | .client(okHttpClient) 58 | .addConverterFactory(GsonConverterFactory.create()) 59 | .build() 60 | 61 | @Provides 62 | @Singleton 63 | fun provideService(retrofit: Retrofit) = retrofit.create() 64 | //api çağırmak için retrofit nesnesi kull. Product service oluşturuldu. 65 | } -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/di/RepositoryModule.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.di 2 | 3 | import com.adilegungor.gungorecommerce.data.repository.ProductRepository 4 | import com.adilegungor.gungorecommerce.data.source.local.ProductDao 5 | import com.adilegungor.gungorecommerce.data.source.remote.ProductService 6 | import dagger.Module 7 | import dagger.Provides 8 | import dagger.hilt.InstallIn 9 | import dagger.hilt.components.SingletonComponent 10 | import javax.inject.Singleton 11 | 12 | @Module 13 | @InstallIn(SingletonComponent::class) 14 | object RepositoryModule { 15 | 16 | @Provides 17 | @Singleton 18 | fun provideRepository(productService: ProductService, productDao: ProductDao) : ProductRepository = 19 | ProductRepository(productService, productDao) 20 | //uzak sunucudan veri çekmede retrofit servisini temsil eden nesne:product service 21 | //local db veri yönetimi sağlar: productdao obj. 22 | //ağ üzerinden veri çekme + yerel veri db ye bunları kaydetme: ProductRepository obj. 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/di/RoomDBModule.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.di 2 | 3 | import android.content.Context 4 | import androidx.room.Room 5 | import com.adilegungor.gungorecommerce.data.source.local.ProductRoomDB 6 | import dagger.Module 7 | import dagger.Provides 8 | import dagger.hilt.InstallIn 9 | import dagger.hilt.android.qualifiers.ApplicationContext 10 | import dagger.hilt.components.SingletonComponent 11 | import javax.inject.Singleton 12 | 13 | @Module 14 | @InstallIn(SingletonComponent::class) 15 | object RoomDBModule { 16 | 17 | @Provides 18 | @Singleton 19 | 20 | //aş. @ notasyonu uyg. genel bağlamını (context) alır ve db yi oluşturur. 21 | fun provideRoomDB(@ApplicationContext context: Context) = 22 | Room.databaseBuilder(context, ProductRoomDB::class.java, "product_room_db").build() 23 | //burada bir product roomdb oluşturuldu 24 | @Provides 25 | @Singleton 26 | 27 | //aş. fonk. roomdb den dao oluşt. ve döndürür. - dao locale erişir-db etkileşimi 28 | fun provideDao(roomDB: ProductRoomDB) = roomDB.productsDao() 29 | 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/ui/anasayfa/HomeFragment.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.ui.anasayfa 2 | 3 | import android.os.Bundle 4 | import android.view.MenuItem 5 | import android.view.View 6 | import androidx.appcompat.widget.Toolbar 7 | import androidx.fragment.app.Fragment 8 | import androidx.fragment.app.viewModels 9 | import androidx.navigation.fragment.findNavController 10 | import com.adilegungor.gungorecommerce.R 11 | import com.adilegungor.gungorecommerce.common.viewBinding 12 | import com.adilegungor.gungorecommerce.data.model.ProductUI 13 | import com.adilegungor.gungorecommerce.databinding.FragmentHomeBinding 14 | import com.google.android.material.bottomnavigation.BottomNavigationView 15 | import com.google.android.material.snackbar.Snackbar 16 | import dagger.hilt.android.AndroidEntryPoint 17 | //hilt usg 18 | @AndroidEntryPoint 19 | class HomeFragment : Fragment(R.layout.fragment_home), ProductAdapter.ProductListener, SalesProductAdapter.ProductListener { 20 | //viewbinding ile xml deki görünümlere erişim 21 | private val binding by viewBinding(FragmentHomeBinding::bind) 22 | 23 | private var bottomNavigationView: BottomNavigationView? = null 24 | private val productAdapter by lazy { ProductAdapter(this) } 25 | private val viewModel by viewModels() 26 | //indirimli ürünleri listeler, dinleyici kendisi 27 | private val salesProductAdapter by lazy { SalesProductAdapter(this) } 28 | 29 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 30 | super.onViewCreated(view, savedInstanceState) 31 | 32 | //visibility:görünürlük 33 | bottomNavigationView = getActivity()?.findViewById(R.id.bottomNavigationView); 34 | bottomNavigationView?.setVisibility(View.VISIBLE); 35 | with(binding) { 36 | rvAllProducts.adapter = productAdapter 37 | rvDiscountedProducts.adapter = salesProductAdapter 38 | radioGroup.setOnCheckedChangeListener { group, checkedId -> 39 | if (checkedId == R.id.rb_all) { 40 | viewModel.getProducts() 41 | } else { 42 | //kategoriye göre listeleme 43 | val category = when (checkedId) { 44 | R.id.rb_notebook -> "notebook" 45 | R.id.rbmntr -> "monitor" 46 | R.id.rbhdst->"headset" 47 | R.id.rbcnsl->"console" 48 | R.id.rbdsktop->"desktop" 49 | else -> "all" 50 | } 51 | viewModel.getProductsByCategory(category) 52 | } 53 | } 54 | 55 | 56 | 57 | toolbar.setOnMenuItemClickListener (object : MenuItem.OnMenuItemClickListener, 58 | Toolbar.OnMenuItemClickListener { 59 | override fun onMenuItemClick(item: MenuItem): Boolean { 60 | when (item.itemId) { 61 | R.id.action_profile -> { 62 | findNavController().navigate(HomeFragmentDirections.actionHomeFragmentToProfileFragment()) 63 | return true 64 | } 65 | } 66 | 67 | return false 68 | } 69 | }) 70 | } 71 | 72 | viewModel.getProducts() 73 | viewModel.getSaleProducts() 74 | 75 | observeData() 76 | } 77 | // verilerin ya dhata durumlarını gözlenmesi 78 | private fun observeData() = with(binding) { 79 | viewModel.homeState.observe(viewLifecycleOwner) { state -> 80 | when (state) { 81 | HomeState.Loading -> { 82 | progressBar2.visibility = View.VISIBLE 83 | } 84 | 85 | is HomeState.Data -> { 86 | progressBar2.visibility = View.GONE 87 | productAdapter.submitList(state.productsResponse) 88 | } 89 | 90 | is HomeState.Error -> { 91 | tvError.setText(state.throwable.message.orEmpty()) 92 | //veri gelince progress bar gizlenir. Recyclerviewe veri eklenir 93 | progressBar2.visibility = View.GONE 94 | rvAllProducts.visibility = View.GONE 95 | ivError.visibility = View.VISIBLE 96 | tvError.visibility = View.VISIBLE 97 | Snackbar.make(requireView(), state.throwable.message.orEmpty(), 1000).show() 98 | } 99 | } 100 | } 101 | 102 | viewModel.salesState.observe(viewLifecycleOwner) { state -> 103 | when (state) { 104 | SalesState.Loading -> { 105 | progressBar2.visibility = View.VISIBLE 106 | } 107 | 108 | is SalesState.Data -> { 109 | progressBar2.visibility = View.GONE 110 | salesProductAdapter.submitList(state.productsResponse) 111 | } 112 | 113 | is SalesState.Error -> { 114 | progressBar2.visibility = View.GONE 115 | Snackbar.make(requireView(), state.throwable.message.orEmpty(), 1000).show() 116 | } 117 | } 118 | } 119 | } 120 | 121 | //ürüne tıklayınca detaya git 122 | override fun onProductClick(id: Int) { 123 | val action = HomeFragmentDirections.actionHomeFragmentToProductDetailFragment(id) 124 | findNavController().navigate(action) 125 | } 126 | 127 | //fav a tıklayınca fav e ekle ve msg göster. 128 | override fun onFavoriteClick(product: ProductUI) { 129 | viewModel.addProductToFav(product) 130 | Snackbar.make(requireView(), "Favorilere Eklendi!", Snackbar.LENGTH_SHORT).show() 131 | } 132 | } -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/ui/anasayfa/HomeViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.ui.anasayfa 2 | 3 | import android.app.Application 4 | import androidx.lifecycle.LiveData 5 | import androidx.lifecycle.MutableLiveData 6 | import com.adilegungor.gungorecommerce.common.Resource 7 | import com.adilegungor.gungorecommerce.data.model.ProductUI 8 | import com.adilegungor.gungorecommerce.data.repository.ProductRepository 9 | import com.adilegungor.gungorecommerce.ui.viewmodel.BaseViewModel 10 | import dagger.hilt.android.lifecycle.HiltViewModel 11 | import kotlinx.coroutines.launch 12 | import javax.inject.Inject 13 | 14 | @HiltViewModel 15 | //kalıtım + repo ile verilere erişim 16 | class HomeViewModel 17 | @Inject constructor( 18 | private val productRepository: ProductRepository, application: Application 19 | ): BaseViewModel(application) { 20 | 21 | private var _homeState = MutableLiveData() 22 | val homeState: LiveData 23 | get() = _homeState 24 | 25 | private var _salesState = MutableLiveData() 26 | val salesState: LiveData 27 | get() = _salesState 28 | 29 | 30 | //başlatıcı 31 | init { 32 | } 33 | 34 | fun getProducts() { 35 | launch { 36 | _homeState.value = HomeState.Loading 37 | val result = productRepository.getProducts() 38 | 39 | when (result) { 40 | is Resource.Success -> { 41 | _homeState.value = HomeState.Data(result.data) 42 | } 43 | 44 | is Resource.Error -> { 45 | _homeState.value = HomeState.Error(result.throwable) 46 | } 47 | } 48 | } 49 | } 50 | 51 | fun getSaleProducts() { 52 | launch { 53 | _salesState.value = SalesState.Loading 54 | val result = productRepository.getSaleProducts() 55 | 56 | when (result) { 57 | is Resource.Success -> { 58 | _salesState.value = SalesState.Data(result.data) 59 | } 60 | 61 | is Resource.Error -> { 62 | _salesState.value = SalesState.Error(result.throwable) 63 | } 64 | } 65 | } 66 | } 67 | 68 | fun getProductsByCategory(category: String) { 69 | launch { 70 | _homeState.value = HomeState.Loading 71 | val result = productRepository.getProductsByCategory(category) 72 | 73 | when (result) { 74 | is Resource.Success -> { 75 | _homeState.value = HomeState.Data(result.data) 76 | } 77 | 78 | is Resource.Error -> { 79 | _homeState.value = HomeState.Error(result.throwable) 80 | } 81 | } 82 | } 83 | } 84 | 85 | fun addProductToFav(product: ProductUI) { 86 | launch { 87 | productRepository.addProductToFav(product) 88 | } 89 | } 90 | } 91 | 92 | sealed interface HomeState { 93 | object Loading: HomeState 94 | data class Data(val productsResponse: List): HomeState 95 | 96 | data class Error(val throwable: Throwable): HomeState 97 | } 98 | 99 | sealed interface SalesState { 100 | object Loading: SalesState 101 | data class Data(val productsResponse: List): SalesState 102 | 103 | data class Error(val throwable: Throwable): SalesState 104 | } -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/ui/anasayfa/ProductAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.ui.anasayfa 2 | 3 | import android.graphics.Paint 4 | import android.view.LayoutInflater 5 | import android.view.ViewGroup 6 | import androidx.core.view.isVisible 7 | import androidx.recyclerview.widget.DiffUtil 8 | import androidx.recyclerview.widget.ListAdapter 9 | import androidx.recyclerview.widget.RecyclerView 10 | import com.adilegungor.gungorecommerce.common.loadImage 11 | import com.adilegungor.gungorecommerce.data.model.ProductUI 12 | import com.adilegungor.gungorecommerce.databinding.ItemProductBinding 13 | 14 | class ProductAdapter ( 15 | private val productListener: ProductListener 16 | ) : ListAdapter(ProductDiffCallBack()) { 17 | 18 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductViewHolder = 19 | ProductViewHolder( 20 | ItemProductBinding.inflate(LayoutInflater.from(parent.context), parent, false), 21 | productListener 22 | ) 23 | 24 | override fun onBindViewHolder(holder: ProductViewHolder, position: Int) = 25 | holder.bind(getItem(position)) 26 | 27 | class ProductViewHolder( 28 | private val binding: ItemProductBinding, 29 | private val productListener: ProductListener 30 | ) : RecyclerView.ViewHolder(binding.root) { 31 | 32 | fun bind(product: ProductUI) = with(binding) { 33 | tvTitle.text = product.title 34 | tvDesc.text = product.description 35 | tvCategory.text = product.category 36 | 37 | imgProduct.loadImage(product.imageOne) 38 | 39 | if (product.saleState == true) { 40 | tvSalePrice.isVisible = true 41 | tvSalePrice.text = "${product.salePrice} ₺" 42 | tvPrice.text = "${product.price} ₺" 43 | tvPrice.paintFlags = tvPrice.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG 44 | } else { 45 | tvPrice.text = "${product.price} ₺" 46 | tvSalePrice.isVisible = false 47 | } 48 | 49 | imgFavorite.setOnClickListener { 50 | productListener.onFavoriteClick(product) 51 | } 52 | 53 | root.setOnClickListener { 54 | productListener.onProductClick(product.id) 55 | } 56 | 57 | 58 | } 59 | } 60 | 61 | class ProductDiffCallBack : DiffUtil.ItemCallback() { 62 | override fun areItemsTheSame(oldItem: ProductUI, newItem: ProductUI): Boolean { 63 | return oldItem.id == newItem.id 64 | } 65 | 66 | override fun areContentsTheSame(oldItem: ProductUI, newItem: ProductUI): Boolean { 67 | return oldItem == newItem 68 | } 69 | //ürün karş. 70 | } 71 | 72 | interface ProductListener { 73 | fun onProductClick(id: Int) 74 | fun onFavoriteClick(product: ProductUI) 75 | } 76 | 77 | } -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/ui/anasayfa/SalesProductAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.ui.anasayfa 2 | 3 | import android.graphics.Paint 4 | import android.view.LayoutInflater 5 | import android.view.ViewGroup 6 | import androidx.core.view.isVisible 7 | import androidx.recyclerview.widget.DiffUtil 8 | import androidx.recyclerview.widget.ListAdapter 9 | import androidx.recyclerview.widget.RecyclerView 10 | import com.adilegungor.gungorecommerce.common.loadImage 11 | import com.adilegungor.gungorecommerce.data.model.ProductUI 12 | import com.adilegungor.gungorecommerce.databinding.ItemSalesProductBinding 13 | 14 | class SalesProductAdapter ( 15 | private val productListener: ProductListener 16 | ) : ListAdapter(ProductDiffCallBack()) { 17 | 18 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SalesProductViewHolder = 19 | SalesProductViewHolder( 20 | ItemSalesProductBinding.inflate(LayoutInflater.from(parent.context), parent, false), 21 | productListener 22 | ) 23 | 24 | override fun onBindViewHolder(holder: SalesProductViewHolder, position: Int) = 25 | holder.bind(getItem(position)) 26 | 27 | class SalesProductViewHolder( 28 | private val binding: ItemSalesProductBinding, 29 | private val productListener: ProductListener 30 | ) : RecyclerView.ViewHolder(binding.root) { 31 | 32 | fun bind(product: ProductUI) = with(binding) { 33 | tvTitle.text = product.title 34 | tvCategory.text = product.category 35 | tvPrice.text = "${product.price} ₺" 36 | 37 | imgProduct.loadImage(product.imageOne) 38 | 39 | if (product.saleState == true) { 40 | tvSalePrice.isVisible = true 41 | tvSalePrice.text = "${product.salePrice} ₺" 42 | tvPrice.text = "${product.price} ₺" 43 | tvPrice.paintFlags = tvPrice.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG 44 | } else { 45 | tvPrice.text = "${product.price} ₺" 46 | tvSalePrice.isVisible = false 47 | } 48 | 49 | imgFavorite.setOnClickListener { 50 | productListener.onFavoriteClick(product) 51 | } 52 | 53 | root.setOnClickListener { 54 | productListener.onProductClick(product.id ?: 1) 55 | } 56 | } 57 | } 58 | 59 | class ProductDiffCallBack : DiffUtil.ItemCallback() { 60 | override fun areItemsTheSame(oldItem: ProductUI, newItem: ProductUI): Boolean { 61 | return oldItem.id == newItem.id 62 | } 63 | 64 | override fun areContentsTheSame(oldItem: ProductUI, newItem: ProductUI): Boolean { 65 | return oldItem == newItem 66 | } 67 | } 68 | 69 | interface ProductListener { 70 | fun onProductClick(id: Int) 71 | fun onFavoriteClick(product: ProductUI) 72 | } 73 | } -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/ui/arama/SearchAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.ui.arama 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.DiffUtil 6 | import androidx.recyclerview.widget.ListAdapter 7 | import androidx.recyclerview.widget.RecyclerView 8 | import com.adilegungor.gungorecommerce.common.loadImage 9 | import com.adilegungor.gungorecommerce.data.model.ProductUI 10 | import com.adilegungor.gungorecommerce.databinding.ItemSearchBinding 11 | 12 | class SearchAdapter ( 13 | private val searchProductListener: SearchProductListener 14 | ) : ListAdapter(ProductDiffCallBack()) { 15 | 16 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchProductViewHolder = 17 | SearchProductViewHolder( 18 | ItemSearchBinding.inflate(LayoutInflater.from(parent.context), parent, false), 19 | searchProductListener 20 | ) 21 | 22 | override fun onBindViewHolder(holder: SearchProductViewHolder, position: Int) = holder.bind(getItem(position)) 23 | 24 | class SearchProductViewHolder( 25 | private val binding: ItemSearchBinding, 26 | private val searchProductListener: SearchProductListener 27 | ) : RecyclerView.ViewHolder(binding.root) { 28 | 29 | fun bind(product: ProductUI) = with(binding) { 30 | tvTitle.text = product.title 31 | ivProduct.loadImage(product.imageOne) 32 | 33 | root.setOnClickListener { 34 | searchProductListener.onProductClick(product.id) 35 | } 36 | 37 | } 38 | } 39 | 40 | class ProductDiffCallBack : DiffUtil.ItemCallback() { 41 | override fun areItemsTheSame(oldItem: ProductUI, newItem: ProductUI): Boolean { 42 | return oldItem.id == newItem.id 43 | } 44 | 45 | override fun areContentsTheSame(oldItem: ProductUI, newItem: ProductUI): Boolean { 46 | return oldItem == newItem 47 | } 48 | } 49 | 50 | interface SearchProductListener { 51 | fun onProductClick(id: Int) 52 | } 53 | } -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/ui/arama/SearchFragment.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.ui.arama 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.activity.OnBackPressedCallback 6 | import androidx.appcompat.widget.SearchView 7 | import androidx.fragment.app.Fragment 8 | import androidx.fragment.app.viewModels 9 | import androidx.navigation.fragment.findNavController 10 | import com.adilegungor.gungorecommerce.R 11 | import com.adilegungor.gungorecommerce.common.gone 12 | import com.adilegungor.gungorecommerce.common.viewBinding 13 | import com.adilegungor.gungorecommerce.common.visible 14 | import com.adilegungor.gungorecommerce.databinding.FragmentSearchBinding 15 | import com.google.android.material.snackbar.Snackbar 16 | import dagger.hilt.android.AndroidEntryPoint 17 | 18 | @AndroidEntryPoint 19 | class SearchFragment : Fragment(R.layout.fragment_search), SearchAdapter.SearchProductListener { 20 | 21 | private val binding by viewBinding(FragmentSearchBinding::bind) 22 | 23 | private val viewModel by viewModels() 24 | 25 | private val searchAdapter by lazy { SearchAdapter(this) } 26 | 27 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 28 | super.onViewCreated(view, savedInstanceState) 29 | 30 | with(binding) { 31 | rvSearch.adapter = searchAdapter 32 | 33 | searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { 34 | override fun onQueryTextSubmit(query: String?): Boolean { 35 | return false 36 | } 37 | 38 | override fun onQueryTextChange(newText: String?): Boolean { 39 | newText?.let { query -> 40 | if (query.length >= 3) { 41 | viewModel.getSearchProduct(query) 42 | } else { 43 | Snackbar.make(requireView(), "Aramak istediğiniz ürün için en az 3 karakter giriniz", 1000).show() 44 | } 45 | } 46 | return true 47 | } 48 | }) 49 | } 50 | 51 | val callback = object : OnBackPressedCallback(true) { 52 | override fun handleOnBackPressed() { 53 | val action = SearchFragmentDirections.actionSearchFragmentToHomeFragment() 54 | findNavController().navigate(action) 55 | } 56 | } 57 | 58 | requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback) 59 | 60 | observeData() 61 | } 62 | 63 | private fun observeData() = with(binding) { 64 | viewModel.searchState.observe(viewLifecycleOwner) { state -> 65 | when (state) { 66 | is SearchState.Loading -> { 67 | progressBar2.visible() 68 | } 69 | 70 | is SearchState.Data -> { 71 | progressBar2.gone() 72 | searchAdapter.submitList(state.products) 73 | } 74 | 75 | is SearchState.Error -> { 76 | progressBar2.gone() 77 | } 78 | } 79 | } 80 | } 81 | override fun onProductClick(id: Int) { 82 | val action = SearchFragmentDirections.actionSearchFragmentToProductDetailFragment(id) 83 | findNavController().navigate(action) 84 | } 85 | } -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/ui/arama/SearchViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.ui.arama 2 | 3 | import android.app.Application 4 | import androidx.lifecycle.LiveData 5 | import androidx.lifecycle.MutableLiveData 6 | import com.adilegungor.gungorecommerce.common.Resource 7 | import com.adilegungor.gungorecommerce.data.model.ProductUI 8 | import com.adilegungor.gungorecommerce.data.repository.ProductRepository 9 | import com.adilegungor.gungorecommerce.ui.viewmodel.BaseViewModel 10 | import dagger.hilt.android.lifecycle.HiltViewModel 11 | import kotlinx.coroutines.launch 12 | import javax.inject.Inject 13 | 14 | @HiltViewModel 15 | class SearchViewModel @Inject constructor( 16 | private val productRepository: ProductRepository, application: Application 17 | ) : BaseViewModel(application) { 18 | 19 | private var _searchState = MutableLiveData() 20 | val searchState: LiveData 21 | get() = _searchState 22 | 23 | fun getSearchProduct(query: String) { 24 | launch { 25 | _searchState.value = SearchState.Loading 26 | when (val result = productRepository.getSearchProduct(query)) { 27 | is Resource.Success -> { 28 | _searchState.value = SearchState.Data(result.data) 29 | } 30 | 31 | is Resource.Error -> { 32 | _searchState.value = SearchState.Error(result.throwable) 33 | } 34 | } 35 | } 36 | } 37 | } 38 | 39 | sealed interface SearchState { 40 | object Loading : SearchState 41 | data class Data(val products: List) : SearchState 42 | data class Error(val throwable: Throwable) : SearchState 43 | } -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/ui/detay/ProductDetailFragment.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.ui.detay 2 | 3 | import android.graphics.Paint 4 | import android.os.Bundle 5 | import android.view.View 6 | import androidx.core.view.isVisible 7 | import androidx.fragment.app.Fragment 8 | import androidx.fragment.app.viewModels 9 | import androidx.navigation.fragment.findNavController 10 | import androidx.navigation.fragment.navArgs 11 | import com.adilegungor.gungorecommerce.R 12 | import com.adilegungor.gungorecommerce.common.loadImage 13 | import com.adilegungor.gungorecommerce.common.viewBinding 14 | import com.adilegungor.gungorecommerce.data.model.AddToCartRequest 15 | import com.adilegungor.gungorecommerce.data.model.ProductUI 16 | import com.adilegungor.gungorecommerce.databinding.FragmentProductDetailBinding 17 | import com.google.android.material.bottomnavigation.BottomNavigationView 18 | import com.google.android.material.snackbar.Snackbar 19 | import com.google.firebase.auth.FirebaseAuth 20 | import com.google.firebase.auth.ktx.auth 21 | import com.google.firebase.ktx.Firebase 22 | import dagger.hilt.android.AndroidEntryPoint 23 | 24 | @AndroidEntryPoint 25 | class ProductDetailFragment : Fragment(R.layout.fragment_product_detail) { 26 | 27 | private val binding by viewBinding(FragmentProductDetailBinding::bind) 28 | 29 | private val args by navArgs() 30 | 31 | private val viewModel by viewModels() 32 | 33 | private var bottomNavigationView: BottomNavigationView? = null 34 | 35 | private lateinit var auth: FirebaseAuth 36 | 37 | var product: ProductUI ?= null 38 | 39 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 40 | super.onViewCreated(view, savedInstanceState) 41 | 42 | auth = Firebase.auth 43 | val userId = auth.currentUser!!.uid 44 | 45 | viewModel.getProductsDetail(args.productId) 46 | 47 | val request = AddToCartRequest(userId, args.productId) 48 | 49 | // Bottom Navigation Visibility 50 | bottomNavigationView = getActivity()?.findViewById(R.id.bottomNavigationView); 51 | bottomNavigationView?.setVisibility(View.GONE); 52 | 53 | with(binding) { 54 | toolbar.setNavigationOnClickListener { 55 | findNavController().navigateUp() 56 | } 57 | 58 | btnAddCart.setOnClickListener { 59 | viewModel.addProductToCart(request) 60 | Snackbar.make(requireView(), "Ürün sepete eklendi!", 1000).show() 61 | } 62 | } 63 | observeData() 64 | } 65 | 66 | private fun observeData() = with(binding) { 67 | 68 | viewModel.productDetailState.observe(viewLifecycleOwner) { state -> 69 | when (state) { 70 | ProductDetailState.Loading -> { 71 | detailProgressBar.visibility = View.VISIBLE 72 | } 73 | 74 | is ProductDetailState.Data -> { 75 | detailProgressBar.visibility = View.GONE 76 | val scaledRating = (state.productResponse?.rate?.toFloat()!! / 5.0f) * 100.0f // Rate değeri 77 | product = state.productResponse 78 | if (state.productResponse != null) { 79 | tvTitle.text = state.productResponse.title 80 | tvDesc.text = state.productResponse.description 81 | tvCategory.text = state.productResponse.category 82 | ivProduct.loadImage(state.productResponse.imageOne) 83 | ratingBar.rating = scaledRating / 20.0f 84 | 85 | if (state.productResponse.saleState == true) { 86 | tvDetailSalePrice.isVisible = true 87 | tvDetailSalePrice.text = "${state.productResponse.salePrice } TL" 88 | tvPrice.text = "${state.productResponse.price * state.productResponse.count} TL" 89 | tvPrice.paintFlags = tvPrice.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG 90 | } else { 91 | tvPrice.text = "${state.productResponse.price * state.productResponse.count} TL" 92 | tvDetailSalePrice.isVisible = false 93 | } 94 | 95 | } 96 | } 97 | 98 | is ProductDetailState.Error -> { 99 | detailProgressBar.visibility = View.GONE 100 | Snackbar.make(requireView(), state.throwable.message.orEmpty(), 1000).show() 101 | } 102 | } 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/ui/detay/ProductDetailViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.ui.detay 2 | 3 | import android.app.Application 4 | import androidx.lifecycle.LiveData 5 | import androidx.lifecycle.MutableLiveData 6 | import com.adilegungor.gungorecommerce.common.Resource 7 | import com.adilegungor.gungorecommerce.data.model.AddToCartRequest 8 | import com.adilegungor.gungorecommerce.data.model.ProductUI 9 | import com.adilegungor.gungorecommerce.data.repository.ProductRepository 10 | import com.adilegungor.gungorecommerce.ui.viewmodel.BaseViewModel 11 | import dagger.hilt.android.lifecycle.HiltViewModel 12 | import kotlinx.coroutines.launch 13 | import javax.inject.Inject 14 | 15 | @HiltViewModel 16 | class ProductDetailViewModel @Inject constructor( 17 | private val productRepository: ProductRepository, application: Application 18 | ): BaseViewModel(application) { 19 | 20 | private var _productDetailState = MutableLiveData() 21 | val productDetailState: LiveData 22 | get() = _productDetailState 23 | 24 | fun getProductsDetail(id: Int) { 25 | launch { 26 | _productDetailState.value = ProductDetailState.Loading 27 | val result = productRepository.getProductsDetail(id) 28 | 29 | when (result) { 30 | is Resource.Success -> { 31 | _productDetailState.value = ProductDetailState.Data(result.data) 32 | } 33 | 34 | is Resource.Error -> { 35 | _productDetailState.value = ProductDetailState.Error(result.throwable) 36 | } 37 | } 38 | } 39 | } 40 | 41 | fun addProductToCart(addToCartRequest: AddToCartRequest) { 42 | launch { 43 | productRepository.addProductToCart(addToCartRequest) 44 | } 45 | } 46 | } 47 | 48 | sealed interface ProductDetailState { 49 | object Loading: ProductDetailState 50 | data class Data(val productResponse: ProductUI): ProductDetailState 51 | data class Error(val throwable: Throwable): ProductDetailState 52 | } -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/ui/favori/FavoriteAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.ui.favori 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.DiffUtil 6 | import androidx.recyclerview.widget.ListAdapter 7 | import androidx.recyclerview.widget.RecyclerView 8 | import com.adilegungor.gungorecommerce.common.loadImage 9 | import com.adilegungor.gungorecommerce.data.model.ProductUI 10 | import com.adilegungor.gungorecommerce.databinding.ItemFavoriteBinding 11 | 12 | class FavoriteAdapter( 13 | private val favProductListener: FavProductListener 14 | ) : ListAdapter(ProductDiffCallBack()) { 15 | 16 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FavProductViewHolder = 17 | FavProductViewHolder( 18 | ItemFavoriteBinding.inflate(LayoutInflater.from(parent.context), parent, false), 19 | favProductListener 20 | ) 21 | 22 | override fun onBindViewHolder(holder: FavProductViewHolder, position: Int) = holder.bind(getItem(position)) 23 | 24 | class FavProductViewHolder( 25 | private val binding: ItemFavoriteBinding, 26 | private val favProductListener: FavProductListener 27 | ) : RecyclerView.ViewHolder(binding.root) { 28 | 29 | fun bind(product: ProductUI) = with(binding) { 30 | tvTitle.text = product.title 31 | tvPrice.text = "${product.price} ₺" 32 | 33 | ivProduct.loadImage(product.imageOne) 34 | 35 | root.setOnClickListener { 36 | favProductListener.onProductClick(product.id) 37 | } 38 | 39 | ivDelete.setOnClickListener { 40 | favProductListener.onDeleteClick(product) 41 | } 42 | } 43 | } 44 | 45 | class ProductDiffCallBack : DiffUtil.ItemCallback() { 46 | override fun areItemsTheSame(oldItem: ProductUI, newItem: ProductUI): Boolean { 47 | return oldItem.id == newItem.id 48 | } 49 | 50 | override fun areContentsTheSame(oldItem: ProductUI, newItem: ProductUI): Boolean { 51 | return oldItem == newItem 52 | } 53 | } 54 | 55 | interface FavProductListener { 56 | fun onProductClick(id: Int) 57 | fun onDeleteClick(product: ProductUI) 58 | } 59 | } -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/ui/favori/FavoriteFragment.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.ui.favori 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.activity.OnBackPressedCallback 6 | import androidx.fragment.app.Fragment 7 | import androidx.fragment.app.viewModels 8 | import androidx.navigation.fragment.findNavController 9 | import com.adilegungor.gungorecommerce.R 10 | import com.adilegungor.gungorecommerce.common.gone 11 | import com.adilegungor.gungorecommerce.common.viewBinding 12 | import com.adilegungor.gungorecommerce.common.visible 13 | import com.adilegungor.gungorecommerce.data.model.ProductUI 14 | import com.adilegungor.gungorecommerce.databinding.FragmentFavoriteBinding 15 | import dagger.hilt.android.AndroidEntryPoint 16 | 17 | @AndroidEntryPoint 18 | class FavoriteFragment : Fragment(R.layout.fragment_favorite), FavoriteAdapter.FavProductListener { 19 | 20 | private val binding by viewBinding(FragmentFavoriteBinding::bind) 21 | 22 | private val viewModel by viewModels() 23 | 24 | private val favAdapter by lazy { FavoriteAdapter(this) } 25 | 26 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 27 | super.onViewCreated(view, savedInstanceState) 28 | 29 | viewModel.getFavProducts() 30 | 31 | binding.rvFavorites.adapter = favAdapter 32 | 33 | observeData() 34 | 35 | val callback = object : OnBackPressedCallback(true) { 36 | override fun handleOnBackPressed() { 37 | val action = FavoriteFragmentDirections.actionFavoriteFragmentToHomeFragment() 38 | findNavController().navigate(action) 39 | } 40 | } 41 | 42 | requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback) 43 | 44 | 45 | } 46 | 47 | private fun observeData() = with(binding) { 48 | viewModel.favState.observe(viewLifecycleOwner) { state -> 49 | when (state) { 50 | is FavState.Loading -> { 51 | progressBar.visible() 52 | } 53 | 54 | is FavState.Data -> { 55 | favAdapter.submitList(state.products) 56 | progressBar.gone() 57 | } 58 | 59 | is FavState.Error -> { 60 | progressBar.gone() 61 | } 62 | } 63 | } 64 | } 65 | 66 | override fun onProductClick(id: Int) { 67 | val action = FavoriteFragmentDirections.actionFavoriteFragmentToProductDetailFragment(id) 68 | findNavController().navigate(action) 69 | } 70 | 71 | override fun onDeleteClick(product: ProductUI) { 72 | viewModel.deleteProduct(product) 73 | } 74 | } -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/ui/favori/FavoriteViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.ui.favori 2 | 3 | import android.app.Application 4 | import androidx.lifecycle.LiveData 5 | import androidx.lifecycle.MutableLiveData 6 | import com.adilegungor.gungorecommerce.common.Resource 7 | import com.adilegungor.gungorecommerce.data.model.ProductUI 8 | import com.adilegungor.gungorecommerce.data.repository.ProductRepository 9 | import com.adilegungor.gungorecommerce.ui.viewmodel.BaseViewModel 10 | import dagger.hilt.android.lifecycle.HiltViewModel 11 | import kotlinx.coroutines.launch 12 | import javax.inject.Inject 13 | 14 | @HiltViewModel 15 | class FavoriteViewModel @Inject constructor( 16 | private val productRepository: ProductRepository, application: Application 17 | ) : BaseViewModel(application) { 18 | 19 | private var _favState = MutableLiveData() 20 | val favState: LiveData 21 | get() = _favState 22 | 23 | fun getFavProducts() { 24 | launch { 25 | _favState.value = FavState.Loading 26 | when (val result = productRepository.getFavProducts()) { 27 | is Resource.Success -> { 28 | _favState.value = FavState.Data(result.data) 29 | } 30 | 31 | is Resource.Error -> { 32 | _favState.value = FavState.Error(result.throwable) 33 | } 34 | } 35 | } 36 | } 37 | 38 | fun deleteProduct(product: ProductUI) { 39 | launch { 40 | productRepository.deleteProductFromFav(product) 41 | } 42 | } 43 | } 44 | 45 | sealed interface FavState { 46 | object Loading : FavState 47 | data class Data(val products: List) : FavState 48 | data class Error(val throwable: Throwable) : FavState 49 | } -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/ui/giris/SignInFragment.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.ui.giris 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.fragment.app.Fragment 6 | import androidx.navigation.fragment.findNavController 7 | import com.adilegungor.gungorecommerce.R 8 | import com.adilegungor.gungorecommerce.common.viewBinding 9 | import com.adilegungor.gungorecommerce.databinding.FragmentSignInBinding 10 | import com.google.android.material.bottomnavigation.BottomNavigationView 11 | import com.google.android.material.snackbar.Snackbar 12 | import com.google.firebase.auth.FirebaseAuth 13 | import dagger.hilt.android.AndroidEntryPoint 14 | 15 | @AndroidEntryPoint 16 | class SignInFragment : Fragment(R.layout.fragment_sign_in) { 17 | 18 | private val binding by viewBinding(FragmentSignInBinding::bind) 19 | private lateinit var auth: FirebaseAuth 20 | private var bottomNavigationView: BottomNavigationView? = null 21 | 22 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 23 | super.onViewCreated(view, savedInstanceState) 24 | 25 | auth = FirebaseAuth.getInstance() 26 | 27 | // Bottom Navigation Visibility 28 | bottomNavigationView = getActivity()?.findViewById(R.id.bottomNavigationView); 29 | bottomNavigationView?.setVisibility(View.GONE); 30 | 31 | with(binding) { 32 | tvDontHaveAnAccount.setOnClickListener { 33 | val action = SignInFragmentDirections.actionSignInFragmentToSignUpFragment() 34 | findNavController().navigate(action) 35 | } 36 | 37 | btnLogin.setOnClickListener { 38 | val email = etEmail.text.toString() 39 | val password = etPassword.text.toString() 40 | 41 | if (email.isNotEmpty() && password.isNotEmpty()) { 42 | signIn(email, password) 43 | } else { 44 | Snackbar.make(requireView(), "Boş alanları doldurunuz.", 1000).show() 45 | } 46 | } 47 | } 48 | } 49 | 50 | private fun signIn(email: String, password: String) { 51 | if (isValidEmail(email) && isValidPassword(password)) { 52 | auth.signInWithEmailAndPassword(email, password) 53 | .addOnSuccessListener { 54 | findNavController().navigate(R.id.signInFragmentToHomeFragment) 55 | } 56 | .addOnFailureListener { 57 | Snackbar.make(requireView(), it.message.orEmpty(), 1000).show() 58 | } 59 | } else { 60 | Snackbar.make(requireView(), "Geçersiz mail veya şifre", Snackbar.LENGTH_SHORT).show() 61 | } 62 | } 63 | 64 | private fun isValidEmail(email: String): Boolean { 65 | val emailPattern = "[a-zA-Z0-9._-]+@[a-z]+\\.+[a-z]+" 66 | return email.matches(emailPattern.toRegex()) 67 | } 68 | 69 | private fun isValidPassword(password: String): Boolean { 70 | return password.length >= 6 71 | } 72 | } -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/ui/kayit/SignUpFragment.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.ui.kayit 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.fragment.app.Fragment 6 | import androidx.navigation.fragment.findNavController 7 | import com.adilegungor.gungorecommerce.R 8 | import com.adilegungor.gungorecommerce.common.viewBinding 9 | import com.adilegungor.gungorecommerce.databinding.FragmentSignUpBinding 10 | import com.google.android.material.bottomnavigation.BottomNavigationView 11 | import com.google.android.material.snackbar.Snackbar 12 | import com.google.firebase.auth.FirebaseAuth 13 | import com.google.firebase.auth.ktx.auth 14 | import com.google.firebase.ktx.Firebase 15 | import dagger.hilt.android.AndroidEntryPoint 16 | 17 | @AndroidEntryPoint 18 | class SignUpFragment : Fragment(R.layout.fragment_sign_up) { 19 | 20 | private val binding by viewBinding(FragmentSignUpBinding::bind) 21 | private lateinit var auth: FirebaseAuth 22 | private var bottomNavigationView: BottomNavigationView? = null 23 | 24 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 25 | super.onViewCreated(view, savedInstanceState) 26 | 27 | auth = Firebase.auth 28 | 29 | auth.currentUser?.let { 30 | findNavController().navigate(R.id.signUpFragmenttohomeFragment) 31 | } 32 | 33 | // Bottom Navigation Visibility 34 | bottomNavigationView = getActivity()?.findViewById(R.id.bottomNavigationView); 35 | bottomNavigationView?.setVisibility(View.GONE); 36 | 37 | with(binding) { 38 | btnSignup.setOnClickListener { 39 | val email = etEmail.text.toString() 40 | val password = etPassword.text.toString() 41 | 42 | if (email.isNotEmpty() && password.isNotEmpty()) { 43 | signUp(email, password) 44 | } else { 45 | Snackbar.make(requireView(), "Boşlukları doldurunuz", 1000).show() 46 | } 47 | } 48 | 49 | tvHaveAnAccount.setOnClickListener { 50 | val action = SignUpFragmentDirections.actionSignUpFragmentToSignInFragment() 51 | findNavController().navigate(action) 52 | } 53 | } 54 | } 55 | 56 | private fun signUp(email: String, password: String) { 57 | if (isValidEmail(email) && isValidPassword(password)) { 58 | auth.createUserWithEmailAndPassword(email, password) 59 | .addOnSuccessListener { 60 | findNavController().navigate(R.id.signUpFragmenttohomeFragment) 61 | } 62 | .addOnFailureListener { 63 | Snackbar.make(requireView(), it.message.orEmpty(), 1000).show() 64 | } 65 | } else { 66 | Snackbar.make(requireView(), "Geçersiz mail veya şifre", Snackbar.LENGTH_SHORT).show() 67 | } 68 | } 69 | 70 | private fun isValidEmail(email: String): Boolean { 71 | val emailPattern = "[a-zA-Z0-9._-]+@[a-z]+\\.+[a-z]+" 72 | return email.matches(emailPattern.toRegex()) 73 | } 74 | 75 | private fun isValidPassword(password: String): Boolean { 76 | return password.length >= 6 77 | } 78 | } -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/ui/odeme/PaymentFragment.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.ui.odeme 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.fragment.app.Fragment 6 | import androidx.navigation.fragment.findNavController 7 | import com.adilegungor.gungorecommerce.R 8 | import com.adilegungor.gungorecommerce.common.viewBinding 9 | import com.adilegungor.gungorecommerce.databinding.FragmentPaymentBinding 10 | import com.google.android.material.bottomnavigation.BottomNavigationView 11 | import com.google.android.material.snackbar.Snackbar 12 | 13 | class PaymentFragment : Fragment(R.layout.fragment_payment) { 14 | 15 | private val binding by viewBinding(FragmentPaymentBinding::bind) 16 | private var bottomNavigationView: BottomNavigationView? = null 17 | 18 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 19 | super.onViewCreated(view, savedInstanceState) 20 | 21 | // Bottom Navigation Visibility 22 | bottomNavigationView = getActivity()?.findViewById(R.id.bottomNavigationView); 23 | bottomNavigationView?.setVisibility(View.GONE); 24 | 25 | with(binding) { 26 | btnPay.setOnClickListener { 27 | val address = etAddress.text.toString() 28 | val name = etNameSurname.text.toString() 29 | val cardNumber = etCardNumber.text.toString() 30 | val cardDate = etCardDate.text.toString() 31 | val cvc = etCvc.text.toString() 32 | 33 | if (cardNumber.length == 16 && cardDate.isNotEmpty() && cvc.isNotEmpty() && address.isNotEmpty() && name.isNotEmpty()) { 34 | findNavController().navigate(PaymentFragmentDirections.actionPaymentFragmentToResultFragment()) 35 | } else { 36 | Snackbar.make(requireView(), "Tüm boşluklar doldurulmalı ve kart numaranız en az 16 karakter içermelidir", Snackbar.LENGTH_SHORT).show() 37 | } 38 | } 39 | 40 | } 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/ui/odeme/ResultFragment.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.ui.odeme 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.fragment.app.Fragment 6 | import androidx.navigation.fragment.findNavController 7 | import com.adilegungor.gungorecommerce.R 8 | import com.adilegungor.gungorecommerce.common.viewBinding 9 | import com.adilegungor.gungorecommerce.databinding.FragmentResultBinding 10 | import com.google.android.material.bottomnavigation.BottomNavigationView 11 | 12 | class ResultFragment : Fragment(R.layout.fragment_result) { 13 | 14 | private val binding by viewBinding(FragmentResultBinding::bind) 15 | private var bottomNavigationView: BottomNavigationView? = null 16 | 17 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 18 | super.onViewCreated(view, savedInstanceState) 19 | 20 | // Bottom Navigation Visibility 21 | bottomNavigationView = getActivity()?.findViewById(R.id.bottomNavigationView); 22 | bottomNavigationView?.setVisibility(View.GONE); 23 | 24 | val gifImg = pl.droidsonroids.gif.GifDrawable(resources, R.drawable.cargo) 25 | 26 | with(binding) { 27 | ivCargo.setImageDrawable(gifImg) 28 | 29 | btnHome.setOnClickListener { 30 | findNavController().navigate(ResultFragmentDirections.actionResultFragmentToHomeFragment()) 31 | } 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/ui/profil/ProfileFragment.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.ui.profil 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | import android.os.Bundle 6 | import android.view.View 7 | import androidx.fragment.app.Fragment 8 | import androidx.fragment.app.viewModels 9 | import com.adilegungor.gungorecommerce.R 10 | import com.adilegungor.gungorecommerce.common.viewBinding 11 | import com.adilegungor.gungorecommerce.databinding.FragmentProfileBinding 12 | import com.google.android.material.bottomnavigation.BottomNavigationView 13 | import com.google.firebase.auth.FirebaseAuth 14 | import com.google.firebase.auth.ktx.auth 15 | import com.google.firebase.ktx.Firebase 16 | import androidx.navigation.fragment.findNavController 17 | import dagger.hilt.android.AndroidEntryPoint 18 | 19 | @AndroidEntryPoint 20 | class ProfileFragment : Fragment(R.layout.fragment_profile) { 21 | 22 | private val binding by viewBinding(FragmentProfileBinding::bind) 23 | private lateinit var auth: FirebaseAuth 24 | private var bottomNavigationView: BottomNavigationView? = null 25 | private val viewModel by viewModels() 26 | 27 | private val sharedPreferencesName = "MyPreferences" 28 | private val keyGender = "userGender" 29 | private val keyAvatar = "userAvatar" 30 | private lateinit var sharedPreferences: SharedPreferences 31 | 32 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 33 | super.onViewCreated(view, savedInstanceState) 34 | 35 | sharedPreferences = requireContext().getSharedPreferences(sharedPreferencesName, Context.MODE_PRIVATE) 36 | 37 | 38 | // Bottom Navigation Visibility 39 | bottomNavigationView = getActivity()?.findViewById(R.id.bottomNavigationView); 40 | bottomNavigationView?.setVisibility(View.GONE); 41 | 42 | auth = Firebase.auth 43 | 44 | with(binding) { 45 | 46 | // Verileri SharedPreferences'ten yükle 47 | val savedGender = sharedPreferences.getString(keyGender, null) 48 | val savedAvatarResource = sharedPreferences.getInt(keyAvatar, R.drawable.avatargirl) 49 | 50 | if (savedGender != null) { 51 | if (savedGender == "boy") { 52 | rbBoy.isChecked = true 53 | } else if (savedGender == "girl") { 54 | rbGirl.isChecked = true 55 | } 56 | 57 | ivProfile.setImageResource(savedAvatarResource) 58 | } 59 | 60 | radioGroup.setOnCheckedChangeListener { group, checkedId -> 61 | 62 | when (checkedId) { 63 | R.id.rb_boy -> { 64 | ivProfile.setImageResource(R.drawable.avatarboy) 65 | saveAvatarAndGender(R.drawable.avatarboy, "boy") 66 | } 67 | R.id.rb_girl -> { 68 | ivProfile.setImageResource(R.drawable.avatargirl) 69 | saveAvatarAndGender(R.drawable.avatargirl, "girl") 70 | } 71 | } 72 | } 73 | 74 | tvEmail.text = auth.currentUser?.email.toString() 75 | 76 | tvProfileFavorites.setOnClickListener { 77 | findNavController().navigate(ProfileFragmentDirections.actionProfileFragmentToFavoriteFragment()) 78 | } 79 | 80 | tvProfileCart.setOnClickListener { 81 | findNavController().navigate(ProfileFragmentDirections.actionProfileFragmentToCartFragment()) 82 | } 83 | 84 | btnSignOut.setOnClickListener { 85 | auth.signOut() 86 | findNavController().navigate(ProfileFragmentDirections.actionProfileFragmentToSignInFragment()) 87 | } 88 | 89 | // Observe LiveData for selected gender 90 | viewModel.selectedGender.observe(viewLifecycleOwner) { gender -> 91 | when (gender) { 92 | "boy" -> { 93 | binding.rbBoy.isChecked = true 94 | } 95 | 96 | "girl" -> { 97 | binding.rbGirl.isChecked = true 98 | } 99 | } 100 | } 101 | 102 | // Observe LiveData for avatar resource 103 | viewModel.avatarResource.observe(viewLifecycleOwner) { avatarResource -> 104 | binding.ivProfile.setImageResource(avatarResource) 105 | } 106 | } 107 | } 108 | 109 | // Save gender to shared preferences 110 | fun saveGender(gender: String) { 111 | val editor = sharedPreferences.edit() 112 | editor.putString(keyGender, gender) 113 | editor.apply() 114 | } 115 | 116 | private fun saveAvatar(avatarResource: Int) { 117 | val editor = sharedPreferences.edit() 118 | editor.putInt(keyAvatar, avatarResource) 119 | editor.apply() 120 | } 121 | 122 | private fun saveAvatarAndGender(avatarResource: Int, gender: String) { 123 | viewModel.updateAvatar(avatarResource, gender) 124 | saveAvatar(avatarResource) 125 | saveGender(gender) 126 | } 127 | 128 | } -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/ui/profil/ProfileViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.ui.profil 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | import com.adilegungor.gungorecommerce.R 7 | 8 | class ProfileViewModel : ViewModel() { 9 | 10 | // LiveData for avatar resource ID 11 | private val _avatarResource = MutableLiveData() 12 | val avatarResource: LiveData 13 | get() = _avatarResource 14 | 15 | // LiveData for selected gender 16 | private val _selectedGender = MutableLiveData() 17 | val selectedGender: LiveData 18 | get() = _selectedGender 19 | 20 | // Initialize ViewModel with default values 21 | init { 22 | // Initialize LiveData with default values here 23 | _avatarResource.value = R.drawable.avatargirl 24 | _selectedGender.value = "girl" 25 | } 26 | 27 | // Update avatar resource and selected gender 28 | fun updateAvatar(avatarResource: Int, gender: String) { 29 | _avatarResource.value = avatarResource 30 | _selectedGender.value = gender 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/ui/sepet/CartFragment.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.ui.sepet 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | import android.os.Bundle 6 | import android.view.View 7 | import androidx.activity.OnBackPressedCallback 8 | import androidx.core.view.isVisible 9 | import androidx.fragment.app.Fragment 10 | import androidx.fragment.app.viewModels 11 | import androidx.navigation.fragment.findNavController 12 | import com.adilegungor.gungorecommerce.R 13 | import com.adilegungor.gungorecommerce.common.gone 14 | import com.adilegungor.gungorecommerce.common.viewBinding 15 | import com.adilegungor.gungorecommerce.common.visible 16 | import com.adilegungor.gungorecommerce.data.model.ClearCartRequest 17 | import com.adilegungor.gungorecommerce.data.model.DeleteFromCartRequest 18 | import com.adilegungor.gungorecommerce.databinding.FragmentCartBinding 19 | import com.google.firebase.auth.FirebaseAuth 20 | import com.google.firebase.auth.ktx.auth 21 | import com.google.firebase.ktx.Firebase 22 | import dagger.hilt.android.AndroidEntryPoint 23 | 24 | @AndroidEntryPoint 25 | class CartFragment : Fragment(R.layout.fragment_cart), CartProductsAdapter.CartProductListener { 26 | 27 | private val binding by viewBinding(FragmentCartBinding::bind) 28 | 29 | private val viewModel by viewModels() 30 | 31 | private lateinit var sharedPreferences: SharedPreferences 32 | 33 | private lateinit var cartProductsAdapter: CartProductsAdapter 34 | 35 | private lateinit var auth: FirebaseAuth 36 | 37 | private lateinit var userId: String 38 | 39 | private var totalAmount = 0.0 40 | 41 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 42 | super.onViewCreated(view, savedInstanceState) 43 | auth = Firebase.auth 44 | userId = auth.currentUser!!.uid 45 | val request = ClearCartRequest(userId) 46 | 47 | sharedPreferences = requireContext().getSharedPreferences("UserPrefs", Context.MODE_PRIVATE) 48 | cartProductsAdapter = CartProductsAdapter(this, sharedPreferences) 49 | 50 | val callback = object : OnBackPressedCallback(true) { 51 | override fun handleOnBackPressed() { 52 | val action = CartFragmentDirections.actionCartFragmentToHomeFragment() 53 | findNavController().navigate(action) 54 | } 55 | } 56 | 57 | requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback) 58 | 59 | viewModel.getCartProducts(userId) 60 | 61 | with (binding) { 62 | rvCartProducts.adapter = cartProductsAdapter 63 | 64 | btnClear.setOnClickListener { 65 | viewModel.clearProduct(request,userId) 66 | 67 | totalAmount = 0.0 68 | 69 | val editor = sharedPreferences.edit() 70 | for (product in cartProductsAdapter.currentList) { 71 | editor.putInt("count_key_${product.id}", 0) 72 | } 73 | editor.apply() 74 | } 75 | 76 | btnBuy.setOnClickListener { 77 | findNavController().navigate(CartFragmentDirections.actionCartFragmentToPaymentFragment()) 78 | } 79 | } 80 | 81 | observeData() 82 | } 83 | 84 | private fun observeData() = with(binding) { 85 | viewModel.cartState.observe(viewLifecycleOwner) { state -> 86 | when (state) { 87 | is CartState.Loading -> { 88 | progressBar.visible() 89 | } 90 | 91 | is CartState.Data -> { 92 | cartProductsAdapter.submitList(state.products) 93 | rvCartProducts.isVisible = state.products.isNotEmpty() 94 | progressBar.gone() 95 | 96 | if (state.products.isEmpty()) { 97 | rvCartProducts.visibility = View.GONE 98 | tvTotal.visibility = View.GONE 99 | tvAmount.visibility = View.GONE 100 | btnClear.visibility = View.GONE 101 | btnBuy.visibility = View.GONE 102 | 103 | tvError.visibility = View.VISIBLE 104 | tvError.setText("Sepetinizde hiç ürün yok!") 105 | } else { 106 | rvCartProducts.visibility = View.VISIBLE 107 | tvTotal.visibility = View.VISIBLE 108 | tvAmount.visibility = View.VISIBLE 109 | btnClear.visibility = View.VISIBLE 110 | btnBuy.visibility = View.VISIBLE 111 | tvError.visibility = View.GONE 112 | 113 | } 114 | 115 | } 116 | 117 | is CartState.Error -> { 118 | progressBar.gone() 119 | } 120 | } 121 | } 122 | 123 | viewModel.totalAmount.observe(viewLifecycleOwner) { 124 | tvTotal.text = "${it} ₺" 125 | } 126 | } 127 | 128 | override fun onProductClick(id: Int) { 129 | val action = CartFragmentDirections.actionCartFragmentToProductDetailFragment(id) 130 | findNavController().navigate(action) 131 | } 132 | 133 | override fun onDeleteClick(id: Int, price: Double) { 134 | val request = DeleteFromCartRequest(id) 135 | viewModel.deleteProduct(request, price) 136 | viewModel.getCartProducts(userId) 137 | } 138 | 139 | override fun onIncreaseClick(price: Double) { 140 | viewModel.onIncreaseClick(price) 141 | } 142 | 143 | override fun onDecreaseClick(price: Double) { 144 | viewModel.onDecreaseClick(price) 145 | } 146 | } -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/ui/sepet/CartProductsAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.ui.sepet 2 | 3 | import android.content.SharedPreferences 4 | import android.provider.Settings.Global.getString 5 | import android.view.LayoutInflater 6 | import android.view.ViewGroup 7 | import androidx.lifecycle.LiveData 8 | import androidx.lifecycle.MutableLiveData 9 | import androidx.recyclerview.widget.DiffUtil 10 | import androidx.recyclerview.widget.ListAdapter 11 | import androidx.recyclerview.widget.RecyclerView 12 | import com.adilegungor.gungorecommerce.R 13 | import kotlinx.coroutines.launch 14 | import com.adilegungor.gungorecommerce.common.loadImage 15 | import com.adilegungor.gungorecommerce.data.model.ProductUI 16 | import com.adilegungor.gungorecommerce.databinding.ItemCartProductBinding 17 | 18 | class CartProductsAdapter( 19 | private val cartProductListener: CartProductListener, 20 | private val sharedPreferences: SharedPreferences 21 | ) : ListAdapter(ProductDiffCallBack()) { 22 | 23 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CartProductViewHolder = 24 | CartProductViewHolder( 25 | ItemCartProductBinding.inflate(LayoutInflater.from(parent.context), parent, false), 26 | cartProductListener 27 | ) 28 | 29 | override fun onBindViewHolder(holder: CartProductViewHolder, position: Int) = holder.bind(getItem(position)) 30 | 31 | inner class CartProductViewHolder( 32 | private val binding: ItemCartProductBinding, 33 | private val cartProductListener: CartProductListener 34 | ) : RecyclerView.ViewHolder(binding.root) { 35 | 36 | private var currentCount = 1 37 | private lateinit var sharedPreferencesKey: String 38 | 39 | fun bind(product: ProductUI) = with(binding) { 40 | tvTitle.text = product.title 41 | tvPrice.text = "${product.price} ₺" 42 | 43 | ivProduct.loadImage(product.imageOne) 44 | 45 | sharedPreferencesKey = "count_key_${product.id}" 46 | 47 | currentCount = sharedPreferences.getInt(sharedPreferencesKey, 1) 48 | tvCount.text = currentCount.toString() 49 | 50 | root.setOnClickListener { 51 | cartProductListener.onProductClick(product.id) 52 | } 53 | 54 | 55 | 56 | 57 | btnAdd.setOnClickListener { 58 | if (currentCount == product.count) { 59 | tvCount.text = currentCount.toString() 60 | cartProductListener.onIncreaseClick(product.price) 61 | } else { 62 | currentCount += 1 63 | 64 | tvCount.text = currentCount.toString() 65 | val editor = sharedPreferences.edit() 66 | editor.putInt(sharedPreferencesKey, currentCount) 67 | editor.apply() 68 | } 69 | } 70 | 71 | btnMinus.setOnClickListener { 72 | if (currentCount >= 1) { 73 | currentCount -= 1 74 | cartProductListener.onDecreaseClick(product.price) 75 | } else { 76 | cartProductListener.onDeleteClick(product.id, product.price) 77 | } 78 | 79 | tvCount.text = currentCount.toString() 80 | 81 | val editor = sharedPreferences.edit() 82 | editor.putInt(sharedPreferencesKey, currentCount) 83 | editor.apply() 84 | } 85 | 86 | ivDelete.setOnClickListener { 87 | cartProductListener.onDeleteClick(product.id, product.price) 88 | currentCount = 0 89 | val editor = sharedPreferences.edit() 90 | editor.putInt(sharedPreferencesKey, currentCount+1) 91 | editor.apply() 92 | tvCount.text = currentCount.toString() 93 | } 94 | } 95 | } 96 | 97 | class ProductDiffCallBack : DiffUtil.ItemCallback() { 98 | override fun areItemsTheSame(oldItem: ProductUI, newItem: ProductUI): Boolean { 99 | return oldItem.id == newItem.id 100 | } 101 | 102 | override fun areContentsTheSame(oldItem: ProductUI, newItem: ProductUI): Boolean { 103 | return oldItem == newItem 104 | } 105 | } 106 | 107 | interface CartProductListener { 108 | fun onProductClick(id: Int) 109 | fun onDeleteClick(id: Int, price: Double) 110 | fun onIncreaseClick(price: Double) 111 | fun onDecreaseClick(price: Double) 112 | } 113 | } -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/ui/sepet/CartViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.ui.sepet 2 | 3 | import android.app.Application 4 | import androidx.lifecycle.LiveData 5 | import androidx.lifecycle.MutableLiveData 6 | import com.adilegungor.gungorecommerce.common.Resource 7 | import com.adilegungor.gungorecommerce.data.model.ClearCartRequest 8 | import com.adilegungor.gungorecommerce.data.model.DeleteFromCartRequest 9 | import com.adilegungor.gungorecommerce.data.model.ProductUI 10 | import com.adilegungor.gungorecommerce.data.repository.ProductRepository 11 | import com.adilegungor.gungorecommerce.ui.viewmodel.BaseViewModel 12 | import dagger.hilt.android.lifecycle.HiltViewModel 13 | import kotlinx.coroutines.launch 14 | import javax.inject.Inject 15 | 16 | @HiltViewModel 17 | class CartViewModel @Inject constructor( 18 | private val productRepository: ProductRepository, application: Application 19 | ) : BaseViewModel(application) { 20 | 21 | private var _cartState = MutableLiveData() 22 | val cartState: LiveData 23 | get() = _cartState 24 | 25 | private var _totalAmount = MutableLiveData() 26 | val totalAmount: LiveData 27 | get() = _totalAmount 28 | 29 | 30 | 31 | 32 | fun getCartProducts(userId: String) { 33 | launch { 34 | _cartState.value = CartState.Loading 35 | when (val result = productRepository.getCartProduct(userId)) { 36 | is Resource.Success -> { 37 | _cartState.value = CartState.Data(result.data) 38 | _totalAmount.value = result.data.sumOf { it.price } 39 | } 40 | 41 | is Resource.Error -> { 42 | _cartState.value = CartState.Error(result.throwable) 43 | } 44 | } 45 | } 46 | } 47 | 48 | fun deleteProduct(request: DeleteFromCartRequest, price: Double) { 49 | launch { 50 | productRepository.deleteProductFromCart(request) 51 | _totalAmount.value = _totalAmount.value?.minus(price) 52 | } 53 | } 54 | 55 | fun clearProduct(request: ClearCartRequest, userId: String) { 56 | launch { 57 | when (val result = productRepository.clearProductFromCart(request)) { 58 | is Resource.Success -> { 59 | getCartProducts(userId) 60 | _totalAmount.value = 0.0 61 | } 62 | 63 | is Resource.Error -> { 64 | _cartState.value = CartState.Error(result.throwable) 65 | } 66 | } 67 | } 68 | } 69 | 70 | fun onIncreaseClick(price: Double) { 71 | _totalAmount.value = _totalAmount.value?.plus(price) 72 | } 73 | 74 | fun onDecreaseClick(price: Double) { 75 | _totalAmount.value = _totalAmount.value?.minus(price) 76 | } 77 | } 78 | 79 | sealed interface CartState { 80 | object Loading : CartState 81 | data class Data(val products: List) : CartState 82 | data class Error(val throwable: Throwable) : CartState 83 | } 84 | -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/ui/splash/SplashFragment.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.ui.splash 2 | 3 | import android.content.res.Configuration 4 | import android.graphics.Color 5 | import android.os.Build 6 | import android.os.Bundle 7 | import android.os.Handler 8 | import android.os.Looper 9 | import android.view.LayoutInflater 10 | import android.view.View 11 | import android.view.ViewGroup 12 | import androidx.fragment.app.Fragment 13 | import androidx.navigation.fragment.findNavController 14 | import com.adilegungor.gungorecommerce.R 15 | import com.adilegungor.gungorecommerce.databinding.FragmentSplashBinding 16 | import com.google.android.material.bottomnavigation.BottomNavigationView 17 | import com.google.firebase.auth.FirebaseAuth 18 | import com.google.firebase.auth.ktx.auth 19 | import com.google.firebase.ktx.Firebase 20 | import dagger.hilt.android.AndroidEntryPoint 21 | 22 | @AndroidEntryPoint 23 | class SplashFragment : Fragment() { 24 | 25 | private var _binding: FragmentSplashBinding? = null 26 | private val binding get() = _binding!! 27 | private lateinit var auth: FirebaseAuth 28 | private var bottomNavigationView: BottomNavigationView? = null 29 | 30 | override fun onCreateView( 31 | inflater: LayoutInflater, container: ViewGroup?, 32 | savedInstanceState: Bundle? 33 | ): View? { 34 | // Inflate the layout for this fragment 35 | _binding = FragmentSplashBinding.inflate(inflater, container, false) 36 | auth = Firebase.auth 37 | 38 | // Bottom Navigation Visibility 39 | bottomNavigationView = getActivity()?.findViewById(R.id.bottomNavigationView); 40 | bottomNavigationView?.setVisibility(View.GONE); 41 | 42 | //Status Bar 43 | val window = requireActivity().window 44 | 45 | // Before Android 6.0 Marshmallow 46 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { 47 | // Status bar will be white 48 | window.statusBarColor = Color.WHITE 49 | } else { 50 | if (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_NO) { 51 | // Light mode 52 | window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR 53 | window.statusBarColor = Color.WHITE 54 | } else { 55 | // Dark mode 56 | window.decorView.systemUiVisibility = 0 57 | window.statusBarColor = Color.BLACK 58 | } 59 | } 60 | 61 | return binding.root 62 | } 63 | 64 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 65 | super.onViewCreated(view, savedInstanceState) 66 | 67 | auth = FirebaseAuth.getInstance() 68 | val currentUser = auth.currentUser 69 | 70 | Handler(Looper.getMainLooper()).postDelayed({ 71 | if (currentUser != null) { 72 | // If user is logged in, redirect to Home screen. 73 | findNavController().navigate(R.id.action_splashFragment_to_homeFragment) 74 | } else { 75 | // If the user is not logged in, redirect them to the SignIn screen. 76 | findNavController().navigate(R.id.action_splashFragment_to_signInFragment) 77 | } 78 | }, SPLASH_TIME_OUT) 79 | } 80 | 81 | companion object { 82 | private const val SPLASH_TIME_OUT: Long = 3000 83 | } 84 | } -------------------------------------------------------------------------------- /app/src/main/java/com/adilegungor/gungorecommerce/ui/viewmodel/BaseViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.adilegungor.gungorecommerce.ui.viewmodel 2 | 3 | import android.app.Application 4 | import androidx.lifecycle.AndroidViewModel 5 | import kotlinx.coroutines.CoroutineScope 6 | import kotlinx.coroutines.Dispatchers 7 | import kotlinx.coroutines.Job 8 | import kotlin.coroutines.CoroutineContext 9 | 10 | abstract class BaseViewModel(application: Application) : AndroidViewModel(application), 11 | CoroutineScope { 12 | 13 | private val job = Job() 14 | 15 | override val coroutineContext: CoroutineContext 16 | get() = job + Dispatchers.Main 17 | 18 | override fun onCleared() { 19 | super.onCleared() 20 | job.cancel() 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/avatarboy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Adl1coder/GungorEcommerce/c1b4fdc795510031dad544d970e179709f9d8182/app/src/main/res/drawable/avatarboy.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/avatargirl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Adl1coder/GungorEcommerce/c1b4fdc795510031dad544d970e179709f9d8182/app/src/main/res/drawable/avatargirl.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/back.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Adl1coder/GungorEcommerce/c1b4fdc795510031dad544d970e179709f9d8182/app/src/main/res/drawable/back.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/backg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Adl1coder/GungorEcommerce/c1b4fdc795510031dad544d970e179709f9d8182/app/src/main/res/drawable/backg.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_add_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_add_circle_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_arrow_circle_left_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_delete_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_favorite_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_home_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_indeterminate_check_box_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_keyboard_double_arrow_left_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_person_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_shopping_cart_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_youtube_searched_for_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_rectangle.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/cargo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Adl1coder/GungorEcommerce/c1b4fdc795510031dad544d970e179709f9d8182/app/src/main/res/drawable/cargo.gif -------------------------------------------------------------------------------- /app/src/main/res/drawable/cartt_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/cartt_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Adl1coder/GungorEcommerce/c1b4fdc795510031dad544d970e179709f9d8182/app/src/main/res/drawable/file.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_calendar.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_card.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_cart.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_delete.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_error.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_favorite.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_favorite_white.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_lock.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_payment.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_profile.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/splas.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Adl1coder/GungorEcommerce/c1b4fdc795510031dad544d970e179709f9d8182/app/src/main/res/drawable/splas.jpg -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 20 | 21 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_cart.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 23 | 24 | 36 | 37 | 51 | 52 | 62 | 63 | 75 | 76 | 91 | 92 | 101 | 102 | 117 | 118 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_favorite.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 22 | 23 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_profile.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 18 | 19 | 31 | 32 | 41 | 42 | 51 | 52 | 61 | 62 | 63 | 64 | 77 | 78 | 91 | 92 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_result.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 23 | 24 | 45 | 46 | 62 | 63 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_search.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 23 | 24 | 36 | 37 | 46 | 47 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_sign_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 27 | 28 | 34 | 35 | 36 | 55 | 56 | 57 | 64 | 65 | 66 |