├── .gitignore ├── .idea ├── .gitignore ├── compiler.xml ├── deploymentTargetDropDown.xml ├── gradle.xml ├── misc.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── developers │ │ └── noteappktorserver │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── developers │ │ │ └── noteappktorserver │ │ │ ├── app │ │ │ └── MyBaseApplication.kt │ │ │ ├── data │ │ │ ├── local │ │ │ │ └── DataStoreManager.kt │ │ │ └── network │ │ │ │ └── ApiNoteService.kt │ │ │ ├── di │ │ │ ├── AppModel.kt │ │ │ ├── DataTimeModel.kt │ │ │ └── RetrofitModel.kt │ │ │ ├── entities │ │ │ ├── MyResponse.kt │ │ │ ├── Note.kt │ │ │ ├── User.kt │ │ │ └── UserInfoDB.kt │ │ │ ├── helpers │ │ │ ├── Event.kt │ │ │ ├── MyValidation.kt │ │ │ ├── RepositoryUtils.kt │ │ │ └── Resource.kt │ │ │ ├── qualifiers │ │ │ ├── IOThread.kt │ │ │ ├── MainThread.kt │ │ │ └── Token.kt │ │ │ ├── repositories │ │ │ ├── AuthenticationRepository.kt │ │ │ ├── CreateNoteRepository.kt │ │ │ └── HomeRepository.kt │ │ │ ├── ui │ │ │ ├── activities │ │ │ │ └── MainActivity.kt │ │ │ ├── adapters │ │ │ │ └── NoteAdapter.kt │ │ │ ├── dialogs │ │ │ │ └── AddUrlDialogs.kt │ │ │ ├── fragments │ │ │ │ ├── CreateNoteFragment.kt │ │ │ │ ├── HomeFragment.kt │ │ │ │ ├── LoginFragment.kt │ │ │ │ ├── RegisterFragment.kt │ │ │ │ └── SplashFragment.kt │ │ │ └── viewmodels │ │ │ │ ├── AuthenticationViewModel.kt │ │ │ │ ├── CreateNoteViewModel.kt │ │ │ │ └── HomeViewModel.kt │ │ │ └── utils │ │ │ ├── BaseEXT.kt │ │ │ ├── FragmentEXT.kt │ │ │ ├── KeyBordEXT.kt │ │ │ ├── NoteUtility.kt │ │ │ └── constants.kt │ └── res │ │ ├── drawable-anydpi │ │ ├── back_icon.xml │ │ ├── emai_icon.xml │ │ ├── lock_icon.xml │ │ ├── logout.xml │ │ └── user_icon.xml │ │ ├── drawable-hdpi │ │ ├── back_icon.png │ │ ├── emai_icon.png │ │ ├── lock_icon.png │ │ ├── logout.png │ │ └── user_icon.png │ │ ├── drawable-mdpi │ │ ├── back_icon.png │ │ ├── emai_icon.png │ │ ├── ic_error.xml │ │ ├── ic_image.xml │ │ ├── lock_icon.png │ │ ├── logout.png │ │ └── user_icon.png │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable-xhdpi │ │ ├── back_icon.png │ │ ├── emai_icon.png │ │ ├── lock_icon.png │ │ ├── logout.png │ │ └── user_icon.png │ │ ├── drawable-xxhdpi │ │ ├── back_icon.png │ │ ├── emai_icon.png │ │ ├── ic_copy.xml │ │ ├── ic_done.xml │ │ ├── lock_icon.png │ │ ├── logout.png │ │ └── user_icon.png │ │ ├── drawable │ │ ├── background_add_button.xml │ │ ├── background_bottom_sheet.xml │ │ ├── background_default_note.xml │ │ ├── background_indicator.xml │ │ ├── background_note.xml │ │ ├── background_note2.xml │ │ ├── background_note3.xml │ │ ├── background_note4.xml │ │ ├── background_note5.xml │ │ ├── background_save_button.xml │ │ ├── background_search.xml │ │ ├── button_background.xml │ │ ├── card_background.xml │ │ ├── edit_text_background.xml │ │ ├── ic_add_circle_outline.xml │ │ ├── ic_add_image.xml │ │ ├── ic_add_link.xml │ │ ├── ic_add_main.xml │ │ ├── ic_alert.xml │ │ ├── ic_baseline_delete_forever_24.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_search.xml │ │ └── ic_web.xml │ │ ├── font │ │ ├── brandon_medium.otf │ │ ├── israr_syria.ttf │ │ ├── poppins.xml │ │ ├── poppins_bold.xml │ │ ├── rta_regular.ttf │ │ ├── uber_move.ttf │ │ └── uber_move_bold.ttf │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── fragment_create_note.xml │ │ ├── fragment_home.xml │ │ ├── fragment_login.xml │ │ ├── fragment_register.xml │ │ ├── fragment_splash.xml │ │ ├── item_container_note.xml │ │ ├── layout_dialog_add_url.xml │ │ └── layout_persistent_bottom_sheet.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── navigation │ │ └── main_nav_graph.xml │ │ ├── raw │ │ ├── note_splash.json │ │ └── notesplash.json │ │ └── values │ │ ├── colors.xml │ │ ├── font_certs.xml │ │ ├── preloaded_fonts.xml │ │ ├── strings.xml │ │ └── themes.xml │ └── test │ └── java │ └── com │ └── developers │ └── noteappktorserver │ └── 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/deploymentTargetDropDown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NoteAppWithApiKtor 2 | 3 | Note App used to save your notes as text, images, and URLs by beautiful design by using ktor framework backend by server ngrok 4 | 5 | # Screenshots 6 | 7 |   8 |   9 |   10 |   11 |   12 |   13 |   14 |   15 |   16 |   17 |   18 |   19 |   20 |   21 | ## Built With 22 | 23 | * [Kotlin](https://kotlinlang.org) - As a programming language. 24 | * [Coroutines](https://developer.android.com/kotlin/coroutines) - For multithreading while handling requests to the server and local database. 25 | * [Navigation Component](https://developer.android.com/guide/navigation/navigation-getting-started) - To handle app navigation. 26 | * [Multidex](https://developer.android.com/studio/build/multidex) - To enable creating multi dex files because of using set of libraries that reached the maximum size of single dex file. 27 | * [Model-View-ViewModel(MVVM)](https://developer.android.com/topic/architecture) - Offers an implementation of observer design pattern. 28 | * [LiveData](https://developer.android.com/topic/libraries/architecture/livedata) - notifies views of any database changes in an observer way. 29 | * [Retrofit](https://square.github.io/retrofit/) - A type-safe HTTP client for Android and Java 30 | * [Glide](https://github.com/bumptech/glide) - It is a fast and efficient open source media management and image loading framework for Android that wraps media decoding, memory and disk caching, and resource pooling into a simple and easy to use interface. 31 | * [Timber](https://github.com/JakeWharton/timber) - It helps in logging while debugging your app. and all logging code will not be embedded in the released APK. 32 | * [Hilt](https://developer.android.com/training/dependency-injection/hilt-android) - It is arguably the most used Dependency Injection, or DI, framework for Android. Many Android projects use Dagger to simplify building and providing dependencies across the app. It gives you the ability to create specific scopes, modules, and components, where each forms a piece of a puzzle: The dependency graph. 33 | * [Clean Architecture](https://www.raywenderlich.com/3595916-clean-architecture-tutorial-for-android-getting-started) - Applying Clean Architecture and Solid Principles to build a robust, maintainable, and testable application. 34 | 35 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'kotlin-android' 4 | id 'androidx.navigation.safeargs.kotlin' 5 | id 'kotlin-kapt' 6 | id 'dagger.hilt.android.plugin' 7 | id 'kotlin-android-extensions' 8 | } 9 | 10 | android { 11 | compileSdk 31 12 | 13 | defaultConfig { 14 | applicationId "com.developers.noteappktorserver" 15 | minSdk 21 16 | targetSdk 31 17 | versionCode 1 18 | versionName "1.0" 19 | 20 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 21 | } 22 | 23 | buildTypes { 24 | release { 25 | minifyEnabled false 26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 27 | } 28 | } 29 | compileOptions { 30 | sourceCompatibility JavaVersion.VERSION_1_8 31 | targetCompatibility JavaVersion.VERSION_1_8 32 | } 33 | kotlinOptions { 34 | jvmTarget = '1.8' 35 | } 36 | 37 | buildFeatures { 38 | viewBinding true 39 | } 40 | } 41 | 42 | dependencies { 43 | 44 | implementation 'androidx.core:core-ktx:1.7.0' 45 | implementation 'androidx.appcompat:appcompat:1.4.0' 46 | implementation 'com.google.android.material:material:1.4.0' 47 | implementation 'androidx.constraintlayout:constraintlayout:2.1.2' 48 | testImplementation 'junit:junit:4.+' 49 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 50 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 51 | 52 | 53 | 54 | // Material Design 55 | implementation 'com.google.android.material:material:1.5.0-beta01' 56 | 57 | 58 | // Coroutine Lifecycle Scopes , ViewModel and architecture components 59 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0" 60 | implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.0" 61 | 62 | 63 | // Glide 64 | implementation 'com.github.bumptech.glide:glide:4.12.0' 65 | kapt 'com.github.bumptech.glide:compiler:4.12.0' 66 | annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0' 67 | 68 | //okhttps 69 | implementation "com.squareup.okhttp3:okhttp:5.0.0-alpha.2" 70 | implementation ('com.github.bumptech.glide:okhttp3-integration:4.7.1'){ 71 | exclude group: 'glide-parent' 72 | } 73 | // Permissions 74 | implementation 'pub.devrel:easypermissions:3.0.0' 75 | 76 | 77 | // Timber 78 | implementation 'com.jakewharton.timber:timber:4.7.1' 79 | 80 | 81 | implementation 'android.arch.lifecycle:extensions:1.1.1' 82 | 83 | implementation 'com.intuit.ssp:ssp-android:1.0.6' 84 | implementation 'com.intuit.sdp:sdp-android:1.0.6' 85 | 86 | // Navigation component. 87 | implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5' 88 | implementation 'androidx.navigation:navigation-ui-ktx:2.3.5' 89 | 90 | 91 | 92 | //ViewModels delegation extensions for activity 93 | implementation 'androidx.activity:activity-ktx:1.4.0' 94 | 95 | // LiveData 96 | implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0" 97 | 98 | // Coroutines 99 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2" 100 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2' 101 | 102 | // circleImageview 103 | implementation 'de.hdodenhof:circleimageview:3.1.0' 104 | 105 | // Retrofit 106 | implementation 'com.squareup.retrofit2:retrofit:2.9.0' 107 | implementation 'com.squareup.retrofit2:converter-gson:2.9.0' 108 | implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.2' 109 | implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.2' 110 | implementation "com.squareup.retrofit2:converter-moshi:2.9.0" 111 | 112 | // Moshi 113 | implementation "com.squareup.moshi:moshi-kotlin:1.12.0" 114 | kapt "com.squareup.moshi:moshi-kotlin-codegen:1.8.0" 115 | 116 | // hilt implementation. 117 | implementation 'com.google.dagger:hilt-android:2.39.1' 118 | kapt 'com.google.dagger:hilt-android-compiler:2.39.1' 119 | //hilt view model. 120 | implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03" 121 | 122 | // Room Data Base 123 | def room_version = "2.3.0" 124 | implementation "androidx.room:room-runtime:$room_version" 125 | kapt "androidx.room:room-compiler:$room_version" 126 | // optional - Kotlin Extensions and Coroutines support for Room 127 | implementation "androidx.room:room-ktx:$room_version" 128 | implementation "androidx.datastore:datastore-preferences:1.0.0" 129 | 130 | api 'com.theartofdev.edmodo:android-image-cropper:2.8.+' 131 | 132 | // rounded Image View 133 | implementation 'com.makeramen:roundedimageview:2.3.0' 134 | // other 135 | implementation 'com.facebook.shimmer:shimmer:0.5.0' 136 | implementation 'com.github.ybq:Android-SpinKit:1.4.0' 137 | implementation "com.airbnb.android:lottie:4.2.1" 138 | 139 | } -------------------------------------------------------------------------------- /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/developers/noteappktorserver/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.developers.noteappktorserver 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.developers.noteappktorserver", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 17 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/developers/noteappktorserver/app/MyBaseApplication.kt: -------------------------------------------------------------------------------- 1 | package com.developers.noteappktorserver.app 2 | 3 | import android.app.Application 4 | import dagger.hilt.android.HiltAndroidApp 5 | 6 | 7 | @HiltAndroidApp 8 | class MyBaseApplication :Application() -------------------------------------------------------------------------------- /app/src/main/java/com/developers/noteappktorserver/data/local/DataStoreManager.kt: -------------------------------------------------------------------------------- 1 | package com.developers.noteappktorserver.data.local 2 | 3 | import android.content.Context 4 | import androidx.datastore.preferences.core.edit 5 | import androidx.datastore.preferences.preferencesDataStore 6 | import androidx.lifecycle.LiveData 7 | import androidx.lifecycle.MutableLiveData 8 | import com.developers.noteappktorserver.entities.UserInfoDB 9 | import com.developers.noteappktorserver.utils.Constants 10 | import kotlinx.coroutines.* 11 | import kotlinx.coroutines.flow.Flow 12 | import kotlinx.coroutines.flow.collect 13 | import kotlinx.coroutines.flow.map 14 | 15 | private val Context.dataStore by preferencesDataStore(Constants.USERS_INFO_FILE) 16 | 17 | class DataStoreManager(appContext: Context) { 18 | 19 | private val tokenDataStore = appContext.dataStore 20 | private val scope = CoroutineScope(Job() + Dispatchers.Main) 21 | private val _glucoseFlow = MutableLiveData() 22 | // For public variables, prefer use LiveData just to read values. 23 | val glucoseFlow: LiveData get() = _glucoseFlow 24 | 25 | suspend fun setUserInfo(email: String?=null,password:String?=null,token:String?=null) { 26 | tokenDataStore.edit {preferences-> 27 | email?.let {email-> 28 | preferences[Constants.USER_EMAIL] = email 29 | } 30 | password?.let{password-> 31 | preferences[Constants.USER_PASSWORD] = password 32 | } 33 | token?.let {token-> 34 | preferences[Constants.USER_TOKEN] = token 35 | } 36 | } 37 | } 38 | 39 | val infoUser: Flow = tokenDataStore.data.map { preferences -> 40 | UserInfoDB( 41 | preferences[Constants.USER_EMAIL] ?: "", 42 | preferences[Constants.USER_PASSWORD] ?: "", 43 | preferences[Constants.USER_TOKEN] ?: "" 44 | ) 45 | 46 | } 47 | 48 | 49 | init { 50 | getTokenUser() 51 | } 52 | 53 | private fun getTokenUser() { 54 | scope.launch { 55 | infoUser.collect { token -> 56 | _glucoseFlow.postValue(token) 57 | } 58 | } 59 | 60 | } 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | } -------------------------------------------------------------------------------- /app/src/main/java/com/developers/noteappktorserver/data/network/ApiNoteService.kt: -------------------------------------------------------------------------------- 1 | package com.developers.noteappktorserver.data.network 2 | 3 | import com.developers.noteappktorserver.entities.MyResponse 4 | import com.developers.noteappktorserver.entities.Note 5 | import com.developers.noteappktorserver.entities.User 6 | import retrofit2.http.* 7 | 8 | 9 | interface ApiNoteService { 10 | 11 | @POST("users/login") 12 | suspend fun loginUser( @Body userinfo: HashMap): MyResponse 13 | 14 | 15 | @POST("users/register") 16 | suspend fun register( @Body userinfo: User): MyResponse 17 | 18 | @GET("notes") 19 | suspend fun getMyNotes(): MyResponse> 20 | 21 | @POST("notes/create") 22 | suspend fun createNote( @Body note: Note): MyResponse 23 | 24 | @DELETE("notes/delete") 25 | suspend fun deleteNote( @Query("id") noteId: Int): MyResponse 26 | 27 | @PUT("notes/update") 28 | suspend fun updateNote(@Body note: Note): MyResponse 29 | 30 | @GET("users/me") 31 | suspend fun getMe(): MyResponse 32 | 33 | 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/developers/noteappktorserver/di/AppModel.kt: -------------------------------------------------------------------------------- 1 | package com.developers.noteappktorserver.di 2 | 3 | import android.content.Context 4 | import com.bumptech.glide.Glide 5 | import com.bumptech.glide.load.engine.DiskCacheStrategy 6 | import com.bumptech.glide.request.RequestOptions 7 | import com.developers.noteappktorserver.R 8 | import com.developers.noteappktorserver.data.local.DataStoreManager 9 | import com.developers.noteappktorserver.qualifiers.IOThread 10 | import com.developers.noteappktorserver.qualifiers.MainThread 11 | 12 | import dagger.Module 13 | import dagger.Provides 14 | import dagger.hilt.InstallIn 15 | import dagger.hilt.android.qualifiers.ApplicationContext 16 | import dagger.hilt.components.SingletonComponent 17 | import kotlinx.coroutines.* 18 | import javax.inject.Singleton 19 | 20 | @Module 21 | @InstallIn(SingletonComponent::class) 22 | object AppModel { 23 | 24 | 25 | @Singleton 26 | @Provides 27 | fun provideApplicationContext( 28 | @ApplicationContext context: Context 29 | ) = context 30 | 31 | // TODO: 11/8/2021 For implementation MainDispatcher 32 | 33 | @MainThread 34 | @Singleton 35 | @Provides 36 | fun provideMainDispatcher(): CoroutineDispatcher = Dispatchers.Main 37 | 38 | 39 | // TODO: 11/8/2021 For implementation IODispatcher 40 | 41 | @IOThread 42 | @Singleton 43 | @Provides 44 | fun provideIODispatcher(): CoroutineDispatcher = Dispatchers.IO 45 | 46 | 47 | // TODO: 11/8/2021 For implementation Glide 48 | @Singleton 49 | @Provides 50 | fun provideGlideInstance( 51 | @ApplicationContext context: Context 52 | ) = Glide.with(context).setDefaultRequestOptions( 53 | RequestOptions() 54 | .placeholder(R.drawable.ic_image) 55 | .error(R.drawable.ic_error) 56 | .diskCacheStrategy(DiskCacheStrategy.DATA) 57 | 58 | ) 59 | @Provides 60 | @Singleton 61 | fun dataStoreManager(@ApplicationContext appContext: Context): DataStoreManager = 62 | DataStoreManager(appContext) 63 | 64 | // TODO: 11/8/2021 For implementation AppDatabase 65 | 66 | // @Provides 67 | // @Singleton 68 | // fun provideAppDatabase(@ApplicationContext appContext: Context): JobDataBase { 69 | // return Room.databaseBuilder( 70 | // appContext, 71 | // JobDataBase::class.java, 72 | // "job_DB" 73 | // ).setJournalMode(RoomDatabase.JournalMode.TRUNCATE) 74 | // .build() 75 | // } 76 | 77 | 78 | 79 | 80 | } -------------------------------------------------------------------------------- /app/src/main/java/com/developers/noteappktorserver/di/DataTimeModel.kt: -------------------------------------------------------------------------------- 1 | package com.developers.noteappktorserver.di 2 | 3 | import dagger.Module 4 | import dagger.Provides 5 | import dagger.hilt.InstallIn 6 | import dagger.hilt.android.components.FragmentComponent 7 | import dagger.hilt.android.components.ViewModelComponent 8 | import dagger.hilt.android.scopes.FragmentScoped 9 | import dagger.hilt.android.scopes.ViewModelScoped 10 | import java.text.SimpleDateFormat 11 | import java.util.* 12 | 13 | @Module 14 | @InstallIn(ViewModelComponent::class) 15 | object DataTimeModel { 16 | 17 | @ViewModelScoped 18 | @Provides 19 | fun provideSimpleDateFormat(): SimpleDateFormat = SimpleDateFormat("EEEE, dd MMMM yyyy HH:mm a", Locale.getDefault()) 20 | 21 | 22 | @ViewModelScoped 23 | @Provides 24 | fun provideDate(): Date = Date() 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/developers/noteappktorserver/di/RetrofitModel.kt: -------------------------------------------------------------------------------- 1 | package com.developers.noteappktorserver.di 2 | 3 | import android.util.Log 4 | import com.developers.noteappktorserver.data.local.DataStoreManager 5 | import com.developers.noteappktorserver.data.network.ApiNoteService 6 | import com.developers.noteappktorserver.qualifiers.Token 7 | import com.developers.noteappktorserver.utils.Constants 8 | import com.squareup.moshi.Moshi 9 | import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory 10 | import dagger.Module 11 | import dagger.Provides 12 | import dagger.hilt.InstallIn 13 | import dagger.hilt.android.components.ViewModelComponent 14 | import dagger.hilt.android.scopes.ViewModelScoped 15 | import okhttp3.OkHttpClient 16 | import okhttp3.Request 17 | import okhttp3.logging.HttpLoggingInterceptor 18 | import retrofit2.Retrofit 19 | import retrofit2.converter.gson.GsonConverterFactory 20 | import java.util.concurrent.TimeUnit 21 | 22 | @Module 23 | @InstallIn(ViewModelComponent::class) 24 | object RetrofitModel { 25 | 26 | 27 | 28 | @Provides 29 | @ViewModelScoped 30 | @Token 31 | fun provideTokenUser(dataStoreManager: DataStoreManager):String = dataStoreManager.glucoseFlow.value?.token?:"" 32 | 33 | 34 | 35 | // TODO: 11/8/2021 For implementation Moshi 36 | 37 | @Provides 38 | @ViewModelScoped 39 | fun providesMoshi(): Moshi = Moshi 40 | .Builder() 41 | .add(KotlinJsonAdapterFactory()) 42 | .build() 43 | 44 | 45 | // TODO: 11/8/2021 For implementation OkHttpClient 46 | 47 | @Provides 48 | @ViewModelScoped 49 | fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor { 50 | val localHttpLoggingInterceptor = HttpLoggingInterceptor() 51 | localHttpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY) 52 | return localHttpLoggingInterceptor 53 | } 54 | 55 | @Provides 56 | @ViewModelScoped 57 | fun provideOkHttpClient(interceptor: HttpLoggingInterceptor, @Token token: String = ""): OkHttpClient = 58 | OkHttpClient.Builder() 59 | .connectTimeout(30, TimeUnit.SECONDS) 60 | .writeTimeout(30, TimeUnit.SECONDS) 61 | .readTimeout(30, TimeUnit.SECONDS) 62 | .addInterceptor { chain -> 63 | val original: Request = chain.request() 64 | val builder: Request.Builder = chain.request().newBuilder() 65 | val newQuest = if (token.isNotEmpty()) { 66 | Log.i(Constants.TAG, "provideOkHttpClient: $token") 67 | original.newBuilder() 68 | .header("Authorization", "Bearer $token") 69 | .header("Content-Type", "application/json") 70 | .method(original.method, original.body) 71 | .build() 72 | } else { 73 | Log.i(Constants.TAG, "provideOkHttpClient: is empty") 74 | 75 | original.newBuilder() 76 | .header("Content-Type", "application/json") 77 | .method(original.method, original.body) 78 | .build() 79 | } 80 | // builder.addHeader(Constants.CONTENT_TYPE, Constants.APP_JSON) 81 | // builder.method(original.method, original.body) 82 | // chain.proceed(builder.build()) 83 | chain.proceed(newQuest) 84 | } 85 | .addNetworkInterceptor(interceptor) 86 | .build() 87 | 88 | 89 | // TODO: 11/8/2021 For implementation Retrofit 90 | 91 | @Provides 92 | @ViewModelScoped 93 | fun providesApiService(moshi: Moshi, okHttpClient: OkHttpClient): ApiNoteService = 94 | Retrofit.Builder() 95 | .run { 96 | baseUrl(Constants.BASE_URL) 97 | client(okHttpClient) 98 | addConverterFactory(GsonConverterFactory.create()) 99 | build() 100 | }.create(ApiNoteService::class.java) 101 | 102 | } -------------------------------------------------------------------------------- /app/src/main/java/com/developers/noteappktorserver/entities/MyResponse.kt: -------------------------------------------------------------------------------- 1 | package com.developers.noteappktorserver.entities 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | 6 | data class MyResponse( 7 | @SerializedName("success") 8 | val success:Boolean=false, 9 | @SerializedName("message") 10 | val message:String, 11 | @SerializedName("data") 12 | val data:T?=null 13 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/developers/noteappktorserver/entities/Note.kt: -------------------------------------------------------------------------------- 1 | package com.developers.noteappktorserver.entities 2 | 3 | import android.os.Parcel 4 | import android.os.Parcelable 5 | import kotlinx.android.parcel.Parcelize 6 | 7 | @Parcelize 8 | data class Note( 9 | val id: Int?, 10 | val title: String, 11 | val subTitle: String, 12 | val dataTime: String, 13 | val imagePath: String?=null, 14 | val note: String, 15 | val color: String="#333333", 16 | val webLink: String?=null, 17 | val userId: Int? 18 | ):Parcelable -------------------------------------------------------------------------------- /app/src/main/java/com/developers/noteappktorserver/entities/User.kt: -------------------------------------------------------------------------------- 1 | package com.developers.noteappktorserver.entities 2 | 3 | 4 | 5 | 6 | data class User( 7 | val id:Int?=-1, 8 | val username:String, 9 | val email:String, 10 | val image:String?, 11 | val password:String 12 | ) 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/developers/noteappktorserver/entities/UserInfoDB.kt: -------------------------------------------------------------------------------- 1 | package com.developers.noteappktorserver.entities 2 | 3 | data class UserInfoDB( 4 | val email:String="", 5 | val password:String="", 6 | val token:String="" 7 | ){ 8 | 9 | override fun toString(): String { 10 | return "email:${email},password:${password},token ${token}" 11 | } 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/developers/noteappktorserver/helpers/Event.kt: -------------------------------------------------------------------------------- 1 | package com.developers.noteappktorserver.helpers 2 | 3 | import com.developers.shopapp.helpers.Resource 4 | import kotlinx.coroutines.flow.FlowCollector 5 | 6 | class Event(private val content: T) { 7 | 8 | var hasBeenHandled = false 9 | private set 10 | 11 | fun getContentIfNotHandled(): T? { 12 | return if(!hasBeenHandled) { 13 | hasBeenHandled = true 14 | content 15 | } else null 16 | } 17 | 18 | fun peekContent() = content 19 | } 20 | 21 | class EventObserver( 22 | private inline val onError: ((String) -> Unit)? = null, 23 | private inline val onLoading: (() -> Unit)? = null, 24 | private inline val onSuccess: (T) -> Unit 25 | ) : FlowCollector>> { 26 | 27 | 28 | override suspend fun emit(value: Event>) { 29 | when(val content = value?.peekContent()) { 30 | is Resource.Success -> { 31 | content.data?.let(onSuccess) 32 | } 33 | is Resource.Error -> { 34 | value.getContentIfNotHandled()?.let { 35 | onError?.let { error -> 36 | error(it.message!!) 37 | } 38 | } 39 | } 40 | is Resource.Loading -> { 41 | onLoading?.let { loading -> 42 | loading() 43 | } 44 | } 45 | } 46 | 47 | } 48 | } 49 | 50 | 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/developers/noteappktorserver/helpers/MyValidation.kt: -------------------------------------------------------------------------------- 1 | package com.developers.noteappktorserver.helpers 2 | 3 | import android.content.Context 4 | import android.util.Patterns 5 | import com.developers.noteappktorserver.R 6 | import com.google.android.material.textfield.TextInputLayout 7 | import java.util.regex.Matcher 8 | import java.util.regex.Pattern 9 | 10 | 11 | object MyValidation { 12 | fun isValidEmail(email: String): Boolean { 13 | if (email != "") { 14 | //for mail 15 | val mailContainCars = !Patterns.EMAIL_ADDRESS.matcher(email).matches() 16 | val checkMailNumbers = email.split("@")[0] 17 | val checkIfMailNumbers: Int? = checkMailNumbers.toIntOrNull() 18 | 19 | return when { 20 | mailContainCars -> { 21 | false 22 | } 23 | email.length < 14 -> { 24 | false 25 | } 26 | checkIfMailNumbers != null -> { 27 | false 28 | } 29 | else -> { 30 | true 31 | } 32 | } 33 | } else { 34 | return false 35 | } 36 | } 37 | 38 | fun validatePass( 39 | context: Context, 40 | userPass: TextInputLayout 41 | ): Boolean { 42 | val pass = userPass.editText?.text.toString() 43 | val pat = Pattern.compile("[A-Z][^A-Z]*$") 44 | val matchCapital: Matcher = pat.matcher(pass) 45 | 46 | var lastCapitalIndex = -1 47 | if (matchCapital.find()) { 48 | lastCapitalIndex = matchCapital.start() 49 | } 50 | val patNumber = Pattern.compile("[0-9]") 51 | val matchNumber: Matcher = patNumber.matcher(pass) 52 | var lastNum = -1 53 | if (matchNumber.find()) { 54 | lastNum = matchNumber.start() 55 | } 56 | if (pass != "") { 57 | // Info.setText(getString(R.string.add_empty)) 58 | //for password 59 | val specialPass = Pattern.compile("[.!@#\$%&*()_+=|<>?{}\\\\\\[\\]~-]") 60 | val b = specialPass.matcher(pass) 61 | val passContainCars = b.find() 62 | //for mail 63 | 64 | //for pass 65 | 66 | when { 67 | pass.length < 8 -> { 68 | userPass.helperText = context.resources.getString(R.string.min_password) 69 | userPass.requestFocus() 70 | return false 71 | } 72 | passContainCars -> { 73 | userPass.helperText = 74 | context.resources.getString(R.string.pass_no_spec_charecters) 75 | userPass.requestFocus() 76 | return false 77 | } 78 | lastCapitalIndex == -1 -> { 79 | userPass.helperText = context.getString(R.string.cap_letter) 80 | userPass.requestFocus() 81 | return false 82 | } 83 | lastNum == -1 -> { 84 | userPass.helperText = context.getString(R.string.on_num) 85 | userPass.requestFocus() 86 | return false 87 | } 88 | else -> { 89 | userPass.helperText = null 90 | return true 91 | } 92 | } 93 | } else { 94 | userPass.helperText = context.resources.getString(R.string.password_empty) 95 | userPass.requestFocus() 96 | 97 | return false 98 | 99 | } 100 | 101 | } 102 | 103 | fun validateMobile(mobile: String): Boolean { 104 | when (mobile.length) { 105 | 11 -> { 106 | val emailSplit = mobile.split("") 107 | 108 | val mobileFirstThreeNumber: String = emailSplit[1] + emailSplit[2] + emailSplit[3] 109 | return mobileFirstThreeNumber == "010" || mobileFirstThreeNumber == "011" || mobileFirstThreeNumber == "012" || mobileFirstThreeNumber == "015" 110 | } 111 | 10 -> { 112 | val emailSplit = mobile.split("") 113 | 114 | val mobileFirstThreeNumber: String = emailSplit[1] + emailSplit[2] + emailSplit[3] 115 | return (mobileFirstThreeNumber == "050" || mobileFirstThreeNumber == "053" || mobileFirstThreeNumber == "054" || mobileFirstThreeNumber == "055" 116 | || mobileFirstThreeNumber == "056" || mobileFirstThreeNumber == "057" || mobileFirstThreeNumber == "058" || mobileFirstThreeNumber == "059") 117 | } 118 | else -> { 119 | return false 120 | } 121 | } 122 | 123 | 124 | } 125 | } -------------------------------------------------------------------------------- /app/src/main/java/com/developers/noteappktorserver/helpers/RepositoryUtils.kt: -------------------------------------------------------------------------------- 1 | package com.developers.noteappktorserver.helpers 2 | 3 | import com.developers.shopapp.helpers.Resource 4 | 5 | inline fun safeCall(action: () -> Resource): Resource { 6 | return try { 7 | action() 8 | } catch(e: Exception) { 9 | Resource.Error(e.message ?: "An unknown error occurred") 10 | } 11 | } 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/developers/noteappktorserver/helpers/Resource.kt: -------------------------------------------------------------------------------- 1 | package com.developers.shopapp.helpers 2 | 3 | 4 | sealed class Resource(val data : T? = null , val message : String? = null) { 5 | class Success(data : T) : Resource(data) 6 | class Error(message : String? , data : T? = null) : Resource(data , message) 7 | class Loading(data : T? = null) : Resource(data) 8 | class Init(data : T? = null) : Resource(data) 9 | } -------------------------------------------------------------------------------- /app/src/main/java/com/developers/noteappktorserver/qualifiers/IOThread.kt: -------------------------------------------------------------------------------- 1 | package com.developers.noteappktorserver.qualifiers 2 | 3 | import javax.inject.Qualifier 4 | 5 | @Qualifier 6 | @Retention(AnnotationRetention.BINARY) 7 | annotation class IOThread() 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/developers/noteappktorserver/qualifiers/MainThread.kt: -------------------------------------------------------------------------------- 1 | package com.developers.noteappktorserver.qualifiers 2 | 3 | import javax.inject.Qualifier 4 | 5 | @Qualifier 6 | @Retention(AnnotationRetention.BINARY) 7 | annotation class MainThread() 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/developers/noteappktorserver/qualifiers/Token.kt: -------------------------------------------------------------------------------- 1 | package com.developers.noteappktorserver.qualifiers 2 | 3 | import javax.inject.Qualifier 4 | 5 | @Qualifier 6 | @Retention(AnnotationRetention.BINARY) 7 | annotation class Token() 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/developers/noteappktorserver/repositories/AuthenticationRepository.kt: -------------------------------------------------------------------------------- 1 | package com.developers.noteappktorserver.repositories 2 | 3 | import com.developers.noteappktorserver.data.network.ApiNoteService 4 | import com.developers.noteappktorserver.entities.MyResponse 5 | import com.developers.noteappktorserver.entities.User 6 | import com.developers.noteappktorserver.qualifiers.IOThread 7 | import com.developers.shopapp.helpers.Resource 8 | import com.developers.noteappktorserver.helpers.safeCall 9 | import kotlinx.coroutines.CoroutineDispatcher 10 | import kotlinx.coroutines.withContext 11 | import javax.inject.Inject 12 | import kotlin.collections.HashMap 13 | 14 | class AuthenticationRepository @Inject constructor( 15 | private val apiShopService: ApiNoteService, 16 | @IOThread 17 | private val dispatcher: CoroutineDispatcher 18 | ) { 19 | 20 | suspend fun loginUser(email: String, password: String): Resource> = 21 | withContext(dispatcher) { 22 | safeCall { 23 | val hasmap = HashMap() 24 | hasmap["email"] = email 25 | hasmap["password"] = password 26 | val result = apiShopService.loginUser(hasmap) 27 | Resource.Success(result) 28 | } 29 | } 30 | 31 | 32 | suspend fun createAccount(user: User): Resource> = withContext(dispatcher) { 33 | safeCall { 34 | 35 | val result = apiShopService.register(user) 36 | Resource.Success(result) 37 | } 38 | } 39 | 40 | suspend fun getProfile(): Resource> = withContext(dispatcher) { 41 | safeCall { 42 | val result=apiShopService.getMe() 43 | Resource.Success(result) 44 | } 45 | } 46 | // 47 | // suspend fun logout():Resource = withContext(dispatcher){ 48 | // safeCall { 49 | // val result=apiShopService.logout() 50 | // Resource.Success(result) 51 | // } 52 | // } 53 | // 54 | // suspend fun verifyEmail(email:String):Resource = withContext(dispatcher){ 55 | // safeCall { 56 | // val hashMap=HashMap() 57 | // hashMap["email"] = email 58 | // val result=apiShopService.verifyEmail(hashMap) 59 | // Resource.Success(result) 60 | // } 61 | // } 62 | // 63 | // suspend fun verifyCode(email:String,code:String):Resource = withContext(dispatcher){ 64 | // safeCall { 65 | // val hashMap=HashMap() 66 | // hashMap["email"] = email 67 | // hashMap["code"] = code 68 | // val result=apiShopService.verifyCode(hashMap) 69 | // Resource.Success(result) 70 | // } 71 | // } 72 | // 73 | // suspend fun resetPassword(password: String, email: String, code: String): Resource = withContext(dispatcher){ 74 | // safeCall { 75 | // val hashMap=HashMap() 76 | // hashMap["email"] = email 77 | // hashMap["code"] = code 78 | // hashMap["password"] = password 79 | // 80 | // val result=apiShopService.resetPassword(hashMap) 81 | // Resource.Success(result) 82 | // } 83 | // } 84 | } -------------------------------------------------------------------------------- /app/src/main/java/com/developers/noteappktorserver/repositories/CreateNoteRepository.kt: -------------------------------------------------------------------------------- 1 | package com.developers.noteappktorserver.repositories 2 | 3 | import com.developers.noteappktorserver.data.network.ApiNoteService 4 | import com.developers.noteappktorserver.entities.MyResponse 5 | import com.developers.noteappktorserver.entities.Note 6 | import com.developers.noteappktorserver.helpers.safeCall 7 | import com.developers.noteappktorserver.qualifiers.IOThread 8 | import com.developers.shopapp.helpers.Resource 9 | import kotlinx.coroutines.CoroutineDispatcher 10 | import kotlinx.coroutines.Dispatchers 11 | import kotlinx.coroutines.withContext 12 | import java.text.SimpleDateFormat 13 | import java.util.* 14 | import javax.inject.Inject 15 | 16 | class NoteRepository @Inject constructor( 17 | private val apiShopService: ApiNoteService, 18 | @IOThread 19 | private val dispatcher: CoroutineDispatcher, 20 | private var simpleDateFormat : SimpleDateFormat, 21 | private var date: Date 22 | ) { 23 | 24 | suspend fun getTimeAndData():String = withContext(dispatcher) { 25 | simpleDateFormat.format(date) 26 | } 27 | 28 | suspend fun setColorIndicator(color:String):String = withContext(dispatcher) { 29 | color 30 | } 31 | 32 | suspend fun insertNote(note: Note): Resource> = withContext(dispatcher) { 33 | safeCall { 34 | val result=apiShopService.createNote(note) 35 | 36 | Resource.Success(result) 37 | } 38 | } 39 | 40 | suspend fun deleteNote(noteId: Int): Resource> = withContext(dispatcher) { 41 | safeCall { 42 | val result=apiShopService.deleteNote(noteId) 43 | Resource.Success(result) 44 | } 45 | } 46 | 47 | suspend fun updateNote(note: Note): Resource> = withContext(dispatcher){ 48 | safeCall { 49 | val result=apiShopService.updateNote(note) 50 | Resource.Success(result) 51 | } 52 | } 53 | 54 | 55 | } -------------------------------------------------------------------------------- /app/src/main/java/com/developers/noteappktorserver/repositories/HomeRepository.kt: -------------------------------------------------------------------------------- 1 | package com.developers.noteappktorserver.repositories 2 | 3 | import com.developers.noteappktorserver.data.network.ApiNoteService 4 | import com.developers.noteappktorserver.entities.MyResponse 5 | import com.developers.noteappktorserver.entities.Note 6 | import com.developers.noteappktorserver.qualifiers.IOThread 7 | import com.developers.shopapp.helpers.Resource 8 | import com.developers.noteappktorserver.helpers.safeCall 9 | import kotlinx.coroutines.CoroutineDispatcher 10 | import kotlinx.coroutines.withContext 11 | import javax.inject.Inject 12 | 13 | class HomeRepository @Inject constructor( 14 | private val apiShopService: ApiNoteService, 15 | @IOThread 16 | private val dispatcher: CoroutineDispatcher 17 | ) { 18 | 19 | 20 | 21 | suspend fun getNotes() :Resource>> = withContext(dispatcher){ 22 | safeCall { 23 | val result= apiShopService.getMyNotes() 24 | Resource.Success(result) 25 | } 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/developers/noteappktorserver/ui/activities/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.developers.noteappktorserver.ui.activities 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import android.os.Bundle 5 | import com.developers.noteappktorserver.R 6 | import dagger.hilt.android.AndroidEntryPoint 7 | 8 | 9 | @AndroidEntryPoint 10 | class MainActivity : AppCompatActivity() { 11 | override fun onCreate(savedInstanceState: Bundle?) { 12 | super.onCreate(savedInstanceState) 13 | setContentView(R.layout.activity_main) 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/developers/noteappktorserver/ui/adapters/NoteAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.developers.noteappktorserver.ui.adapters 2 | 3 | import android.annotation.SuppressLint 4 | import android.graphics.Color 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.recyclerview.widget.AsyncListDiffer 9 | import androidx.recyclerview.widget.DiffUtil 10 | import androidx.recyclerview.widget.RecyclerView 11 | import com.bumptech.glide.RequestManager 12 | import com.developers.noteappktorserver.R 13 | import com.developers.noteappktorserver.entities.Note 14 | import kotlinx.android.synthetic.main.item_container_note.view.* 15 | import javax.inject.Inject 16 | 17 | class NoteAdapter @Inject constructor( 18 | private val glide : RequestManager 19 | ) : RecyclerView.Adapter() { 20 | 21 | 22 | var notes : List 23 | get() = differ.currentList 24 | set(value) = differ.submitList(value) 25 | 26 | private val diffCallback = object : DiffUtil.ItemCallback() { 27 | override fun areContentsTheSame(oldItem : Note , newItem : Note) : Boolean { 28 | return oldItem.hashCode() == newItem.hashCode() 29 | } 30 | 31 | override fun areItemsTheSame(oldItem : Note , newItem : Note) : Boolean { 32 | return oldItem.id == newItem.id 33 | } 34 | } 35 | private val differ = AsyncListDiffer(this , diffCallback) 36 | 37 | class NoteViewHolder(itemView : View) : RecyclerView.ViewHolder(itemView) { 38 | val imageNote = itemView.image_list_note 39 | val titleNote = itemView.titleNote 40 | val subtitleNote = itemView.subtitleNote 41 | val textDataTimeNote = itemView.textDataTimeNote 42 | val urlItem = itemView.urlItem 43 | val ic_delete_note_list = itemView.ic_delete_note_list 44 | val cardNote = itemView.cardNote 45 | } 46 | 47 | override fun onCreateViewHolder(parent : ViewGroup , viewType : Int) : NoteViewHolder { 48 | return NoteViewHolder( 49 | LayoutInflater.from(parent.context).inflate( 50 | R.layout.item_container_note , 51 | parent , 52 | false 53 | ) 54 | ) 55 | } 56 | 57 | @SuppressLint("Range") 58 | override fun onBindViewHolder(holder : NoteViewHolder , position : Int) { 59 | val note = notes[position] 60 | holder.apply { 61 | note.imagePath?.let {uri-> 62 | if (uri.isNotEmpty()) 63 | glide.load(uri).into(imageNote) 64 | } 65 | titleNote.text = note.title 66 | subtitleNote.text = note.subTitle 67 | textDataTimeNote.text = note.dataTime 68 | 69 | 70 | note.webLink?.let {uri-> 71 | urlItem.text=uri 72 | } 73 | 74 | 75 | itemView.setOnClickListener { 76 | onNoteClickListener?.let { click -> 77 | click(note) 78 | } 79 | } 80 | ic_delete_note_list.setOnClickListener { 81 | onDeleteClickListener?.let { click -> 82 | click(note) 83 | } 84 | } 85 | 86 | 87 | note.color.let { color -> 88 | if (color.endsWith("#FFFFFF")) { 89 | titleNote.setTextColor(Color.parseColor("#000000")) 90 | subtitleNote.setTextColor(Color.parseColor("#000000")) 91 | textDataTimeNote.setTextColor(Color.parseColor("#000000")) 92 | } else { 93 | titleNote.setTextColor(Color.parseColor("#FFFFFF")) 94 | subtitleNote.setTextColor(Color.parseColor("#FFFFFF")) 95 | textDataTimeNote.setTextColor(Color.parseColor("#FFFFFF")) 96 | } 97 | // try { 98 | cardNote.setCardBackgroundColor(Color.parseColor(color)) 99 | // } catch (e : Exception) { 100 | // cardNote.setCardBackgroundColor(Color.parseColor("#333333")) 101 | // } 102 | 103 | } 104 | 105 | } 106 | } 107 | 108 | override fun getItemCount() : Int = notes.size 109 | 110 | 111 | // click options 112 | private var onNoteClickListener : ((Note) -> Unit)? = null 113 | 114 | fun setOnNoteClickListener(listener : (Note) -> Unit) { 115 | onNoteClickListener = listener 116 | } 117 | 118 | private var onDeleteClickListener : ((Note) -> Unit)? = null 119 | 120 | fun setOnDeleteClickListener(listener : (Note) -> Unit) { 121 | onDeleteClickListener = listener 122 | } 123 | 124 | } -------------------------------------------------------------------------------- /app/src/main/java/com/developers/noteappktorserver/ui/dialogs/AddUrlDialogs.kt: -------------------------------------------------------------------------------- 1 | package com.developers.noteappktorserver.ui.dialogs 2 | 3 | import android.annotation.SuppressLint 4 | import android.app.Dialog 5 | import android.graphics.Color 6 | import android.graphics.drawable.ColorDrawable 7 | import android.os.Bundle 8 | import android.view.LayoutInflater 9 | import android.view.View 10 | import android.view.ViewGroup 11 | import androidx.fragment.app.DialogFragment 12 | import com.developers.noteappktorserver.R 13 | import com.google.android.material.dialog.MaterialAlertDialogBuilder 14 | import dagger.hilt.android.AndroidEntryPoint 15 | import kotlinx.android.synthetic.main.layout_dialog_add_url.* 16 | 17 | @AndroidEntryPoint 18 | class AddUrlDialogs : DialogFragment() { 19 | 20 | 21 | private lateinit var dialogView : View 22 | 23 | private var addUrlListener: ((String,Dialog?) -> Unit)? = null 24 | 25 | fun setPositiveAddUrlListener(listener: (String,Dialog?) -> Unit) { 26 | addUrlListener = listener 27 | } 28 | override fun onCreateView(inflater : LayoutInflater , container : ViewGroup? , savedInstanceState : Bundle?) : View { 29 | return dialogView 30 | } 31 | 32 | @SuppressLint("InflateParams") 33 | override fun onCreateDialog(savedInstanceState : Bundle?) : Dialog { 34 | dialogView = LayoutInflater.from(requireContext()).inflate( 35 | R.layout.layout_dialog_add_url , 36 | null 37 | ) 38 | return MaterialAlertDialogBuilder(requireContext()) 39 | .setView(dialogView) 40 | .setBackground(ColorDrawable(Color.TRANSPARENT)) 41 | .create() 42 | } 43 | 44 | override fun onViewCreated(view : View , savedInstanceState : Bundle?) { 45 | super.onViewCreated(view , savedInstanceState) 46 | btAdd.setOnClickListener { 47 | addUrlListener?.let { click -> 48 | click(input_url.text.toString(),dialog) 49 | dismiss() 50 | } 51 | } 52 | 53 | btCancel.setOnClickListener { 54 | dismiss() 55 | } 56 | 57 | 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /app/src/main/java/com/developers/noteappktorserver/ui/fragments/LoginFragment.kt: -------------------------------------------------------------------------------- 1 | package com.developers.noteappktorserver.ui.fragments 2 | 3 | import android.os.Bundle 4 | import android.util.Log 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.core.view.isVisible 9 | import androidx.core.widget.doAfterTextChanged 10 | import androidx.fragment.app.Fragment 11 | import androidx.fragment.app.viewModels 12 | import androidx.lifecycle.lifecycleScope 13 | import androidx.navigation.NavOptions 14 | import androidx.navigation.fragment.findNavController 15 | import com.developers.noteappktorserver.R 16 | import com.developers.noteappktorserver.data.local.DataStoreManager 17 | import com.developers.noteappktorserver.databinding.FragmentLoginBinding 18 | import com.developers.noteappktorserver.helpers.EventObserver 19 | import com.developers.noteappktorserver.ui.viewmodels.AuthenticationViewModel 20 | import com.developers.noteappktorserver.utils.Constants 21 | import com.developers.shopapp.utils.snackbar 22 | 23 | import dagger.hilt.android.AndroidEntryPoint 24 | import kotlinx.coroutines.launch 25 | import javax.inject.Inject 26 | 27 | @AndroidEntryPoint 28 | class LoginFragment:Fragment() { 29 | private var _binding: FragmentLoginBinding? = null 30 | private val binding get() = _binding!! 31 | 32 | 33 | private val authenticationViewModel: AuthenticationViewModel by viewModels() 34 | 35 | @Inject 36 | lateinit var dataStoreManager: DataStoreManager 37 | 38 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 39 | super.onViewCreated(view, savedInstanceState) 40 | 41 | 42 | 43 | // to get email , password if sets it as remember me 44 | val dataUserInfo = dataStoreManager.glucoseFlow.value 45 | binding.inputTextEmail.setText(dataUserInfo?.email) 46 | binding.inputTextPassword.setText(dataUserInfo?.password) 47 | Log.i(Constants.TAG, "dataUserInfo: ${dataUserInfo.toString()}") 48 | // clear error text 49 | binding.inputTextEmail.doAfterTextChanged { 50 | binding.inputTextLayoutEmail.isHelperTextEnabled = false 51 | } 52 | 53 | binding.inputTextPassword.doAfterTextChanged { 54 | binding.inputTextLayoutPassword.isHelperTextEnabled = false 55 | } 56 | 57 | binding.textSignUp.setOnClickListener { 58 | findNavController().navigate(R.id.action_loginFragment_to_registerFragment) 59 | } 60 | 61 | binding.forgetPassword.setOnClickListener { 62 | //findNavController().navigate(R.id.action_loginFragment_to_forgetPasswordFragment) 63 | } 64 | binding.backIcon.setOnClickListener { 65 | requireActivity().finish() 66 | } 67 | // sign in 68 | binding.singinBtn.setOnClickListener { 69 | 70 | authenticationViewModel.loginUser( 71 | binding.inputTextLayoutEmail, 72 | binding.inputTextLayoutPassword, 73 | ) 74 | } 75 | 76 | // check login state 77 | subscribeToObservables(savedInstanceState) 78 | 79 | 80 | 81 | 82 | } 83 | 84 | private fun subscribeToObservables(savedInstanceState: Bundle?) { 85 | lifecycleScope.launchWhenCreated { 86 | authenticationViewModel.authLoginUserStatus.collect(EventObserver( 87 | onLoading = { 88 | binding.spinKit.isVisible=true 89 | 90 | Log.i(Constants.TAG, "onViewCreated: Loding ") 91 | 92 | }, 93 | onError = { 94 | snackbar(it) 95 | binding.spinKit.isVisible=false 96 | Log.i(Constants.TAG, "onViewCreated: ERROR $it") 97 | 98 | }, 99 | onSuccess = { 100 | binding.spinKit.isVisible=false 101 | Log.i(Constants.TAG, "onViewCreated: onSuccess $it") 102 | snackbar(it.message) 103 | it.data?.let { token -> 104 | lifecycleScope.launch { 105 | saveDataAndNavigate(token,savedInstanceState) 106 | } 107 | } 108 | 109 | } 110 | )) 111 | } 112 | 113 | } 114 | private suspend fun saveDataAndNavigate(token: String, savedInstanceState: Bundle?) { 115 | updateToken(token) 116 | if (binding.switchRememberMe.isChecked) { 117 | val password = binding.inputTextPassword.text.toString() 118 | val email = binding.inputTextEmail.text.toString() 119 | saveEmailAndPassword(email, password) 120 | } 121 | 122 | val navOptions = NavOptions.Builder() 123 | .setPopUpTo(R.id.loginFragment, true) 124 | .build() 125 | findNavController().navigate( 126 | R.id.action_loginFragment_to_homeFragment, 127 | savedInstanceState, 128 | navOptions 129 | ) 130 | } 131 | 132 | private suspend fun saveEmailAndPassword(email: String, password: String) { 133 | dataStoreManager.setUserInfo(email = email, password = password) 134 | 135 | } 136 | 137 | private suspend fun updateToken(token: String) { 138 | dataStoreManager.setUserInfo(token = token) 139 | 140 | } 141 | 142 | override fun onCreateView( 143 | inflater: LayoutInflater, 144 | container: ViewGroup?, 145 | savedInstanceState: Bundle? 146 | ): View? { 147 | _binding= FragmentLoginBinding.inflate(inflater, container, false) 148 | 149 | return binding.root 150 | } 151 | 152 | 153 | override fun onDestroyView() { 154 | super.onDestroyView() 155 | _binding=null 156 | } 157 | } -------------------------------------------------------------------------------- /app/src/main/java/com/developers/noteappktorserver/ui/fragments/RegisterFragment.kt: -------------------------------------------------------------------------------- 1 | package com.developers.noteappktorserver.ui.fragments 2 | 3 | import android.Manifest 4 | import android.annotation.SuppressLint 5 | import android.app.Activity 6 | import android.content.Intent 7 | import android.net.Uri 8 | import android.os.Build 9 | import android.os.Bundle 10 | import android.util.Log 11 | import android.view.LayoutInflater 12 | import android.view.View 13 | import android.view.ViewGroup 14 | import androidx.annotation.RequiresApi 15 | import androidx.core.view.isVisible 16 | import androidx.core.widget.doAfterTextChanged 17 | import androidx.fragment.app.Fragment 18 | import androidx.fragment.app.viewModels 19 | import androidx.lifecycle.lifecycleScope 20 | import androidx.navigation.NavOptions 21 | import androidx.navigation.fragment.findNavController 22 | import com.developers.noteappktorserver.R 23 | import com.developers.noteappktorserver.data.local.DataStoreManager 24 | import com.developers.noteappktorserver.databinding.FragmentRegisterBinding 25 | import com.developers.noteappktorserver.entities.Note 26 | import com.developers.noteappktorserver.helpers.EventObserver 27 | import com.developers.noteappktorserver.ui.viewmodels.AuthenticationViewModel 28 | import com.developers.noteappktorserver.utils.Constants 29 | import com.developers.noteappktorserver.utils.NoteUtility 30 | import com.developers.shopapp.utils.snackbar 31 | import com.theartofdev.edmodo.cropper.CropImage 32 | import com.theartofdev.edmodo.cropper.CropImageView 33 | 34 | import dagger.hilt.android.AndroidEntryPoint 35 | import kotlinx.coroutines.launch 36 | import pub.devrel.easypermissions.AppSettingsDialog 37 | import pub.devrel.easypermissions.EasyPermissions 38 | import javax.inject.Inject 39 | 40 | @AndroidEntryPoint 41 | class RegisterFragment:Fragment(), EasyPermissions.PermissionCallbacks { 42 | private var _binding: FragmentRegisterBinding? = null 43 | private val binding get() = _binding!! 44 | 45 | private val authenticationViewModel: AuthenticationViewModel by viewModels() 46 | 47 | @Inject 48 | lateinit var dataStoreManager: DataStoreManager 49 | private var imageUserUri:String="" 50 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 51 | super.onViewCreated(view, savedInstanceState) 52 | 53 | // clear error text 54 | binding.inputTextUserName.doAfterTextChanged { 55 | binding.inputTextLayoutUserName.isHelperTextEnabled = false 56 | } 57 | binding.inputTextEmail.doAfterTextChanged { 58 | binding.inputTextLayoutEmail.isHelperTextEnabled = false 59 | } 60 | 61 | binding.inputTextPassword.doAfterTextChanged { 62 | binding.inputTextLayoutPassword.isHelperTextEnabled = false 63 | } 64 | 65 | binding.alreadyHaveAccount.setOnClickListener { 66 | findNavController().navigate(R.id.action_registerFragment_to_loginFragment) 67 | } 68 | binding.backIcon.setOnClickListener { 69 | findNavController().popBackStack() 70 | } 71 | // sign in 72 | binding.createAccount.setOnClickListener { 73 | authenticationViewModel.createAccount( 74 | binding.inputTextLayoutUserName, 75 | binding.inputTextLayoutEmail, 76 | binding.inputTextLayoutPassword,imageUserUri 77 | ) 78 | } 79 | 80 | binding.userImage.setOnClickListener { 81 | requestPermissions() 82 | } 83 | 84 | // check login state 85 | subscribeToObservables(savedInstanceState) 86 | 87 | 88 | 89 | 90 | } 91 | 92 | private fun subscribeToObservables(savedInstanceState: Bundle?) { 93 | lifecycleScope.launchWhenStarted { 94 | authenticationViewModel.createAccountStatus.collect(EventObserver( 95 | onLoading ={ 96 | binding.spinKit.isVisible=true 97 | }, 98 | onSuccess = { 99 | binding.spinKit.isVisible=false 100 | 101 | snackbar(it.message) 102 | it.data?.let { 103 | lifecycleScope.launch { 104 | 105 | saveDataAndNavigate(it,savedInstanceState) 106 | } 107 | } 108 | }, 109 | onError = { 110 | binding.spinKit.isVisible=false 111 | snackbar(it) 112 | } 113 | )) 114 | } 115 | } 116 | 117 | private suspend fun saveDataAndNavigate(token: String, savedInstanceState: Bundle?) { 118 | updateToken(token) 119 | val navOptions = NavOptions.Builder() 120 | .setPopUpTo(R.id.registerFragment, true) 121 | .build() 122 | findNavController().navigate( 123 | R.id.action_registerFragment_to_homeFragment, 124 | savedInstanceState, 125 | navOptions 126 | ) 127 | } 128 | 129 | private fun saveEmailAndPassword(email: String, password: String) { 130 | lifecycleScope.launch { 131 | dataStoreManager.setUserInfo(email = email,password = password) 132 | } 133 | } 134 | 135 | private suspend fun updateToken(token: String) { 136 | dataStoreManager.setUserInfo(token = token) 137 | 138 | } 139 | 140 | 141 | override fun onCreateView( 142 | inflater: LayoutInflater, 143 | container: ViewGroup?, 144 | savedInstanceState: Bundle? 145 | ): View? { 146 | _binding= FragmentRegisterBinding.inflate(inflater, container, false) 147 | 148 | return binding.root 149 | } 150 | 151 | 152 | override fun onDestroyView() { 153 | super.onDestroyView() 154 | _binding=null 155 | } 156 | 157 | private fun requestPermissions() { 158 | 159 | if (NoteUtility.hasReadExternalStoragePermissions(requireContext())) { 160 | null 161 | } 162 | 163 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { 164 | EasyPermissions.requestPermissions( 165 | this, 166 | "you need to accept read storage permissions to use this optional.", 167 | Constants.REQUEST_CODE_READ_EXTERNAL_STORAGE_PERMISSIONS, 168 | Manifest.permission.READ_EXTERNAL_STORAGE 169 | ) 170 | } else { 171 | EasyPermissions.requestPermissions( 172 | this, 173 | "you need to accept read storage permissions to use this optional.", 174 | Constants.REQUEST_CODE_READ_EXTERNAL_STORAGE_PERMISSIONS, 175 | Manifest.permission.READ_EXTERNAL_STORAGE 176 | ) 177 | } 178 | } 179 | 180 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP_MR1) 181 | override fun onPermissionsGranted(requestCode: Int, perms: MutableList) { 182 | openGallery() 183 | } 184 | 185 | @RequiresApi(Build.VERSION_CODES.Q) 186 | override fun onPermissionsDenied(requestCode: Int, perms: MutableList) { 187 | if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) { 188 | AppSettingsDialog.Builder(this).build().show() 189 | } else { 190 | requestPermissions() 191 | } 192 | } 193 | 194 | override fun onRequestPermissionsResult( 195 | requestCode: Int, 196 | permissions: Array, 197 | grantResults: IntArray 198 | ) { 199 | super.onRequestPermissionsResult(requestCode, permissions, grantResults) 200 | EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) 201 | } 202 | 203 | private fun openGallery() { 204 | CropImage.startPickImageActivity(requireContext(), this@RegisterFragment) 205 | } 206 | 207 | fun cropImage(uri: Uri?) { 208 | uri?.let { myuri -> 209 | CropImage.activity(uri) 210 | .setCropShape(CropImageView.CropShape.OVAL) 211 | .setMultiTouchEnabled(true) 212 | .setGuidelines(CropImageView.Guidelines.ON) 213 | .start(requireContext(), this) 214 | } 215 | 216 | } 217 | 218 | @SuppressLint("CheckResult") 219 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 220 | super.onActivityResult(requestCode, resultCode, data) 221 | Log.i("here", "onActivityResult: ") 222 | when (requestCode) { 223 | CropImage.PICK_IMAGE_CHOOSER_REQUEST_CODE -> { 224 | if (resultCode == Activity.RESULT_OK) { 225 | val uri = CropImage.getPickImageResultUri(requireContext(), data) 226 | cropImage(uri) 227 | } 228 | } 229 | CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE -> { 230 | val result = CropImage.getActivityResult(data) 231 | if (resultCode == Activity.RESULT_OK) { 232 | binding.userImage.setImageURI(result.uri) 233 | imageUserUri = result.uri.toString() 234 | } 235 | } 236 | 237 | } 238 | 239 | } 240 | 241 | } -------------------------------------------------------------------------------- /app/src/main/java/com/developers/noteappktorserver/ui/fragments/SplashFragment.kt: -------------------------------------------------------------------------------- 1 | package com.developers.noteappktorserver.ui.fragments 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import android.os.Handler 6 | import android.util.Log 7 | import android.view.LayoutInflater 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import android.widget.Toast 11 | import androidx.fragment.app.Fragment 12 | import androidx.lifecycle.lifecycleScope 13 | import androidx.navigation.NavOptions 14 | import androidx.navigation.fragment.findNavController 15 | import com.developers.noteappktorserver.R 16 | import com.developers.noteappktorserver.data.local.DataStoreManager 17 | import com.developers.noteappktorserver.databinding.FragmentSplashBinding 18 | 19 | import dagger.hilt.android.AndroidEntryPoint 20 | import kotlinx.coroutines.delay 21 | import kotlinx.coroutines.flow.collect 22 | import kotlinx.coroutines.launch 23 | import javax.inject.Inject 24 | 25 | @AndroidEntryPoint 26 | class SplashFragment : Fragment() { 27 | private var _binding: FragmentSplashBinding? = null 28 | private val binding get() = _binding!! 29 | 30 | @Inject 31 | lateinit var dataStoreManager: DataStoreManager 32 | 33 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 34 | super.onViewCreated(view, savedInstanceState) 35 | 36 | binding.tvNoteApp.animate().translationY(-1400f).setDuration(2700).setStartDelay(0) 37 | // binding.animationView.animate().translationX(2000f).setDuration(2700).setStartDelay(2900) 38 | 39 | 40 | 41 | Handler().postDelayed({ 42 | checkUserStatsAndNavigate(savedInstanceState) 43 | 44 | }, 5000) 45 | 46 | 47 | } 48 | 49 | private fun checkUserStatsAndNavigate(savedInstanceState: Bundle?) { 50 | lifecycleScope.launchWhenStarted { 51 | dataStoreManager.infoUser.collect {userInfo-> 52 | val navOptions = NavOptions.Builder() 53 | .setPopUpTo(R.id.splashFragment, true) 54 | .build() 55 | 56 | if (userInfo.token.isNotEmpty()) { 57 | findNavController().navigate( 58 | R.id.action_splashFragment_to_homeFragment, 59 | savedInstanceState, 60 | navOptions 61 | ) 62 | } else { 63 | findNavController().navigate( 64 | R.id.action_splashFragment_to_loginFragment, 65 | savedInstanceState, 66 | navOptions 67 | ) 68 | } 69 | } 70 | } 71 | } 72 | 73 | 74 | override fun onCreateView( 75 | inflater: LayoutInflater, 76 | container: ViewGroup?, 77 | savedInstanceState: Bundle? 78 | ): View? { 79 | _binding = FragmentSplashBinding.inflate(inflater, container, false) 80 | 81 | return binding.root 82 | } 83 | 84 | 85 | override fun onDestroyView() { 86 | super.onDestroyView() 87 | _binding = null 88 | } 89 | } -------------------------------------------------------------------------------- /app/src/main/java/com/developers/noteappktorserver/ui/viewmodels/AuthenticationViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.developers.noteappktorserver.ui.viewmodels 2 | 3 | import android.content.Context 4 | import androidx.lifecycle.ViewModel 5 | import androidx.lifecycle.viewModelScope 6 | import com.developers.noteappktorserver.entities.MyResponse 7 | import com.developers.noteappktorserver.entities.User 8 | import com.developers.noteappktorserver.helpers.Event 9 | import com.developers.noteappktorserver.helpers.MyValidation 10 | import com.developers.noteappktorserver.qualifiers.MainThread 11 | import com.developers.noteappktorserver.repositories.AuthenticationRepository 12 | import com.developers.shopapp.helpers.Resource 13 | import com.google.android.material.textfield.TextInputLayout 14 | import dagger.hilt.android.lifecycle.HiltViewModel 15 | import dagger.hilt.android.qualifiers.ApplicationContext 16 | import kotlinx.coroutines.CoroutineDispatcher 17 | import kotlinx.coroutines.flow.MutableStateFlow 18 | import kotlinx.coroutines.launch 19 | import javax.inject.Inject 20 | 21 | @HiltViewModel 22 | class AuthenticationViewModel @Inject constructor( 23 | @ApplicationContext val context: Context, 24 | private val repository: AuthenticationRepository, 25 | @MainThread 26 | private val dispatcher: CoroutineDispatcher 27 | 28 | ) : ViewModel() { 29 | 30 | 31 | private val _loginUserStatus = 32 | MutableStateFlow>>>(Event(Resource.Init())) 33 | val authLoginUserStatus: MutableStateFlow>>> = _loginUserStatus 34 | 35 | private val _createAccountStatus = 36 | MutableStateFlow>>>(Event(Resource.Init())) 37 | val createAccountStatus: MutableStateFlow>>> = _createAccountStatus 38 | 39 | 40 | 41 | // private val _logoutStatus = MutableStateFlow>>(Event(Resource.Init())) 42 | // val logoutStatus: MutableStateFlow>> = _logoutStatus 43 | // 44 | // private val _verifyEmailStatus = MutableStateFlow>>(Event(Resource.Init())) 45 | // val verifyEmailStatus: MutableStateFlow>> = _verifyEmailStatus 46 | // 47 | // private val _verifyCodeStatus = MutableStateFlow>>(Event(Resource.Init())) 48 | // val verifyCodeStatus: MutableStateFlow>> = _verifyCodeStatus 49 | // 50 | // private val _resetPasswordStatus = MutableStateFlow>>(Event(Resource.Init())) 51 | // val resetPasswordStatus: MutableStateFlow>> = _resetPasswordStatus 52 | 53 | 54 | fun loginUser( 55 | inputTextLayoutEmail: TextInputLayout, 56 | inputTextLayoutPassword: TextInputLayout 57 | ) { 58 | viewModelScope.launch(dispatcher) { 59 | val email = inputTextLayoutEmail.editText!!.text.toString() 60 | val password = inputTextLayoutPassword.editText!!.text.toString() 61 | when { 62 | email.isEmpty() -> { 63 | _loginUserStatus.emit(Event(Resource.Error("E-mail is require"))) 64 | inputTextLayoutEmail.isHelperTextEnabled = true 65 | inputTextLayoutEmail.helperText = "Require*" 66 | return@launch 67 | } 68 | 69 | !MyValidation.isValidEmail(email = email) -> { 70 | _loginUserStatus.emit(Event(Resource.Error("E-mail is not valid"))) 71 | inputTextLayoutEmail.isHelperTextEnabled = true 72 | inputTextLayoutEmail.helperText = "not valid" 73 | } 74 | password.isEmpty() -> { 75 | _loginUserStatus.emit(Event(Resource.Error("Password is require"))) 76 | inputTextLayoutPassword.isHelperTextEnabled = true 77 | inputTextLayoutPassword.helperText = "Require*" 78 | return@launch 79 | } 80 | !MyValidation.validatePass(context, inputTextLayoutPassword) -> { 81 | _loginUserStatus.emit(Event(Resource.Error(inputTextLayoutPassword.helperText.toString()))) 82 | } 83 | else -> { 84 | _loginUserStatus.emit(Event(Resource.Loading())) 85 | val result = repository.loginUser(email, password) 86 | _loginUserStatus.emit(Event(result)) 87 | } 88 | } 89 | } 90 | 91 | } 92 | 93 | fun createAccount( 94 | inputTextLayoutUserName: TextInputLayout, 95 | inputTextLayoutEmail: TextInputLayout, 96 | inputTextLayoutPassword: TextInputLayout, 97 | imageUserUri: String 98 | ) { 99 | viewModelScope.launch(dispatcher) { 100 | val username = inputTextLayoutUserName.editText!!.text.toString() 101 | val email = inputTextLayoutEmail.editText!!.text.toString() 102 | val password = inputTextLayoutPassword.editText!!.text.toString() 103 | when { 104 | imageUserUri.isEmpty() -> { 105 | _createAccountStatus.emit(Event(Resource.Error("Please Selected Your image profile"))) 106 | return@launch 107 | } 108 | username.isEmpty() -> { 109 | _createAccountStatus.emit(Event(Resource.Error("Username is require"))) 110 | inputTextLayoutUserName.isHelperTextEnabled = true 111 | inputTextLayoutUserName.helperText = "Require*" 112 | return@launch 113 | } 114 | 115 | email.isEmpty() -> { 116 | _createAccountStatus.emit(Event(Resource.Error("E-mail is require"))) 117 | inputTextLayoutEmail.isHelperTextEnabled = true 118 | inputTextLayoutEmail.helperText = "Require*" 119 | return@launch 120 | } 121 | 122 | !MyValidation.isValidEmail(email = email) -> { 123 | _createAccountStatus.emit(Event(Resource.Error("E-mail is not valid"))) 124 | inputTextLayoutEmail.isHelperTextEnabled = true 125 | inputTextLayoutEmail.helperText = "not valid" 126 | return@launch 127 | } 128 | 129 | 130 | password.isEmpty() -> { 131 | _createAccountStatus.emit(Event(Resource.Error("Password is require"))) 132 | inputTextLayoutPassword.isHelperTextEnabled = true 133 | inputTextLayoutPassword.helperText = "Require*" 134 | return@launch 135 | } 136 | !MyValidation.validatePass(context, inputTextLayoutPassword) -> { 137 | _createAccountStatus.emit(Event(Resource.Error(inputTextLayoutPassword.helperText.toString()))) 138 | return@launch 139 | } 140 | else -> { 141 | _createAccountStatus.emit(Event(Resource.Loading())) 142 | val user=User( 143 | username = username, 144 | email = email, 145 | password = password, 146 | image = imageUserUri 147 | ) 148 | val result = repository.createAccount( 149 | user 150 | ) 151 | _createAccountStatus.emit(Event(result)) 152 | return@launch 153 | } 154 | } 155 | } 156 | 157 | 158 | } 159 | 160 | 161 | // 162 | // fun logout() { 163 | // viewModelScope.launch(dispatcher) { 164 | // _logoutStatus.emit(Event(Resource.Loading())) 165 | // val result = repository.logout() 166 | // _logoutStatus.emit(Event(result)) 167 | // } 168 | // } 169 | // 170 | // fun verifyEmail(inputTextLayoutEmail: TextInputLayout) { 171 | // viewModelScope.launch(dispatcher) { 172 | // val email = inputTextLayoutEmail.editText!!.text.toString() 173 | // when { 174 | // email.isEmpty() -> { 175 | // _loginUserStatus.emit(Event(Resource.Error("E-mail is require"))) 176 | // inputTextLayoutEmail.isHelperTextEnabled = true 177 | // inputTextLayoutEmail.helperText = "Require*" 178 | // } 179 | // 180 | // !MyValidation.isValidEmail(email = email) -> { 181 | // _loginUserStatus.emit(Event(Resource.Error("E-mail is not valid"))) 182 | // inputTextLayoutEmail.isHelperTextEnabled = true 183 | // inputTextLayoutEmail.helperText = "not valid" 184 | // } 185 | // else -> { 186 | // _verifyEmailStatus.emit(Event(Resource.Loading())) 187 | // val result = repository.verifyEmail(email) 188 | // _verifyEmailStatus.emit(Event(result)) 189 | // } 190 | // } 191 | // } 192 | // 193 | // } 194 | // 195 | // fun verifyCode(email: String, codeEmail: PinView) { 196 | // viewModelScope.launch(dispatcher) { 197 | // val code = codeEmail.text.toString() 198 | // when { 199 | // code.isEmpty() -> { 200 | // _verifyCodeStatus.emit(Event(Resource.Error("Code is require"))) 201 | // } 202 | // 203 | // code.length<4 -> { 204 | // _verifyCodeStatus.emit(Event(Resource.Error("Code content 4 numbers"))) 205 | // } 206 | // else -> { 207 | // _verifyCodeStatus.emit(Event(Resource.Loading())) 208 | // val result = repository.verifyCode(email,code) 209 | // _verifyCodeStatus.emit(Event(result)) 210 | // } 211 | // } 212 | // } 213 | // 214 | // } 215 | // 216 | // fun resetPassword(inputTextLayoutPassword: TextInputLayout, email: String, code: String) { 217 | // viewModelScope.launch { 218 | // val password=inputTextLayoutPassword.editText?.text.toString() 219 | // when{ 220 | // password.isEmpty() -> { 221 | // _resetPasswordStatus.emit(Event(Resource.Error("Password is require"))) 222 | // inputTextLayoutPassword.isHelperTextEnabled = true 223 | // inputTextLayoutPassword.helperText = "Require*" 224 | // } 225 | // !MyValidation.validatePass(context, inputTextLayoutPassword) -> { 226 | // _resetPasswordStatus.emit(Event(Resource.Error(inputTextLayoutPassword.helperText.toString()))) 227 | // } 228 | // else ->{ 229 | // _resetPasswordStatus.emit(Event(Resource.Loading())) 230 | // val result = repository.resetPassword(password,email,code) 231 | // _resetPasswordStatus.emit(Event(result)) 232 | // } 233 | // } 234 | // } 235 | // } 236 | } -------------------------------------------------------------------------------- /app/src/main/java/com/developers/noteappktorserver/ui/viewmodels/HomeViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.developers.noteappktorserver.ui.viewmodels 2 | 3 | import android.content.Context 4 | import androidx.lifecycle.ViewModel 5 | import androidx.lifecycle.viewModelScope 6 | import com.developers.noteappktorserver.entities.MyResponse 7 | import com.developers.noteappktorserver.entities.Note 8 | import com.developers.noteappktorserver.entities.User 9 | import com.developers.noteappktorserver.helpers.Event 10 | import com.developers.noteappktorserver.qualifiers.MainThread 11 | import com.developers.noteappktorserver.repositories.AuthenticationRepository 12 | import com.developers.noteappktorserver.repositories.HomeRepository 13 | import com.developers.noteappktorserver.repositories.NoteRepository 14 | import com.developers.shopapp.helpers.Resource 15 | import dagger.hilt.android.lifecycle.HiltViewModel 16 | import dagger.hilt.android.qualifiers.ApplicationContext 17 | import kotlinx.coroutines.CoroutineDispatcher 18 | import kotlinx.coroutines.flow.MutableStateFlow 19 | import kotlinx.coroutines.launch 20 | import javax.inject.Inject 21 | 22 | @HiltViewModel 23 | class HomeViewModel@Inject constructor( 24 | @ApplicationContext val context: Context, 25 | private val repository: HomeRepository, 26 | private val noteRepository: NoteRepository, 27 | private val authRepository: AuthenticationRepository, 28 | @MainThread 29 | private val dispatcher: CoroutineDispatcher 30 | 31 | ) : ViewModel() { 32 | 33 | private val _notesStatus = 34 | MutableStateFlow>>>>(Event(Resource.Init())) 35 | val notesStatus: MutableStateFlow>>>> = _notesStatus 36 | 37 | private val _deleteNoteStatus = 38 | MutableStateFlow>>>(Event(Resource.Init())) 39 | val deleteNoteStatus: MutableStateFlow>>> = _deleteNoteStatus 40 | private val _userInfoStatus = 41 | MutableStateFlow>>>(Event(Resource.Init())) 42 | val userInfoStatus: MutableStateFlow>>> = _userInfoStatus 43 | 44 | init { 45 | getNotes() 46 | myProfile() 47 | } 48 | 49 | private fun myProfile(){ 50 | viewModelScope.launch { 51 | userInfoStatus.emit(Event(Resource.Loading())) 52 | 53 | val result=authRepository.getProfile(); 54 | userInfoStatus.emit(Event(result)) 55 | 56 | } 57 | } 58 | 59 | fun getNotes(){ 60 | viewModelScope.launch(dispatcher) { 61 | _notesStatus.emit(Event(Resource.Loading())) 62 | val result=repository.getNotes() 63 | _notesStatus.emit(Event(result)) 64 | } 65 | } 66 | 67 | fun searchNote(toString: String) { 68 | 69 | } 70 | 71 | fun deleteNote(noteId: Int) { 72 | viewModelScope.launch(dispatcher) { 73 | _deleteNoteStatus.emit(Event(Resource.Loading())) 74 | val result= noteRepository.deleteNote(noteId) 75 | _deleteNoteStatus.emit(Event(result)) 76 | } 77 | } 78 | 79 | } -------------------------------------------------------------------------------- /app/src/main/java/com/developers/noteappktorserver/utils/BaseEXT.kt: -------------------------------------------------------------------------------- 1 | package com.developers.noteappktorserver.utils 2 | 3 | import android.content.Context 4 | import android.net.ConnectivityManager 5 | import androidx.appcompat.app.AppCompatDelegate 6 | import dagger.hilt.android.qualifiers.ApplicationContext 7 | import kotlinx.coroutines.flow.Flow 8 | import kotlinx.coroutines.flow.flow 9 | import okhttp3.Call 10 | import okhttp3.ResponseBody 11 | import retrofit2.HttpException; 12 | import java.io.IOException 13 | import java.net.SocketTimeoutException 14 | import java.text.SimpleDateFormat 15 | import java.util.* 16 | 17 | fun isNetworkConnected(@ApplicationContext context: Context): Flow = flow { 18 | val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 19 | cm?.let { 20 | val activeNetwork = cm.activeNetworkInfo 21 | emit(activeNetwork != null && activeNetwork.isConnectedOrConnecting) 22 | } 23 | 24 | emit(false) 25 | } 26 | 27 | fun setupTheme(isDarkMode: Boolean) { 28 | if (isDarkMode) { 29 | AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) 30 | } else { 31 | AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) 32 | } 33 | } 34 | 35 | fun errorMessageHandler(call: Call, t: Throwable): Flow =flow{ 36 | if (t is SocketTimeoutException) { 37 | emit( "Connection timeout, Please try again!") 38 | } else if (t is HttpException) { 39 | val body: ResponseBody = (t as HttpException).response()?.errorBody()!! 40 | try { 41 | emit( body.toString()) 42 | } catch (e: IOException) { 43 | e.printStackTrace() 44 | } 45 | } else if (t is IOException) { 46 | emit( "Request timeout, Please try again!") 47 | } else { 48 | //Call was cancelled by user 49 | if (call.isCanceled()) { 50 | emit( "Call was cancelled forcefully, Please try again!") 51 | } else { 52 | emit( "Network Problem, Please try again!") 53 | } 54 | } 55 | emit( "Network Problem, Please try again!") 56 | } 57 | 58 | fun dateFormatter(Date: String?): Long { 59 | 60 | Date?.let { 61 | 62 | val formatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm") 63 | val date: Date = formatter.parse(Date) 64 | return date.time 65 | } 66 | return 0 67 | } 68 | 69 | 70 | -------------------------------------------------------------------------------- /app/src/main/java/com/developers/noteappktorserver/utils/FragmentEXT.kt: -------------------------------------------------------------------------------- 1 | package com.developers.shopapp.utils 2 | 3 | import android.app.Activity 4 | import androidx.appcompat.app.AppCompatDelegate 5 | import androidx.fragment.app.Fragment 6 | import com.google.android.material.snackbar.Snackbar 7 | 8 | fun Fragment.snackbar(message:String){ 9 | Snackbar.make( 10 | requireView(), 11 | message, 12 | Snackbar.LENGTH_LONG 13 | ).show() 14 | 15 | } 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/developers/noteappktorserver/utils/KeyBordEXT.kt: -------------------------------------------------------------------------------- 1 | package com.developers.shopapp.utils 2 | 3 | import android.content.Context 4 | import android.view.View 5 | import android.view.inputmethod.InputMethodManager 6 | 7 | fun View.hideKeyboard() { 8 | val inputManager = 9 | context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager 10 | inputManager.hideSoftInputFromWindow(windowToken, 0) 11 | } -------------------------------------------------------------------------------- /app/src/main/java/com/developers/noteappktorserver/utils/NoteUtility.kt: -------------------------------------------------------------------------------- 1 | package com.developers.noteappktorserver.utils 2 | 3 | import android.Manifest 4 | import android.content.Context 5 | import android.os.Build 6 | import androidx.annotation.RequiresApi 7 | import pub.devrel.easypermissions.EasyPermissions 8 | 9 | object NoteUtility { 10 | 11 | 12 | fun hasReadExternalStoragePermissions(context: Context) = 13 | when { 14 | Build.VERSION.SDK_INT < Build.VERSION_CODES.Q -> { 15 | EasyPermissions.hasPermissions( 16 | context, 17 | Manifest.permission.READ_EXTERNAL_STORAGE 18 | ) 19 | } 20 | else -> { 21 | EasyPermissions.hasPermissions( 22 | context, 23 | Manifest.permission.READ_EXTERNAL_STORAGE 24 | ) 25 | } 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/developers/noteappktorserver/utils/constants.kt: -------------------------------------------------------------------------------- 1 | package com.developers.noteappktorserver.utils 2 | 3 | import android.content.Context 4 | import androidx.datastore.preferences.core.stringPreferencesKey 5 | import java.util.* 6 | 7 | 8 | object Constants { 9 | val ACTION_LOGIN_FRAGMENT_AFTER_LOGOUT: String="ACTION_LOGIN_FRAGMENT_AFTER_LOGOUT" 10 | const val TAG = "GAMALRAGAB" 11 | const val REQUEST_CODE_READ_EXTERNAL_STORAGE_PERMISSIONS=123 12 | const val USERS_INFO_FILE: String = "USER_INFO" 13 | const val BASE_URL = "http://45da-197-38-125-238.ngrok.io/v1/" 14 | val USER_TOKEN = stringPreferencesKey("USER_TOKEN") 15 | val USER_EMAIL = stringPreferencesKey("USER_EMAIL") 16 | val USER_PASSWORD = stringPreferencesKey("USER_PASSWORD") 17 | const val IMAGE_URL = 18 | "data:image/jpeg;base64,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" 19 | 20 | 21 | const val MIN_TITLENOTE_LENGTH=3 22 | const val MAX_TITLENOTE_LENGTH=50 23 | 24 | const val MIN_SUBTITLE_LENGTH=3 25 | const val MAX_SUBTITLE_LENGTH=40 26 | const val SEARCH_TIME_DELAY= 500L 27 | 28 | 29 | 30 | private const val SECOND_MILLIS = 1000 31 | private const val MINUTE_MILLIS = 60 * SECOND_MILLIS 32 | private const val HOUR_MILLIS = 60 * MINUTE_MILLIS 33 | private const val DAY_MILLIS = 24 * HOUR_MILLIS 34 | 35 | fun getTimeAgo(time: Long, ctx: Context?): String? { 36 | var time = time 37 | if (time < 1000000000000L) { 38 | // if timestamp given in seconds, convert to millis 39 | time *= 1000 40 | } 41 | val now: Long = Date().time 42 | if (time > now || time <= 0) { 43 | return null 44 | } 45 | 46 | // TODO: localize 47 | val diff = now - time 48 | return if (diff < MINUTE_MILLIS) { 49 | "just now" 50 | } else if (diff < 2 * MINUTE_MILLIS) { 51 | "a minute ago" 52 | } else if (diff < 50 * MINUTE_MILLIS) { 53 | " ${diff / MINUTE_MILLIS} minutes ago" 54 | } else if (diff < 90 * MINUTE_MILLIS) { 55 | "an hour ago" 56 | } else if (diff < 24 * HOUR_MILLIS) { 57 | " ${diff / HOUR_MILLIS} hours ago" 58 | } else if (diff < 48 * HOUR_MILLIS) { 59 | "yesterday" 60 | } else { 61 | "${diff / DAY_MILLIS} days ago" 62 | } 63 | } 64 | 65 | private var suffixes: NavigableMap = TreeMap() 66 | 67 | fun init() { 68 | suffixes[1_000L] = "k" 69 | suffixes[1_000_000L] = "M" 70 | suffixes[1_000_000_000L] = "G" 71 | suffixes[1_000_000_000_000L] = "T" 72 | suffixes[1_000_000_000_000_000L] = "P" 73 | suffixes[1_000_000_000_000_000_000L] = "E" 74 | } 75 | 76 | fun format(value: Long): String { 77 | init() 78 | //Long.MIN_VALUE == -Long.MIN_VALUE so we need an adjustment here 79 | if (value == Long.MIN_VALUE) return format(Long.MIN_VALUE + 1) 80 | if (value < 0) return "-" + format(-value) 81 | if (value < 1000) return java.lang.Long.toString(value) //deal with easy case 82 | val e = suffixes.floorEntry(value) 83 | val divideBy = e.key 84 | val suffix = e.value 85 | val truncated = value / (divideBy / 10) //the number part of the output times 10 86 | val hasDecimal = truncated < 100 && truncated / 10.0 != (truncated / 10).toDouble() 87 | return if (hasDecimal) (truncated / 10.0).toString() + suffix else (truncated / 10).toString() + suffix 88 | } 89 | 90 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-anydpi/back_icon.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-anydpi/emai_icon.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-anydpi/lock_icon.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-anydpi/logout.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-anydpi/user_icon.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/back_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamalragab21/NoteAppWithApiKtor/70c72cbd073439eae23437b25540981ab6901887/app/src/main/res/drawable-hdpi/back_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/emai_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamalragab21/NoteAppWithApiKtor/70c72cbd073439eae23437b25540981ab6901887/app/src/main/res/drawable-hdpi/emai_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/lock_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamalragab21/NoteAppWithApiKtor/70c72cbd073439eae23437b25540981ab6901887/app/src/main/res/drawable-hdpi/lock_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/logout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamalragab21/NoteAppWithApiKtor/70c72cbd073439eae23437b25540981ab6901887/app/src/main/res/drawable-hdpi/logout.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/user_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamalragab21/NoteAppWithApiKtor/70c72cbd073439eae23437b25540981ab6901887/app/src/main/res/drawable-hdpi/user_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/back_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamalragab21/NoteAppWithApiKtor/70c72cbd073439eae23437b25540981ab6901887/app/src/main/res/drawable-mdpi/back_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/emai_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamalragab21/NoteAppWithApiKtor/70c72cbd073439eae23437b25540981ab6901887/app/src/main/res/drawable-mdpi/emai_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_error.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_image.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/lock_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamalragab21/NoteAppWithApiKtor/70c72cbd073439eae23437b25540981ab6901887/app/src/main/res/drawable-mdpi/lock_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/logout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamalragab21/NoteAppWithApiKtor/70c72cbd073439eae23437b25540981ab6901887/app/src/main/res/drawable-mdpi/logout.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/user_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamalragab21/NoteAppWithApiKtor/70c72cbd073439eae23437b25540981ab6901887/app/src/main/res/drawable-mdpi/user_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/back_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamalragab21/NoteAppWithApiKtor/70c72cbd073439eae23437b25540981ab6901887/app/src/main/res/drawable-xhdpi/back_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/emai_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamalragab21/NoteAppWithApiKtor/70c72cbd073439eae23437b25540981ab6901887/app/src/main/res/drawable-xhdpi/emai_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/lock_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamalragab21/NoteAppWithApiKtor/70c72cbd073439eae23437b25540981ab6901887/app/src/main/res/drawable-xhdpi/lock_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/logout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamalragab21/NoteAppWithApiKtor/70c72cbd073439eae23437b25540981ab6901887/app/src/main/res/drawable-xhdpi/logout.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/user_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamalragab21/NoteAppWithApiKtor/70c72cbd073439eae23437b25540981ab6901887/app/src/main/res/drawable-xhdpi/user_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/back_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamalragab21/NoteAppWithApiKtor/70c72cbd073439eae23437b25540981ab6901887/app/src/main/res/drawable-xxhdpi/back_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/emai_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamalragab21/NoteAppWithApiKtor/70c72cbd073439eae23437b25540981ab6901887/app/src/main/res/drawable-xxhdpi/emai_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_copy.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_done.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/lock_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamalragab21/NoteAppWithApiKtor/70c72cbd073439eae23437b25540981ab6901887/app/src/main/res/drawable-xxhdpi/lock_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/logout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamalragab21/NoteAppWithApiKtor/70c72cbd073439eae23437b25540981ab6901887/app/src/main/res/drawable-xxhdpi/logout.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/user_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamalragab21/NoteAppWithApiKtor/70c72cbd073439eae23437b25540981ab6901887/app/src/main/res/drawable-xxhdpi/user_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_add_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_bottom_sheet.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_default_note.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 9 | 12 | 13 | 14 | 20 | 22 | 26 | 27 | 28 | 29 | 35 | 37 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_indicator.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_note.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 9 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_note2.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 9 | 12 | 13 | 14 | 20 | 22 | 26 | 27 | 28 | 29 | 35 | 37 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_note3.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 9 | 12 | 13 | 14 | 20 | 22 | 26 | 27 | 28 | 29 | 35 | 37 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_note4.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 9 | 12 | 13 | 14 | 20 | 22 | 26 | 27 | 28 | 29 | 35 | 37 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_note5.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 9 | 12 | 13 | 14 | 20 | 22 | 26 | 27 | 28 | 29 | 35 | 37 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_save_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_search.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/card_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/edit_text_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add_circle_outline.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add_image.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add_link.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add_main.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_alert.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_delete_forever_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_search.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_web.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/font/brandon_medium.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamalragab21/NoteAppWithApiKtor/70c72cbd073439eae23437b25540981ab6901887/app/src/main/res/font/brandon_medium.otf -------------------------------------------------------------------------------- /app/src/main/res/font/israr_syria.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamalragab21/NoteAppWithApiKtor/70c72cbd073439eae23437b25540981ab6901887/app/src/main/res/font/israr_syria.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/poppins.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/font/poppins_bold.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/font/rta_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamalragab21/NoteAppWithApiKtor/70c72cbd073439eae23437b25540981ab6901887/app/src/main/res/font/rta_regular.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/uber_move.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamalragab21/NoteAppWithApiKtor/70c72cbd073439eae23437b25540981ab6901887/app/src/main/res/font/uber_move.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/uber_move_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamalragab21/NoteAppWithApiKtor/70c72cbd073439eae23437b25540981ab6901887/app/src/main/res/font/uber_move_bold.ttf -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_create_note.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | 17 | 21 | 22 | 35 | 36 | 50 | 51 | 69 | 70 | 81 | 82 | 104 | 105 | 114 | 115 | 116 | 130 | 131 | 143 | 144 | 157 | 158 | 169 | 170 | 189 | 190 | 200 | 201 | 202 | 203 | 204 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 20 | 21 | 36 | 37 | 47 | 48 | 64 | 65 | 72 | 73 | 88 | 89 | 90 | 91 | 102 | 103 | 114 | 126 | 127 | 128 | 140 | 141 | 150 | 151 | 158 | 159 | 167 | 168 | 176 | 185 | 186 | 187 | 188 | 189 | 202 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 19 | 20 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_container_note.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 19 | 20 | 28 | 29 | 40 | 41 | 53 | 54 | 67 | 78 | 79 | 89 | 90 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_dialog_add_url.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 17 | 18 | 29 | 30 | 45 | 46 | 65 | 66 | 67 | 68 |