diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2cf9b31 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/app/release diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..a1b5ab6 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +D Note \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..fb7f4a8 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..a2d7c21 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..875a112 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,23 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..403cbaf --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..d39fcb7 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,107 @@ +plugins { + id 'com.android.application' + id 'org.jetbrains.kotlin.android' + id 'kotlin-kapt' + id 'dagger.hilt.android.plugin' + id 'com.google.devtools.ksp' version '1.7.10-1.0.6' +} + +android { + compileSdk 33 + + defaultConfig { + applicationId "com.mahmoudrh.roomxml" + minSdk 22 + targetSdk 33 + versionCode 4 + versionName "2.0.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables { + useSupportLibrary true + } + } + + buildTypes { + release { + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + buildFeatures { + compose true + } + composeOptions { + kotlinCompilerExtensionVersion compose_version + } + packagingOptions { + resources { + excludes += '/META-INF/{AL2.0,LGPL2.1}' + } + } + + applicationVariants.all { variant -> + kotlin.sourceSets { + getByName(variant.name) { + kotlin.srcDir("build/generated/ksp/${variant.name}/kotlin") + } + } + } +} + +dependencies { + + + + //-> Material Icons + implementation "androidx.compose.material:material-icons-extended:$compose_version" + + + //-> Compose Destinations Library + implementation "io.github.raamcosta.compose-destinations:core:$nav_destinations_version" + ksp "io.github.raamcosta.compose-destinations:ksp:$nav_destinations_version" + + //-> RoomDB + implementation "androidx.room:room-runtime:$room_version" + kapt "androidx.room:room-compiler:$room_version" + implementation "androidx.room:room-ktx:$room_version" + + // ViewModel utilities for Compose + implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.6.0-alpha01" + + //-> Hilt DI + implementation "com.google.dagger:hilt-android:$hilt_version" + kapt "com.google.dagger:hilt-compiler:$hilt_version" + kapt "androidx.hilt:hilt-compiler:1.0.0" + implementation 'androidx.hilt:hilt-navigation-compose:1.0.0' + + + // ACRA crash report library + implementation "ch.acra:acra-mail:5.9.5" + implementation "ch.acra:acra-dialog:5.9.5" + + + + implementation 'androidx.core:core-ktx:1.7.0' + implementation "androidx.compose.ui:ui:$compose_version" + implementation "androidx.compose.ui:ui-util:$compose_version" + implementation 'androidx.compose.material3:material3:1.0.0-beta01' + + implementation 'androidx.appcompat:appcompat:1.5.0' + implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' + implementation 'androidx.activity:activity-compose:1.5.1' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" + debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" + debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version" +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/com/mahmoudrh/roomxml/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/mahmoudrh/roomxml/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..605c080 --- /dev/null +++ b/app/src/androidTest/java/com/mahmoudrh/roomxml/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.mahmoudrh.roomxml + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.mahmoudrh.roomxml", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..f41b85d --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/mahmoudrh/roomxml/application/NotesApplication.kt b/app/src/main/java/com/mahmoudrh/roomxml/application/NotesApplication.kt new file mode 100644 index 0000000..8453bfa --- /dev/null +++ b/app/src/main/java/com/mahmoudrh/roomxml/application/NotesApplication.kt @@ -0,0 +1,40 @@ +package com.mahmoudrh.roomxml.application + +import android.app.Application +import android.content.Context +import com.mahmoudrh.roomxml.BuildConfig +import com.mahmoudrh.roomxml.R +import dagger.hilt.android.HiltAndroidApp +import org.acra.config.dialog +import org.acra.config.mailSender +import org.acra.data.StringFormat +import org.acra.ktx.initAcra + +@HiltAndroidApp +class NotesApplication: Application() { + + override fun attachBaseContext(base: Context?) { + super.attachBaseContext(base) + + initAcra { + buildConfigClass = BuildConfig::class.java + reportFormat = StringFormat.JSON + + dialog { + text = getString(R.string.dialog_text) + title = getString(R.string.dialog_title) + positiveButtonText = getString(R.string.dialog_positive) + negativeButtonText = getString(R.string.dialog_negative) + commentPrompt = getString(R.string.dialog_comment) + resIcon = R.drawable.ic_dialog_icon + resTheme = R.style.Theme_DNote + } + + mailSender { + mailTo = "dnote.developer@gmail.com" + subject = "D-Note Crash Report" + reportFileName = "Crash.txt" + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mahmoudrh/roomxml/data/local/NoteDAO.kt b/app/src/main/java/com/mahmoudrh/roomxml/data/local/NoteDAO.kt new file mode 100644 index 0000000..1dcb1aa --- /dev/null +++ b/app/src/main/java/com/mahmoudrh/roomxml/data/local/NoteDAO.kt @@ -0,0 +1,30 @@ +package com.mahmoudrh.roomxml.data.local + +import androidx.room.* +import com.mahmoudrh.roomxml.domain.models.Note +import kotlinx.coroutines.flow.Flow + +@Dao +interface NoteDAO { + + @Insert + suspend fun insertNote(note: Note) + + @Update + suspend fun updateNote(note: Note) + + @Delete + suspend fun deleteNote(note: Note) + + @Query("DELETE FROM notes") + suspend fun deleteAll() + + @Query("SELECT * FROM notes") + fun getAllNotes(): Flow> + + @Query("SELECT * FROM notes WHERE title LIKE '%' || :searchWord || '%'") + fun searchNotes(searchWord: String? = null): Flow> + + + +} \ No newline at end of file diff --git a/app/src/main/java/com/mahmoudrh/roomxml/data/local/NotesDB.kt b/app/src/main/java/com/mahmoudrh/roomxml/data/local/NotesDB.kt new file mode 100644 index 0000000..867f735 --- /dev/null +++ b/app/src/main/java/com/mahmoudrh/roomxml/data/local/NotesDB.kt @@ -0,0 +1,14 @@ +package com.mahmoudrh.roomxml.data.local + +import androidx.room.Database +import androidx.room.RoomDatabase +import com.mahmoudrh.roomxml.domain.models.Note + +@Database(entities = [Note::class], version = 1, exportSchema = false) +abstract class NotesDB : RoomDatabase() { + abstract val noteDAO: NoteDAO + + companion object{ + const val DATABASE_NAME = "NotesDB" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mahmoudrh/roomxml/data/repository/NotesRepositoryImpl.kt b/app/src/main/java/com/mahmoudrh/roomxml/data/repository/NotesRepositoryImpl.kt new file mode 100644 index 0000000..dcdd9a6 --- /dev/null +++ b/app/src/main/java/com/mahmoudrh/roomxml/data/repository/NotesRepositoryImpl.kt @@ -0,0 +1,33 @@ +package com.mahmoudrh.roomxml.data.repository + +import com.mahmoudrh.roomxml.data.local.NoteDAO +import com.mahmoudrh.roomxml.domain.models.Note +import com.mahmoudrh.roomxml.domain.repository.NotesRepository +import kotlinx.coroutines.flow.Flow + +class NotesRepositoryImpl(private val dao: NoteDAO) : NotesRepository { + + override suspend fun insert(note: Note) { + dao.insertNote(note) + } + + override suspend fun update(note: Note) { + dao.updateNote(note) + } + + override suspend fun delete(note: Note) { + dao.deleteNote(note) + } + + override suspend fun deleteAll() { + dao.deleteAll() + } + + override fun searchNotes(searchWord: String): Flow> { + return dao.searchNotes(searchWord) + } + + override fun getAllNotes(): Flow> { + return dao.getAllNotes() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mahmoudrh/roomxml/di/AppModule.kt b/app/src/main/java/com/mahmoudrh/roomxml/di/AppModule.kt new file mode 100644 index 0000000..038c258 --- /dev/null +++ b/app/src/main/java/com/mahmoudrh/roomxml/di/AppModule.kt @@ -0,0 +1,44 @@ +package com.mahmoudrh.roomxml.di + +import android.app.Application +import androidx.room.Room +import com.mahmoudrh.roomxml.data.local.NotesDB +import com.mahmoudrh.roomxml.data.repository.NotesRepositoryImpl +import com.mahmoudrh.roomxml.domain.repository.NotesRepository +import com.mahmoudrh.roomxml.domain.usecases.* +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object AppModule { + + @Provides + @Singleton + fun provideDataBase(app:Application): NotesDB{ + return Room.databaseBuilder(app,NotesDB::class.java,NotesDB.DATABASE_NAME).build() + } + + + @Provides + @Singleton + fun provideNoteRepository(database:NotesDB): NotesRepository{ + return NotesRepositoryImpl(database.noteDAO) + } + + @Provides + @Singleton + fun provideNoteUseCases(repository:NotesRepository):NoteUseCases{ + return NoteUseCases( + getAllNotes = GetAllNotes(repository), + deleteNote = DeleteNote(repository), + updateNote = UpdateNote(repository), + searchNotes = SearchNotes(repository), + deleteAllNotes = DeleteAllNotes(repository), + insertNote = InsertNote(repository) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mahmoudrh/roomxml/domain/models/Note.kt b/app/src/main/java/com/mahmoudrh/roomxml/domain/models/Note.kt new file mode 100644 index 0000000..637dc3c --- /dev/null +++ b/app/src/main/java/com/mahmoudrh/roomxml/domain/models/Note.kt @@ -0,0 +1,43 @@ +package com.mahmoudrh.roomxml.domain.models + +import android.os.Parcel +import android.os.Parcelable +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "notes") +data class Note( + @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "id") var id:Int = 0, + @ColumnInfo(name = "title") val title: String, + @ColumnInfo(name = "content") val content: String, + @ColumnInfo(name = "date") val date: String, +) : Parcelable { + constructor(parcel: Parcel) : this( + parcel.readInt(), + parcel.readString()?:"no title", + parcel.readString()?:"no content", + parcel.readString()?:"no date", + ) + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeInt(id) + parcel.writeString(title) + parcel.writeString(content) + parcel.writeString(date) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): Note { + return Note(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} diff --git a/app/src/main/java/com/mahmoudrh/roomxml/domain/repository/NotesRepository.kt b/app/src/main/java/com/mahmoudrh/roomxml/domain/repository/NotesRepository.kt new file mode 100644 index 0000000..0ffc919 --- /dev/null +++ b/app/src/main/java/com/mahmoudrh/roomxml/domain/repository/NotesRepository.kt @@ -0,0 +1,19 @@ +package com.mahmoudrh.roomxml.domain.repository + +import com.mahmoudrh.roomxml.domain.models.Note +import kotlinx.coroutines.flow.Flow + +interface NotesRepository { + + suspend fun insert(note: Note) + + suspend fun update(note: Note) + + suspend fun delete(note: Note) + + suspend fun deleteAll() + + fun searchNotes(searchWord: String): Flow> + + fun getAllNotes(): Flow> +} \ No newline at end of file diff --git a/app/src/main/java/com/mahmoudrh/roomxml/domain/usecases/DeleteAllNotes.kt b/app/src/main/java/com/mahmoudrh/roomxml/domain/usecases/DeleteAllNotes.kt new file mode 100644 index 0000000..3acbe14 --- /dev/null +++ b/app/src/main/java/com/mahmoudrh/roomxml/domain/usecases/DeleteAllNotes.kt @@ -0,0 +1,9 @@ +package com.mahmoudrh.roomxml.domain.usecases + +import com.mahmoudrh.roomxml.domain.repository.NotesRepository + +class DeleteAllNotes constructor(private val repository: NotesRepository) { + suspend operator fun invoke(){ + repository.deleteAll() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mahmoudrh/roomxml/domain/usecases/DeleteNote.kt b/app/src/main/java/com/mahmoudrh/roomxml/domain/usecases/DeleteNote.kt new file mode 100644 index 0000000..79081b0 --- /dev/null +++ b/app/src/main/java/com/mahmoudrh/roomxml/domain/usecases/DeleteNote.kt @@ -0,0 +1,10 @@ +package com.mahmoudrh.roomxml.domain.usecases + +import com.mahmoudrh.roomxml.domain.models.Note +import com.mahmoudrh.roomxml.domain.repository.NotesRepository + +class DeleteNote(private val repository: NotesRepository){ + suspend operator fun invoke(note: Note){ + repository.delete(note) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mahmoudrh/roomxml/domain/usecases/GetAllNotes.kt b/app/src/main/java/com/mahmoudrh/roomxml/domain/usecases/GetAllNotes.kt new file mode 100644 index 0000000..ecee10d --- /dev/null +++ b/app/src/main/java/com/mahmoudrh/roomxml/domain/usecases/GetAllNotes.kt @@ -0,0 +1,29 @@ +package com.mahmoudrh.roomxml.domain.usecases + +import com.mahmoudrh.roomxml.domain.models.Note +import com.mahmoudrh.roomxml.domain.repository.NotesRepository +import com.mahmoudrh.roomxml.domain.utils.OrderBy +import com.mahmoudrh.roomxml.domain.utils.OrderType +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +class GetAllNotes(private val repository: NotesRepository) { + operator fun invoke(orderBy: OrderBy = OrderBy.Date(OrderType.Descending)): Flow> { + return repository.getAllNotes().map { notesList -> + when (orderBy.orderType) { + is OrderType.Descending -> { + when (orderBy) { + is OrderBy.Title -> notesList.sortedByDescending { it.title.lowercase() } + is OrderBy.Date -> notesList.sortedByDescending { it.date.toLong() } + } + } + is OrderType.Ascending -> { + when (orderBy) { + is OrderBy.Title -> notesList.sortedBy { it.title.lowercase() } + is OrderBy.Date -> notesList.sortedBy { it.date.toLong() } + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mahmoudrh/roomxml/domain/usecases/InsertNote.kt b/app/src/main/java/com/mahmoudrh/roomxml/domain/usecases/InsertNote.kt new file mode 100644 index 0000000..c3a3e98 --- /dev/null +++ b/app/src/main/java/com/mahmoudrh/roomxml/domain/usecases/InsertNote.kt @@ -0,0 +1,10 @@ +package com.mahmoudrh.roomxml.domain.usecases + +import com.mahmoudrh.roomxml.domain.models.Note +import com.mahmoudrh.roomxml.domain.repository.NotesRepository + +class InsertNote (private val repository: NotesRepository) { + suspend operator fun invoke(note: Note){ + repository.insert(note) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mahmoudrh/roomxml/domain/usecases/NoteUseCases.kt b/app/src/main/java/com/mahmoudrh/roomxml/domain/usecases/NoteUseCases.kt new file mode 100644 index 0000000..a30ebf4 --- /dev/null +++ b/app/src/main/java/com/mahmoudrh/roomxml/domain/usecases/NoteUseCases.kt @@ -0,0 +1,10 @@ +package com.mahmoudrh.roomxml.domain.usecases + +data class NoteUseCases( + val getAllNotes: GetAllNotes, + val deleteNote: DeleteNote, + val updateNote: UpdateNote, + val searchNotes: SearchNotes, + val deleteAllNotes: DeleteAllNotes, + val insertNote: InsertNote +) \ No newline at end of file diff --git a/app/src/main/java/com/mahmoudrh/roomxml/domain/usecases/SearchNotes.kt b/app/src/main/java/com/mahmoudrh/roomxml/domain/usecases/SearchNotes.kt new file mode 100644 index 0000000..9f10889 --- /dev/null +++ b/app/src/main/java/com/mahmoudrh/roomxml/domain/usecases/SearchNotes.kt @@ -0,0 +1,11 @@ +package com.mahmoudrh.roomxml.domain.usecases + +import com.mahmoudrh.roomxml.domain.models.Note +import com.mahmoudrh.roomxml.domain.repository.NotesRepository +import kotlinx.coroutines.flow.Flow + +class SearchNotes constructor(private val repository: NotesRepository) { + operator fun invoke(searchWord:String): Flow> { + return repository.searchNotes(searchWord) + } +} diff --git a/app/src/main/java/com/mahmoudrh/roomxml/domain/usecases/UpdateNote.kt b/app/src/main/java/com/mahmoudrh/roomxml/domain/usecases/UpdateNote.kt new file mode 100644 index 0000000..e888a96 --- /dev/null +++ b/app/src/main/java/com/mahmoudrh/roomxml/domain/usecases/UpdateNote.kt @@ -0,0 +1,10 @@ +package com.mahmoudrh.roomxml.domain.usecases + +import com.mahmoudrh.roomxml.domain.models.Note +import com.mahmoudrh.roomxml.domain.repository.NotesRepository + +class UpdateNote(private val repository: NotesRepository) { + suspend operator fun invoke(note: Note){ + repository.update(note) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mahmoudrh/roomxml/domain/utils/OrderBy.kt b/app/src/main/java/com/mahmoudrh/roomxml/domain/utils/OrderBy.kt new file mode 100644 index 0000000..1965e05 --- /dev/null +++ b/app/src/main/java/com/mahmoudrh/roomxml/domain/utils/OrderBy.kt @@ -0,0 +1,58 @@ +package com.mahmoudrh.roomxml.domain.utils + +sealed class OrderBy(val orderType: OrderType) { + class Title(orderType: OrderType) : OrderBy(orderType) + class Date(orderType: OrderType) : OrderBy(orderType) + + fun copy(orderType: OrderType): OrderBy { + return when (this) { + is Title -> Title(orderType) + is Date -> Date(orderType) + } + } + + companion object { + const val DATE = 0 + const val TITLE = 1 + + fun getOrderBy(orderType: Int, orderBy: Int): OrderBy { + val ot: OrderType = when (orderType) { + OrderType.ASCENDING -> { + OrderType.Ascending + } + OrderType.DESCENDING -> { + OrderType.Descending + } + else -> { + OrderType.Descending + } + } + val ob: OrderBy = when (orderBy) { + DATE -> { + Date(ot) + } + TITLE -> { + Title(ot) + } + else -> { + Date(ot) + } + } + + return ob + } + + fun getOrderByInt(orderBy: OrderBy): Int { + return when (orderBy) { + is Date -> DATE + is Title -> TITLE + } + } + fun getOrderTypeInt(orderBy: OrderBy): Int { + return when (orderBy.orderType) { + OrderType.Ascending -> OrderType.ASCENDING + OrderType.Descending -> OrderType.DESCENDING + } + } + } +} diff --git a/app/src/main/java/com/mahmoudrh/roomxml/domain/utils/OrderType.kt b/app/src/main/java/com/mahmoudrh/roomxml/domain/utils/OrderType.kt new file mode 100644 index 0000000..dc612b7 --- /dev/null +++ b/app/src/main/java/com/mahmoudrh/roomxml/domain/utils/OrderType.kt @@ -0,0 +1,10 @@ +package com.mahmoudrh.roomxml.domain.utils + +sealed class OrderType{ + object Ascending:OrderType() + object Descending:OrderType() + companion object{ + const val DESCENDING = 0 + const val ASCENDING = 1 + } +} diff --git a/app/src/main/java/com/mahmoudrh/roomxml/presentation/MainActivity.kt b/app/src/main/java/com/mahmoudrh/roomxml/presentation/MainActivity.kt new file mode 100644 index 0000000..3ad3bde --- /dev/null +++ b/app/src/main/java/com/mahmoudrh/roomxml/presentation/MainActivity.kt @@ -0,0 +1,31 @@ +package com.mahmoudrh.roomxml.presentation + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.ui.Modifier +import com.mahmoudrh.roomxml.presentation.screens.NavGraphs +import com.mahmoudrh.roomxml.presentation.theme.DNoteTheme +import com.ramcosta.composedestinations.DestinationsNavHost +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + DNoteTheme { + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + DestinationsNavHost(navGraph = NavGraphs.root) + } + } + } + } +} + diff --git a/app/src/main/java/com/mahmoudrh/roomxml/presentation/screens/all_notes/AllNotesEvent.kt b/app/src/main/java/com/mahmoudrh/roomxml/presentation/screens/all_notes/AllNotesEvent.kt new file mode 100644 index 0000000..031c25d --- /dev/null +++ b/app/src/main/java/com/mahmoudrh/roomxml/presentation/screens/all_notes/AllNotesEvent.kt @@ -0,0 +1,11 @@ +package com.mahmoudrh.roomxml.presentation.screens.all_notes + +import com.mahmoudrh.roomxml.domain.models.Note +import com.mahmoudrh.roomxml.domain.utils.OrderBy + +sealed class AllNotesEvent { + data class Order(val order: OrderBy): AllNotesEvent() + data class DeleteNote(val note: Note): AllNotesEvent() + object RestoreNote: AllNotesEvent() + object ToggleOrderSection: AllNotesEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/mahmoudrh/roomxml/presentation/screens/all_notes/AllNotesScreen.kt b/app/src/main/java/com/mahmoudrh/roomxml/presentation/screens/all_notes/AllNotesScreen.kt new file mode 100644 index 0000000..f6de1e2 --- /dev/null +++ b/app/src/main/java/com/mahmoudrh/roomxml/presentation/screens/all_notes/AllNotesScreen.kt @@ -0,0 +1,106 @@ +package com.mahmoudrh.roomxml.presentation.screens.all_notes + +import androidx.compose.animation.* +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import com.mahmoudrh.roomxml.presentation.screens.destinations.NoteScreenDestination +import com.mahmoudrh.roomxml.presentation.screens.destinations.SearchScreenDestination +import com.mahmoudrh.roomxml.presentation.ui_components.* +import com.ramcosta.composedestinations.annotation.Destination +import com.ramcosta.composedestinations.annotation.RootNavGraph +import com.ramcosta.composedestinations.navigation.DestinationsNavigator +import kotlinx.coroutines.launch + + +@RootNavGraph(start = true) +@Destination +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AllNotesScreen( + viewModel: AllNotesViewModel = hiltViewModel(), + navigator: DestinationsNavigator +) { + val notesState = viewModel.notesState.value + val snackBarHostState = remember { SnackbarHostState() } + val scope = rememberCoroutineScope() + Scaffold( + topBar = { + AppTopBars.DefaultTopBar(title = "D Note", actionIcon = Icons.Default.Search) { + navigator.navigate(SearchScreenDestination) + /*TODO Navigate To Search Screen*/ + } + }, + containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp), + floatingActionButton = { + FloatingActionButton(onClick = { navigator.navigate(NoteScreenDestination.route) }) { + Icon(imageVector = Icons.Default.Add, contentDescription = "New Note") + } + }, snackbarHost = { SnackbarHost(snackBarHostState) }) + { paddingValues -> + LazyColumn( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues), + contentPadding = PaddingValues(start = 20.dp, end = 10.dp, top = 10.dp, bottom = 10.dp) + ) { + item { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Sort ", + ) + IconButton(onClick = { viewModel.onEvent(AllNotesEvent.ToggleOrderSection) }) { + Icon(imageVector = Icons.Default.Sort, contentDescription = "Sort") + } + } + AnimatedVisibility( + visible = notesState.isOrderSectionVisible, + enter = expandVertically(), + exit = shrinkVertically() + ) { + OrderSection( + order = notesState.order, + onOrderChange = { + viewModel.onEvent(AllNotesEvent.Order(it)) + } + ) + } + } + items(notesState.notes, key = { it.id }) { + + NoteItem( + modifier = Modifier.padding(vertical = 8.dp), + note = it, + onClick = { navigator.navigate(NoteScreenDestination(it)) }, + onSwipeOut = { noteObject -> + viewModel.onEvent(AllNotesEvent.DeleteNote(noteObject)) + scope.launch { + val result = snackBarHostState.showSnackbar( + message = "Note deleted", + actionLabel = "Undo" + ) + if (result == SnackbarResult.ActionPerformed) { + viewModel.onEvent(AllNotesEvent.RestoreNote) + } + } + } + ) + + } + } + EmptyListScreen(visibility = notesState.isListEmpty, text = "Add Some Notes!") + LoadingScreen(visibility = notesState.isListLoading) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mahmoudrh/roomxml/presentation/screens/all_notes/AllNotesViewModel.kt b/app/src/main/java/com/mahmoudrh/roomxml/presentation/screens/all_notes/AllNotesViewModel.kt new file mode 100644 index 0000000..5600b24 --- /dev/null +++ b/app/src/main/java/com/mahmoudrh/roomxml/presentation/screens/all_notes/AllNotesViewModel.kt @@ -0,0 +1,91 @@ +package com.mahmoudrh.roomxml.presentation.screens.all_notes + +import android.app.Application +import android.content.Context +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.mahmoudrh.roomxml.domain.models.Note +import com.mahmoudrh.roomxml.domain.usecases.NoteUseCases +import com.mahmoudrh.roomxml.domain.utils.OrderBy +import com.mahmoudrh.roomxml.domain.utils.OrderType +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class AllNotesViewModel @Inject constructor( + private val noteUseCases: NoteUseCases, + private val application: Application +) : ViewModel() { + private val _notesState = mutableStateOf(NotesState()) + val notesState: State = _notesState + private var recentlyDeletedNote: Note? = null + private var getNotesJob: Job? = null + private val preferences = application.getSharedPreferences("OrderPreferences",Context.MODE_PRIVATE) + init { + val ob = preferences.getInt("OrderBy",OrderBy.DATE) + val ot = preferences.getInt("OrderType",OrderType.DESCENDING) + val orderPreference = OrderBy.getOrderBy(orderType = ot, orderBy = ob) + loadAllNotes(orderPreference) + } + + private fun loadAllNotes(orderBy: OrderBy) { + getNotesJob?.cancel() // cancel if already running + getNotesJob = noteUseCases.getAllNotes(orderBy).onEach { + _notesState.value = notesState.value.copy( + notes = it, + order = orderBy, + isListLoading = false, + isListEmpty = it.isEmpty() + ) + }.launchIn(viewModelScope) + } + + fun onEvent(event: AllNotesEvent) { + when (event) { + is AllNotesEvent.DeleteNote -> { + viewModelScope.launch { + noteUseCases.deleteNote(event.note) + recentlyDeletedNote = event.note + } + } + is AllNotesEvent.Order -> { + if (notesState.value.order::class == event.order::class && + notesState.value.order.orderType == event.order.orderType) { // same order and same order type + return + }else { + loadAllNotes(event.order) + saveIntoSharedPreferences(event.order) + } + } + AllNotesEvent.RestoreNote -> { + recentlyDeletedNote?.let { + viewModelScope.launch { + noteUseCases.insertNote(it) + recentlyDeletedNote = null + } + } + } + AllNotesEvent.ToggleOrderSection -> { + _notesState.value = notesState.value.copy( + isOrderSectionVisible = !notesState.value.isOrderSectionVisible + ) + } + } + } + + private fun saveIntoSharedPreferences(order: OrderBy) { + val ot = OrderBy.getOrderTypeInt(order) + val ob = OrderBy.getOrderByInt(order) + preferences.edit().apply { + putInt("OrderType",ot) + putInt("OrderBy",ob) + apply() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mahmoudrh/roomxml/presentation/screens/all_notes/NotesState.kt b/app/src/main/java/com/mahmoudrh/roomxml/presentation/screens/all_notes/NotesState.kt new file mode 100644 index 0000000..7712423 --- /dev/null +++ b/app/src/main/java/com/mahmoudrh/roomxml/presentation/screens/all_notes/NotesState.kt @@ -0,0 +1,13 @@ +package com.mahmoudrh.roomxml.presentation.screens.all_notes + +import com.mahmoudrh.roomxml.domain.models.Note +import com.mahmoudrh.roomxml.domain.utils.OrderBy +import com.mahmoudrh.roomxml.domain.utils.OrderType + +data class NotesState( + val notes: List = emptyList(), + val order: OrderBy = OrderBy.Date(OrderType.Descending), + val isOrderSectionVisible: Boolean = false, + val isListLoading:Boolean = true, + val isListEmpty:Boolean = false +) \ No newline at end of file diff --git a/app/src/main/java/com/mahmoudrh/roomxml/presentation/screens/note/NoteEvent.kt b/app/src/main/java/com/mahmoudrh/roomxml/presentation/screens/note/NoteEvent.kt new file mode 100644 index 0000000..91718fa --- /dev/null +++ b/app/src/main/java/com/mahmoudrh/roomxml/presentation/screens/note/NoteEvent.kt @@ -0,0 +1,6 @@ +package com.mahmoudrh.roomxml.presentation.screens.note + +sealed class NoteEvent { + object InsertNote :NoteEvent() + object UpdateNote :NoteEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/mahmoudrh/roomxml/presentation/screens/note/NoteScreen.kt b/app/src/main/java/com/mahmoudrh/roomxml/presentation/screens/note/NoteScreen.kt new file mode 100644 index 0000000..626476d --- /dev/null +++ b/app/src/main/java/com/mahmoudrh/roomxml/presentation/screens/note/NoteScreen.kt @@ -0,0 +1,84 @@ +package com.mahmoudrh.roomxml.presentation.screens.note + +import android.widget.Toast +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Check +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusDirection +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import com.mahmoudrh.roomxml.domain.models.Note +import com.mahmoudrh.roomxml.presentation.ui_components.AppTopBars +import com.ramcosta.composedestinations.annotation.Destination +import com.ramcosta.composedestinations.navigation.DestinationsNavigator + +@Destination(navArgsDelegate = NoteNavArgs::class) +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun NoteScreen(viewModel: NoteViewModel = hiltViewModel(), navigator: DestinationsNavigator) { + + val focusManager = LocalFocusManager.current + val eventName = viewModel.eventName + Scaffold(topBar = { + AppTopBars.DefaultTopBar( + title = eventName.value, + onNavigateBack = { navigator.popBackStack() }, + actionIcon = Icons.Default.Check, + onActionClick = { + if (viewModel.isUpdatingNote()) { + viewModel.onEvent(NoteEvent.UpdateNote) + } else { + viewModel.onEvent(NoteEvent.InsertNote) + } + }) + }) { paddingValues -> + Column( + modifier = Modifier + .padding(paddingValues) + .padding(horizontal = 20.dp, vertical = 10.dp) + ) { + OutlinedTextField( + modifier = Modifier.fillMaxWidth(), + value = viewModel.noteTitle.value, + onValueChange = { viewModel.noteTitle.value = it }, + label = { Text(text = "Title") }, + singleLine = true, + placeholder = { Text(text = "Add some title...") }, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), + keyboardActions = KeyboardActions( + onNext = { + focusManager.moveFocus(FocusDirection.Down) + } + ), + isError = viewModel.isTitleError.value + ) + Spacer(modifier = Modifier.size(8.dp)) + OutlinedTextField( + modifier = Modifier.fillMaxSize(), + value = viewModel.noteContent.value, + onValueChange = { viewModel.noteContent.value = it }, + label = { Text(text = "Content") }, + placeholder = { Text(text = "Add some content...") }, + isError = viewModel.isContentError.value + ) + } + } + + if (viewModel.isEventSuccess.value) { + Toast.makeText(LocalContext.current, "${eventName.value} Success", Toast.LENGTH_SHORT) + .show() + navigator.popBackStack() + } + +} + + +data class NoteNavArgs(val note: Note?) \ No newline at end of file diff --git a/app/src/main/java/com/mahmoudrh/roomxml/presentation/screens/note/NoteScreenViewModel.kt b/app/src/main/java/com/mahmoudrh/roomxml/presentation/screens/note/NoteScreenViewModel.kt new file mode 100644 index 0000000..5dab4fc --- /dev/null +++ b/app/src/main/java/com/mahmoudrh/roomxml/presentation/screens/note/NoteScreenViewModel.kt @@ -0,0 +1,83 @@ +package com.mahmoudrh.roomxml.presentation.screens.note + +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.mahmoudrh.roomxml.domain.models.Note +import com.mahmoudrh.roomxml.domain.usecases.NoteUseCases +import com.mahmoudrh.roomxml.presentation.screens.navArgs +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import java.util.* +import javax.inject.Inject + +@HiltViewModel +class NoteViewModel @Inject constructor( + private val noteUseCases: NoteUseCases, + handle: SavedStateHandle, +) : ViewModel() { + val noteTitle = mutableStateOf("") + val noteContent = mutableStateOf("") + val note = handle.navArgs().note + val isTitleError = mutableStateOf(false) + val isContentError = mutableStateOf(false) + val isEventSuccess = mutableStateOf(false) + val eventName = mutableStateOf("Adding New Note") + init { + note?.let { + eventName.value = "Editing Note" + noteTitle.value = it.title + noteContent.value = it.content + } + } + + fun onEvent(event: NoteEvent) { + when (event) { + is NoteEvent.InsertNote -> { + viewModelScope.launch { + isTitleError.value = isTitleEmpty() + isContentError.value = isContentEmpty() + if (isTitleEmpty() || isContentEmpty()) { + return@launch + } else { + noteUseCases.insertNote( + Note( + title = noteTitle.value, + content = noteContent.value, + date = Date().time.toString(), + ) + ) + isEventSuccess.value = true + } + + } + } + is NoteEvent.UpdateNote -> { + viewModelScope.launch { + isTitleError.value = isTitleEmpty() + isContentError.value = isContentEmpty() + if (isTitleEmpty() || isContentEmpty()) { + return@launch + } else { + note?.let { + noteUseCases.updateNote( + it.copy( + title = noteTitle.value, + content = noteContent.value, + ) + ) + isEventSuccess.value = true + } + } + } + } + } + + } + + private fun isTitleEmpty() = noteTitle.value.isEmpty() + private fun isContentEmpty() = noteContent.value.isEmpty() + fun isUpdatingNote() = note != null //if the object passed isn't null, the user is updating an existing note. + +} \ No newline at end of file diff --git a/app/src/main/java/com/mahmoudrh/roomxml/presentation/screens/search/SearchScreen.kt b/app/src/main/java/com/mahmoudrh/roomxml/presentation/screens/search/SearchScreen.kt new file mode 100644 index 0000000..39d5d9f --- /dev/null +++ b/app/src/main/java/com/mahmoudrh/roomxml/presentation/screens/search/SearchScreen.kt @@ -0,0 +1,70 @@ +package com.mahmoudrh.roomxml.presentation.screens.search + + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.surfaceColorAtElevation +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.hilt.navigation.compose.hiltViewModel +import com.mahmoudrh.roomxml.presentation.screens.destinations.NoteScreenDestination +import com.mahmoudrh.roomxml.presentation.ui_components.AppTopBars +import com.mahmoudrh.roomxml.presentation.ui_components.EmptyListScreen +import com.mahmoudrh.roomxml.presentation.ui_components.LoadingScreen +import com.mahmoudrh.roomxml.presentation.ui_components.NoteItem +import com.ramcosta.composedestinations.annotation.Destination +import com.ramcosta.composedestinations.navigation.DestinationsNavigator + +@Destination +@Composable +fun SearchScreen(viewModel: SearchViewModel = hiltViewModel(), navigator: DestinationsNavigator) { + val focusRequester = FocusRequester() + + Column(Modifier.fillMaxSize()) { + AppTopBars.SearchTopBar( + onNavigateBack = { navigator.popBackStack() }, + hint = "Search..", + focusRequester = focusRequester, + searchWord = viewModel.searchWord, + onSearch = { viewModel.search() } + ) + Box(Modifier.background(MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp))) { + LazyColumn( + modifier = Modifier + .fillMaxSize(), + contentPadding = PaddingValues( + start = 20.dp, + end = 10.dp, + top = 10.dp, + bottom = 10.dp + ) + ) { + items(viewModel.resultsList, key = { it.id }) { + NoteItem( + modifier = Modifier.padding(vertical = 8.dp), + note = it, + onClick = { navigator.navigate(NoteScreenDestination(it)) }, + ) + } + } + EmptyListScreen( + visibility = viewModel.isResultsListEmpty.value, + text = "Sorry, We Couldn't Find Any Results.. ", + fontSize = 20.sp + ) + LoadingScreen(visibility = viewModel.isLoading.value) + } + } + + DisposableEffect(Unit) { + focusRequester.requestFocus() + onDispose { } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mahmoudrh/roomxml/presentation/screens/search/SearchViewModel.kt b/app/src/main/java/com/mahmoudrh/roomxml/presentation/screens/search/SearchViewModel.kt new file mode 100644 index 0000000..b7ea1cf --- /dev/null +++ b/app/src/main/java/com/mahmoudrh/roomxml/presentation/screens/search/SearchViewModel.kt @@ -0,0 +1,33 @@ +package com.mahmoudrh.roomxml.presentation.screens.search + +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.mahmoudrh.roomxml.domain.models.Note +import com.mahmoudrh.roomxml.domain.usecases.NoteUseCases +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import javax.inject.Inject + +@HiltViewModel +class SearchViewModel @Inject constructor( + private val noteUseCases: NoteUseCases + ) : ViewModel() { + val searchWord = mutableStateOf("") + var resultsList : List = emptyList() + val isLoading = mutableStateOf(false) + val isResultsListEmpty = mutableStateOf(false) + + private var searchJob:Job? = null + fun search(){ + isLoading.value = true + searchJob?.cancel() + searchJob = noteUseCases.searchNotes(searchWord.value.trim()).onEach { + resultsList = it + isLoading.value = false + isResultsListEmpty.value = it.isEmpty() + }.launchIn(viewModelScope) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mahmoudrh/roomxml/presentation/theme/Color.kt b/app/src/main/java/com/mahmoudrh/roomxml/presentation/theme/Color.kt new file mode 100644 index 0000000..8bdaa8c --- /dev/null +++ b/app/src/main/java/com/mahmoudrh/roomxml/presentation/theme/Color.kt @@ -0,0 +1,11 @@ +package com.mahmoudrh.roomxml.presentation.theme + +import androidx.compose.ui.graphics.Color + +val Purple80 = Color(0xFFD0BCFF) +val PurpleGrey80 = Color(0xFFCCC2DC) +val Pink80 = Color(0xFFEFB8C8) + +val Purple40 = Color(0xFF6650a4) +val PurpleGrey40 = Color(0xFF625b71) +val Pink40 = Color(0xFF7D5260) \ No newline at end of file diff --git a/app/src/main/java/com/mahmoudrh/roomxml/presentation/theme/Theme.kt b/app/src/main/java/com/mahmoudrh/roomxml/presentation/theme/Theme.kt new file mode 100644 index 0000000..2c51204 --- /dev/null +++ b/app/src/main/java/com/mahmoudrh/roomxml/presentation/theme/Theme.kt @@ -0,0 +1,68 @@ +package com.mahmoudrh.roomxml.presentation.theme + +import android.app.Activity +import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.core.view.ViewCompat + +private val DarkColorScheme = darkColorScheme( + primary = Purple80, + secondary = PurpleGrey80, + tertiary = Pink80 +) + +private val LightColorScheme = lightColorScheme( + primary = Purple40, + secondary = PurpleGrey40, + tertiary = Pink40 + + /* Other default colors to override + background = Color(0xFFFFFBFE), + surface = Color(0xFFFFFBFE), + onPrimary = Color.White, + onSecondary = Color.White, + onTertiary = Color.White, + onBackground = Color(0xFF1C1B1F), + onSurface = Color(0xFF1C1B1F), + */ +) + +@Composable +fun DNoteTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + // Dynamic color is available on Android 12+ + dynamicColor: Boolean = true, + content: @Composable () -> Unit +) { + val colorScheme = when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + darkTheme -> DarkColorScheme + else -> LightColorScheme + } + val view = LocalView.current + if (!view.isInEditMode) { + SideEffect { + (view.context as Activity).window.statusBarColor = colorScheme.primary.toArgb() + ViewCompat.getWindowInsetsController(view)?.isAppearanceLightStatusBars = darkTheme + } + } + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/mahmoudrh/roomxml/presentation/theme/Type.kt b/app/src/main/java/com/mahmoudrh/roomxml/presentation/theme/Type.kt new file mode 100644 index 0000000..831fca0 --- /dev/null +++ b/app/src/main/java/com/mahmoudrh/roomxml/presentation/theme/Type.kt @@ -0,0 +1,34 @@ +package com.mahmoudrh.roomxml.presentation.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) + /* Other default text styles to override + titleLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 22.sp, + lineHeight = 28.sp, + letterSpacing = 0.sp + ), + labelSmall = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 11.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp + ) + */ +) \ No newline at end of file diff --git a/app/src/main/java/com/mahmoudrh/roomxml/presentation/ui_components/AppTopBars.kt b/app/src/main/java/com/mahmoudrh/roomxml/presentation/ui_components/AppTopBars.kt new file mode 100644 index 0000000..e6d693e --- /dev/null +++ b/app/src/main/java/com/mahmoudrh/roomxml/presentation/ui_components/AppTopBars.kt @@ -0,0 +1,96 @@ +package com.mahmoudrh.roomxml.presentation.ui_components + + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.unit.dp + + +object AppTopBars { + + @OptIn(ExperimentalMaterial3Api::class) + @Composable + fun DefaultTopBar( + title: String, + onNavigateBack: () -> Unit, + actionIcon: ImageVector, + onActionClick: () -> Unit + ) { + CenterAlignedTopAppBar( + modifier = Modifier.shadow(elevation = 8.dp), + title = { Text(text = title) }, + colors = TopAppBarDefaults.centerAlignedTopAppBarColors( + containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(15.dp), + ), + navigationIcon = { + IconButton(onClick = onNavigateBack) { + Icon(Icons.Default.ArrowBack, contentDescription = null) + } + }, + actions = { + IconButton(onClick = onActionClick) { + Icon(actionIcon, contentDescription = null) + } + } + ) + } + + @OptIn(ExperimentalMaterial3Api::class) + @Composable + fun DefaultTopBar( + title: String, + actionIcon: ImageVector, + onActionClick: () -> Unit + ) { + CenterAlignedTopAppBar( + modifier = Modifier.shadow(elevation = 8.dp), + title = { Text(text = title) }, + colors = TopAppBarDefaults.centerAlignedTopAppBarColors( + containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(15.dp), + ), + actions = { + IconButton(onClick = onActionClick) { + Icon(actionIcon, contentDescription = null) + } + } + ) + } + + @OptIn(ExperimentalMaterial3Api::class) + @Composable + fun SearchTopBar( + onNavigateBack: () -> Unit = {}, + searchWord: MutableState, + onSearch: () -> Unit = {}, + hint: String, + focusRequester: FocusRequester + ) { + SmallTopAppBar( + modifier = Modifier.shadow(elevation = 8.dp), + title = { + SearchTextField( + searchWord = searchWord, + hint = hint, + focusRequester = focusRequester + ) { + if (searchWord.value.isNotEmpty()) + onSearch() + } + }, + colors = TopAppBarDefaults.centerAlignedTopAppBarColors( + containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(15.dp), + ), + navigationIcon = { + IconButton(onClick = onNavigateBack) { + Icon(Icons.Default.ArrowBack, contentDescription = null) + } + }) + } + +} diff --git a/app/src/main/java/com/mahmoudrh/roomxml/presentation/ui_components/EmptyListScreen.kt b/app/src/main/java/com/mahmoudrh/roomxml/presentation/ui_components/EmptyListScreen.kt new file mode 100644 index 0000000..1eb360e --- /dev/null +++ b/app/src/main/java/com/mahmoudrh/roomxml/presentation/ui_components/EmptyListScreen.kt @@ -0,0 +1,44 @@ +package com.mahmoudrh.roomxml.presentation.ui_components + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.surfaceColorAtElevation +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +@Composable +fun EmptyListScreen(visibility: Boolean, text: String, fontSize: TextUnit = 20.sp) { + AnimatedVisibility( + visible = visibility, + enter = fadeIn(), + exit = fadeOut() + ) { + Box( + modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)), + contentAlignment = Alignment.Center + ) { + Text( + modifier = Modifier.fillMaxWidth().padding(horizontal = 20.dp), + text = text, + fontSize = fontSize, + color = MaterialTheme.colorScheme.secondary, + fontWeight = FontWeight.Thin, + textAlign = TextAlign.Center + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mahmoudrh/roomxml/presentation/ui_components/LoadingScreen.kt b/app/src/main/java/com/mahmoudrh/roomxml/presentation/ui_components/LoadingScreen.kt new file mode 100644 index 0000000..b962c74 --- /dev/null +++ b/app/src/main/java/com/mahmoudrh/roomxml/presentation/ui_components/LoadingScreen.kt @@ -0,0 +1,28 @@ +package com.mahmoudrh.roomxml.presentation.ui_components + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier + +@Composable +fun LoadingScreen(visibility: Boolean) { + AnimatedVisibility( + visible = visibility, + enter = fadeIn(), + exit = fadeOut() + ) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator(color = MaterialTheme.colorScheme.primary) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mahmoudrh/roomxml/presentation/ui_components/NoteItem.kt b/app/src/main/java/com/mahmoudrh/roomxml/presentation/ui_components/NoteItem.kt new file mode 100644 index 0000000..85bf622 --- /dev/null +++ b/app/src/main/java/com/mahmoudrh/roomxml/presentation/ui_components/NoteItem.kt @@ -0,0 +1,197 @@ +package com.mahmoudrh.roomxml.presentation.ui_components + +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.draggable +import androidx.compose.foundation.gestures.rememberDraggableState +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.mahmoudrh.roomxml.domain.models.Note +import com.mahmoudrh.roomxml.presentation.utils.DateFormatter +import kotlinx.coroutines.launch +import java.lang.ArithmeticException +import kotlin.math.absoluteValue + + +@Composable +fun NoteItem( + modifier: Modifier = Modifier, + note: Note, + onClick:()->Unit, + onSwipeOut:(Note)->Unit +) { + val scope = rememberCoroutineScope() + + var offsetX by mutableStateOf(0f) + var alpha by mutableStateOf(0f) + val vanishOffsetRight = remember { Animatable(250f) } + val vanishOffsetLeft = remember { Animatable(-250f) } + fun resetOffset() { + scope.launch { + val resetOffset = Animatable(offsetX) + resetOffset.animateTo(0f, tween(180)) { offsetX = value } + } + } + + fun swipeOut() { + scope.launch { + launch { + val vanishAlpha = Animatable(0.3f) + vanishAlpha.animateTo(1f, tween(80)) { alpha = value } + } + launch { + if (offsetX > 0) + vanishOffsetRight.animateTo(1030f, tween(80)) { offsetX = value } + else + vanishOffsetLeft.animateTo(-1050f, tween(80)) { offsetX = value } + } + }.invokeOnCompletion { + onSwipeOut(note) + } + } + Card( + modifier = modifier + .graphicsLayer { + alpha = try{ + (offsetX) / 1000 + }catch (e: ArithmeticException){ + 1f + } + this.alpha = (1f - alpha.absoluteValue).coerceIn(0f,1f) + this.translationX = offsetX + } + .clickable { onClick() } + .draggable( + rememberDraggableState(onDelta = { delta -> + offsetX += delta + }), + onDragStopped = { velocity -> + if (velocity.absoluteValue < 2000) { + resetOffset() + } else { + swipeOut() + } + }, + orientation = Orientation.Horizontal + ) + .wrapContentSize(), + elevation = CardDefaults.cardElevation(4.dp), + shape = RoundedCornerShape(4.dp) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .height(IntrinsicSize.Min) + .background(MaterialTheme.colorScheme.surface) + ) { + Divider( + color = MaterialTheme.colorScheme.primary, modifier = Modifier + .fillMaxHeight() + .weight(0.2f) + ) + + Column( + modifier = Modifier + .padding(8.dp) + .weight(8f) + ) { + Text( + text = note.title, + color = MaterialTheme.colorScheme.onBackground, + fontWeight = FontWeight.Bold, + fontSize = 20.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = note.content, + color = MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + } + Text( + modifier = Modifier + .weight(2f) + .align(Alignment.Bottom) + .padding(bottom = 8.dp, end = 8.dp), + text = DateFormatter.formatDate(note.date), + textAlign = TextAlign.End, + color = Color.Gray + ) + } + } +} + +@Composable +fun NoteItem( + modifier: Modifier = Modifier, + note: Note, + onClick:()->Unit, +) { + Card( + modifier = modifier + .clickable { onClick() } + .wrapContentSize(), + elevation = CardDefaults.cardElevation(4.dp), + shape = RoundedCornerShape(4.dp) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .height(IntrinsicSize.Min) + .background(MaterialTheme.colorScheme.surface) + ) { + Divider( + color = MaterialTheme.colorScheme.primary, modifier = Modifier + .fillMaxHeight() + .weight(0.2f) + ) + Column( + modifier = Modifier + .padding(8.dp) + .weight(8f) + ) { + Text( + text = note.title, + color = MaterialTheme.colorScheme.onBackground, + fontWeight = FontWeight.Bold, + fontSize = 20.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = note.content, + color = MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + } + Text( + modifier = Modifier + .weight(2f) + .align(Alignment.Bottom) + .padding(bottom = 8.dp, end = 8.dp), + text = DateFormatter.formatDate(note.date), + textAlign = TextAlign.End, + color = Color.Gray + ) + } + } +} diff --git a/app/src/main/java/com/mahmoudrh/roomxml/presentation/ui_components/OrderSection.kt b/app/src/main/java/com/mahmoudrh/roomxml/presentation/ui_components/OrderSection.kt new file mode 100644 index 0000000..7dc9e69 --- /dev/null +++ b/app/src/main/java/com/mahmoudrh/roomxml/presentation/ui_components/OrderSection.kt @@ -0,0 +1,95 @@ +package com.mahmoudrh.roomxml.presentation.ui_components + + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.mahmoudrh.roomxml.domain.utils.OrderBy +import com.mahmoudrh.roomxml.domain.utils.OrderType + + +@Preview +@Composable +fun OrderSection( + order: OrderBy = OrderBy.Date(OrderType.Descending), + onOrderChange: (OrderBy) -> Unit = {} +) { + Card( + modifier = Modifier.padding(vertical = 8.dp), + elevation = CardDefaults.cardElevation(4.dp), + shape = RoundedCornerShape(4.dp), + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .height(IntrinsicSize.Min) + .background(MaterialTheme.colorScheme.surface) + ) { + Divider( + color = MaterialTheme.colorScheme.primary, modifier = Modifier + .fillMaxHeight() + .weight(0.2f) + ) + Column( + modifier = Modifier.weight(10f).padding(horizontal = 8.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text(text = "Sort By: ") + DefaultRadioButton( + text = "Title", + selected = order is OrderBy.Title, + onSelect = { onOrderChange(OrderBy.Title(order.orderType)) } + ) + DefaultRadioButton( + text = "Date", + selected = order is OrderBy.Date, + onSelect = { onOrderChange(OrderBy.Date(order.orderType)) } + ) + } + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text(text = "Sort Type: ") + DefaultRadioButton( + text = "Asc", + selected = order.orderType is OrderType.Ascending, + onSelect = { + onOrderChange(order.copy(OrderType.Ascending)) + } + ) + DefaultRadioButton( + text = "Desc", + selected = order.orderType is OrderType.Descending, + onSelect = { + onOrderChange(order.copy(OrderType.Descending)) + } + ) + } + } + } + } + +} + +@Composable +fun DefaultRadioButton(text: String, selected: Boolean, onSelect: () -> Unit) { + + Row(verticalAlignment = Alignment.CenterVertically) { + Text(text = text) + Spacer(modifier = Modifier.width(4.dp)) + RadioButton(selected = selected, onClick = onSelect) + } + +} diff --git a/app/src/main/java/com/mahmoudrh/roomxml/presentation/ui_components/SearchTextField.kt b/app/src/main/java/com/mahmoudrh/roomxml/presentation/ui_components/SearchTextField.kt new file mode 100644 index 0000000..73823d1 --- /dev/null +++ b/app/src/main/java/com/mahmoudrh/roomxml/presentation/ui_components/SearchTextField.kt @@ -0,0 +1,94 @@ +package com.mahmoudrh.roomxml.presentation.ui_components + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.expandHorizontally +import androidx.compose.animation.shrinkHorizontally +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Cancel +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.unit.sp + +@OptIn(ExperimentalComposeUiApi::class) +@Composable +fun SearchTextField( + searchWord: MutableState, + hint: String, + focusRequester: FocusRequester, + onSearch: () -> Unit = {}, +) { + val keyboardController = LocalSoftwareKeyboardController.current + BasicTextField( + modifier = Modifier + .fillMaxWidth() + .focusRequester(focusRequester) + .onFocusChanged { + if (it.isFocused) + keyboardController?.show() + }, + value = searchWord.value, + onValueChange = { searchWord.value = it }, + singleLine = true, + decorationBox = { innerTextField -> + AnimatedVisibility( + searchWord.value.isEmpty(), + enter = expandHorizontally(), + exit = shrinkHorizontally() + ) { + Text(text = hint, color = Color.Gray, fontSize = 18.sp) + } + if (searchWord.value.isNotEmpty()) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.CenterStart + ) { + IconButton( + modifier = Modifier.align(Alignment.CenterEnd), + onClick = { + searchWord.value = " " + // I Had to put the space and trim it in the view model to avoid crash caused by + // BasicTextField (https://issuetracker.google.com/issues/229378536) + }) { + Icon( + imageVector = Icons.Default.Cancel, + contentDescription = "Clear" + ) + } + innerTextField() + } + } else + innerTextField() + }, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), + keyboardActions = KeyboardActions(onSearch = { + keyboardController?.hide() + onSearch() + }), + textStyle = TextStyle( + color = MaterialTheme.colorScheme.onBackground, + fontSize = 18.sp + ), + cursorBrush = SolidColor(MaterialTheme.colorScheme.onBackground), + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/mahmoudrh/roomxml/presentation/utils/DateFormatter.kt b/app/src/main/java/com/mahmoudrh/roomxml/presentation/utils/DateFormatter.kt new file mode 100644 index 0000000..871d0fd --- /dev/null +++ b/app/src/main/java/com/mahmoudrh/roomxml/presentation/utils/DateFormatter.kt @@ -0,0 +1,12 @@ +package com.mahmoudrh.roomxml.presentation.utils + +import java.text.SimpleDateFormat +import java.util.* + +object DateFormatter { + fun formatDate(noteDate: String): String { + val timeStamp = noteDate.toLong() + val date = Date(timeStamp) + return SimpleDateFormat("MMM dd", Locale.getDefault()).format(date) + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_add.xml b/app/src/main/res/drawable/ic_add.xml new file mode 100644 index 0000000..eb23254 --- /dev/null +++ b/app/src/main/res/drawable/ic_add.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_check.xml b/app/src/main/res/drawable/ic_check.xml new file mode 100644 index 0000000..0432fa6 --- /dev/null +++ b/app/src/main/res/drawable/ic_check.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_delete.xml b/app/src/main/res/drawable/ic_delete.xml new file mode 100644 index 0000000..3c4030b --- /dev/null +++ b/app/src/main/res/drawable/ic_delete.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_dialog_icon.xml b/app/src/main/res/drawable/ic_dialog_icon.xml new file mode 100644 index 0000000..4c78355 --- /dev/null +++ b/app/src/main/res/drawable/ic_dialog_icon.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_edit.xml b/app/src/main/res/drawable/ic_edit.xml new file mode 100644 index 0000000..2844baf --- /dev/null +++ b/app/src/main/res/drawable/ic_edit.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_save.xml b/app/src/main/res/drawable/ic_save.xml new file mode 100644 index 0000000..1a8d86d --- /dev/null +++ b/app/src/main/res/drawable/ic_save.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_search.xml b/app/src/main/res/drawable/ic_search.xml new file mode 100644 index 0000000..07b76d6 --- /dev/null +++ b/app/src/main/res/drawable/ic_search.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..3048de6 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..76c1b11 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..1043d07 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..32b4027 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..292f9f0 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..0577100 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..ee5a82e Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..9eebb65 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..f49410b Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..a9c2a1a Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..69ee03e Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..910f0be Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..1ffc6ae Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..8bc9de1 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..1294c96 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..01cb4b5 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,12 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + + #FFA479E2 + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..7b575a2 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,14 @@ + + D Note + + + Search Notes + Sorry.. Tha application crashed. Please send a report to the developers. + D Note Crashed + Send + Don\'t Send + You can add a comment here: + + + + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..f0fa75b --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,4 @@ + + +