├── app ├── .gitignore ├── src │ └── main │ │ ├── res │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── xml │ │ │ └── file_paths.xml │ │ ├── values │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ ├── layout │ │ │ ├── item_list_director.xml │ │ │ ├── dialog_director.xml │ │ │ ├── item_list_movie.xml │ │ │ ├── fragment_movies.xml │ │ │ ├── fragment_directors.xml │ │ │ ├── dialog_movie.xml │ │ │ ├── activity_main.xml │ │ │ └── content_main.xml │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ ├── drawable │ │ │ ├── ic_add_black_24dp.xml │ │ │ └── ic_launcher_background.xml │ │ ├── menu │ │ │ ├── navigation.xml │ │ │ └── overflow.xml │ │ └── drawable-v24 │ │ │ └── ic_launcher_foreground.xml │ │ ├── java │ │ └── com │ │ │ └── lomza │ │ │ └── moviesroom │ │ │ ├── db │ │ │ ├── MovieDao.kt │ │ │ ├── Director.kt │ │ │ ├── Movie.kt │ │ │ ├── DirectorDao.kt │ │ │ ├── DbUtils.kt │ │ │ └── MoviesDatabase.kt │ │ │ ├── FileUtils.kt │ │ │ ├── director │ │ │ ├── DirectorsViewModel.kt │ │ │ ├── DirectorsListAdapter.kt │ │ │ ├── DirectorsListFragment.kt │ │ │ └── DirectorSaveDialogFragment.kt │ │ │ ├── movie │ │ │ ├── MoviesViewModel.kt │ │ │ ├── MoviesListAdapter.kt │ │ │ ├── MoviesListFragment.kt │ │ │ └── MovieSaveDialogFragment.kt │ │ │ └── MainActivity.kt │ │ ├── AndroidManifest.xml │ │ └── assets │ │ └── schemas │ │ └── com.lomza.moviesroom.db.MoviesDatabase │ │ └── 1.json ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── movies-room.png ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .idea ├── caches │ └── build_file_checksums.ser ├── vcs.xml ├── inspectionProfiles │ └── Project_Default.xml ├── modules.xml ├── runConfigurations.xml ├── checkstyle-idea.xml ├── jarRepositories.xml ├── misc.xml └── codeStyles │ └── Project.xml ├── README.md ├── .gitignore ├── LICENSE ├── gradle.properties ├── gradlew.bat └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | rootProject.name = "movies-room" -------------------------------------------------------------------------------- /movies-room.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lomza/movies-room/HEAD/movies-room.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lomza/movies-room/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lomza/movies-room/HEAD/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lomza/movies-room/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lomza/movies-room/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lomza/movies-room/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lomza/movies-room/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lomza/movies-room/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lomza/movies-room/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lomza/movies-room/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/xml/file_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lomza/movies-room/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lomza/movies-room/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lomza/movies-room/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | #3F51B5 3 | #303F9F 4 | #FF4081 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed May 27 11:14:53 CEST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-rc-1-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_list_director.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/menu/navigation.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_director.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 15 | -------------------------------------------------------------------------------- /.idea/checkstyle-idea.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_list_movie.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/lomza/moviesroom/db/MovieDao.kt: -------------------------------------------------------------------------------- 1 | package com.lomza.moviesroom.db 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.room.* 5 | 6 | /** 7 | * @author Antonina 8 | */ 9 | @Dao 10 | interface MovieDao { 11 | 12 | @Query("SELECT * FROM movie WHERE title = :title LIMIT 1") 13 | suspend fun findMovieByTitle(title: String?): Movie? 14 | 15 | @Insert(onConflict = OnConflictStrategy.IGNORE) 16 | suspend fun insert(vararg directors: Movie) 17 | 18 | @Update(onConflict = OnConflictStrategy.IGNORE) 19 | suspend fun update(director: Movie) 20 | 21 | @Query("DELETE FROM movie") 22 | suspend fun deleteAll() 23 | 24 | @get:Query("SELECT * FROM movie ORDER BY title ASC") 25 | val allMovies: LiveData> 26 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # movies-room 2 | 3 | Example Android application with [Room DB](https://developer.android.com/topic/libraries/architecture/room.html) persistant storage and [ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel.html) with [LiveData](https://developer.android.com/topic/libraries/architecture/livedata.html) and coroutines. Additionalially, there's a menu option to export DB tables to CSV files, using [FileProvider](https://developer.android.com/reference/androidx/core/content/FileProvider) and [kotlin-csv](https://github.com/doyaaaaaken/kotlin-csv) library. 4 | 5 | All code is in Kotlin. 6 | 7 | I explain the code in details [here](https://medium.com/@tonia.tkachuk/android-app-example-using-room-database-63f7091e69af). 8 | 9 | ![Screenshots](movies-room.png) 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/lomza/moviesroom/db/Director.kt: -------------------------------------------------------------------------------- 1 | package com.lomza.moviesroom.db 2 | 3 | import androidx.room.* 4 | import com.lomza.moviesroom.db.Director.Companion.FULL_NAME 5 | import com.lomza.moviesroom.db.Director.Companion.TABLE_NAME 6 | 7 | /** 8 | * @author Antonina 9 | */ 10 | @Entity( 11 | tableName = TABLE_NAME, 12 | indices = [Index(value = [FULL_NAME], unique = true)]) 13 | data class Director( 14 | @PrimaryKey(autoGenerate = true) 15 | @ColumnInfo(name = "did") var id: Long = 0, 16 | @ColumnInfo(name = FULL_NAME) var fullName: String, 17 | @Ignore var age: Int = 0) { 18 | 19 | constructor() : this(0L, "", 0) 20 | 21 | companion object { 22 | const val TABLE_NAME = "director" 23 | const val FULL_NAME = "full_name" 24 | } 25 | } -------------------------------------------------------------------------------- /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/main/res/layout/fragment_movies.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/menu/overflow.xml: -------------------------------------------------------------------------------- 1 | 5 | 10 | 11 | 16 | 17 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_directors.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/lomza/moviesroom/FileUtils.kt: -------------------------------------------------------------------------------- 1 | package com.lomza.moviesroom 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import androidx.core.content.FileProvider 6 | import java.io.File 7 | 8 | fun generateFile(context: Context, fileName: String): File? { 9 | val csvFile = File(context.filesDir, fileName) 10 | csvFile.createNewFile() 11 | 12 | return if (csvFile.exists()) { 13 | csvFile 14 | } else { 15 | null 16 | } 17 | } 18 | 19 | fun goToFileIntent(context: Context, file: File): Intent { 20 | val intent = Intent(Intent.ACTION_VIEW) 21 | val contentUri = FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", file) 22 | val mimeType = context.contentResolver.getType(contentUri) 23 | intent.setDataAndType(contentUri, mimeType) 24 | intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION 25 | 26 | return intent 27 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_movie.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 16 | 17 | 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # Intellij 36 | *.iml 37 | .idea/workspace.xml 38 | .idea/tasks.xml 39 | .idea/gradle.xml 40 | .idea/dictionaries 41 | .idea/libraries 42 | 43 | # Keystore files 44 | *.jks 45 | 46 | # External native build folder generated in Android Studio 2.2 and later 47 | .externalNativeBuild 48 | 49 | # Google Services (e.g. APIs or Firebase) 50 | google-services.json 51 | 52 | # Freeline 53 | freeline.py 54 | freeline/ 55 | freeline_project_description.json 56 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Movies Room Example 3 | Movies 4 | Directors 5 | Delete list data 6 | Re-create database 7 | Export DB table to CSV file 8 | Title 9 | Director 10 | Full Name 11 | Movie 12 | Director 13 | Save 14 | Cancel 15 | CSV file generated! 16 | Something went wrong. Please try again. 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/lomza/moviesroom/db/Movie.kt: -------------------------------------------------------------------------------- 1 | package com.lomza.moviesroom.db 2 | 3 | import androidx.room.* 4 | import com.lomza.moviesroom.db.Movie.Companion.TITLE 5 | import com.lomza.moviesroom.db.Movie.Companion.DIRECTOR_ID 6 | import com.lomza.moviesroom.db.Movie.Companion.TABLE_NAME 7 | 8 | /** 9 | * @author Antonina 10 | */ 11 | @Entity( 12 | tableName = TABLE_NAME, 13 | foreignKeys = [ForeignKey( 14 | entity = Director::class, 15 | parentColumns = ["did"], 16 | childColumns = [DIRECTOR_ID], 17 | onDelete = ForeignKey.CASCADE 18 | )], 19 | indices = [Index(TITLE), Index(DIRECTOR_ID)] 20 | ) 21 | data class Movie( 22 | @PrimaryKey(autoGenerate = true) 23 | @ColumnInfo(name = "mid") var id: Long = 0, 24 | @ColumnInfo(name = TITLE) var title: String, 25 | @ColumnInfo(name = DIRECTOR_ID) var directorId: Long) { 26 | 27 | companion object { 28 | const val TABLE_NAME = "movie" 29 | const val TITLE = "title" 30 | const val DIRECTOR_ID = "directorId" 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/java/com/lomza/moviesroom/director/DirectorsViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.lomza.moviesroom.director 2 | 3 | import android.app.Application 4 | import androidx.lifecycle.AndroidViewModel 5 | import androidx.lifecycle.LiveData 6 | import com.lomza.moviesroom.db.Director 7 | import com.lomza.moviesroom.db.DirectorDao 8 | import com.lomza.moviesroom.db.MoviesDatabase 9 | 10 | /** 11 | * @author Antonina 12 | */ 13 | class DirectorsViewModel(application: Application) : AndroidViewModel(application) { 14 | 15 | private val directorDao: DirectorDao = MoviesDatabase.getDatabase(application).directorDao() 16 | val directorList: LiveData> 17 | 18 | init { 19 | directorList = directorDao.allDirectors 20 | } 21 | 22 | suspend fun insert(vararg directors: Director) { 23 | directorDao.insert(*directors) 24 | } 25 | 26 | suspend fun update(director: Director) { 27 | directorDao.update(director) 28 | } 29 | 30 | suspend fun deleteAll() { 31 | directorDao.deleteAll() 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/lomza/moviesroom/db/DirectorDao.kt: -------------------------------------------------------------------------------- 1 | package com.lomza.moviesroom.db 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.room.* 5 | 6 | /** 7 | * @author Antonina 8 | */ 9 | @Dao 10 | interface DirectorDao { 11 | 12 | @Query("SELECT * FROM director WHERE did = :id LIMIT 1") 13 | suspend fun findDirectorById(id: Long): Director? 14 | 15 | @Query("SELECT * FROM director WHERE full_name = :fullName LIMIT 1") 16 | suspend fun findDirectorByName(fullName: String?): Director? 17 | 18 | @Insert(onConflict = OnConflictStrategy.IGNORE) 19 | suspend fun insert(director: Director): Long 20 | 21 | @Insert(onConflict = OnConflictStrategy.IGNORE) 22 | suspend fun insert(vararg directors: Director) 23 | 24 | @Update(onConflict = OnConflictStrategy.IGNORE) 25 | suspend fun update(director: Director) 26 | 27 | @Query("DELETE FROM director") 28 | suspend fun deleteAll() 29 | 30 | @get:Query("SELECT * FROM director ORDER BY full_name ASC") 31 | val allDirectors: LiveData> 32 | } -------------------------------------------------------------------------------- /app/src/main/java/com/lomza/moviesroom/movie/MoviesViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.lomza.moviesroom.movie 2 | 3 | import android.app.Application 4 | import androidx.lifecycle.AndroidViewModel 5 | import androidx.lifecycle.LiveData 6 | import com.lomza.moviesroom.db.* 7 | 8 | /** 9 | * @author Antonina 10 | */ 11 | class MoviesViewModel(application: Application) : AndroidViewModel(application) { 12 | 13 | private val movieDao: MovieDao = MoviesDatabase.getDatabase(application).movieDao() 14 | private val directorDao: DirectorDao = MoviesDatabase.getDatabase(application).directorDao() 15 | 16 | val moviesList: LiveData> 17 | val directorsList: LiveData> 18 | 19 | init { 20 | moviesList = movieDao.allMovies 21 | directorsList = directorDao.allDirectors 22 | } 23 | 24 | suspend fun insert(vararg movies: Movie) { 25 | movieDao.insert(*movies) 26 | } 27 | 28 | suspend fun update(movie: Movie) { 29 | movieDao.update(movie) 30 | } 31 | 32 | suspend fun deleteAll() { 33 | movieDao.deleteAll() 34 | } 35 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Antonina 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 26 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official -------------------------------------------------------------------------------- /app/src/main/java/com/lomza/moviesroom/db/DbUtils.kt: -------------------------------------------------------------------------------- 1 | package com.lomza.moviesroom.db 2 | 3 | import kotlinx.coroutines.Dispatchers 4 | import kotlinx.coroutines.withContext 5 | 6 | suspend fun rePopulateDb(database: MoviesDatabase?) { 7 | database?.let { db -> 8 | withContext(Dispatchers.IO) { 9 | val movieDao: MovieDao = db.movieDao() 10 | val directorDao: DirectorDao = db.directorDao() 11 | 12 | movieDao.deleteAll() 13 | directorDao.deleteAll() 14 | 15 | val directorOne = Director(fullName = "Adam McKay") 16 | val directorTwo = Director(fullName = "Denis Villeneuve", age = 35) 17 | val directorThree = Director(fullName = "Morten Tyldum", age = 26) 18 | val movieOne = Movie(title = "The Big Short", directorId = directorDao.insert(directorOne)) 19 | val dIdTwo = directorDao.insert(directorTwo) 20 | val movieTwo = Movie(title = "Arrival", directorId = dIdTwo) 21 | val movieThree = Movie(title = "Blade Runner 2049", directorId = dIdTwo) 22 | val movieFour = Movie(title = "Passengers", directorId = directorDao.insert(directorThree)) 23 | movieDao.insert(movieOne, movieTwo, movieThree, movieFour) 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/lomza/moviesroom/db/MoviesDatabase.kt: -------------------------------------------------------------------------------- 1 | package com.lomza.moviesroom.db 2 | 3 | import android.content.Context 4 | import android.util.Log 5 | import androidx.room.Database 6 | import androidx.room.Room 7 | import androidx.room.RoomDatabase 8 | import androidx.sqlite.db.SupportSQLiteDatabase 9 | import kotlinx.coroutines.* 10 | 11 | /** 12 | * @author Antonina 13 | */ 14 | @Database(entities = [Movie::class, Director::class], version = 1) 15 | abstract class MoviesDatabase : RoomDatabase() { 16 | 17 | abstract fun movieDao(): MovieDao 18 | abstract fun directorDao(): DirectorDao 19 | 20 | companion object { 21 | private var INSTANCE: MoviesDatabase? = null 22 | private const val DB_NAME = "movies.db" 23 | 24 | fun getDatabase(context: Context): MoviesDatabase { 25 | if (INSTANCE == null) { 26 | synchronized(MoviesDatabase::class.java) { 27 | if (INSTANCE == null) { 28 | INSTANCE = Room.databaseBuilder(context.applicationContext, MoviesDatabase::class.java, DB_NAME) 29 | //.allowMainThreadQueries() // Uncomment if you don't want to use RxJava or coroutines just yet (blocks UI thread) 30 | .addCallback(object : Callback() { 31 | override fun onCreate(db: SupportSQLiteDatabase) { 32 | super.onCreate(db) 33 | Log.d("MoviesDatabase", "populating with data...") 34 | GlobalScope.launch(Dispatchers.IO) { rePopulateDb(INSTANCE) } 35 | } 36 | }).build() 37 | } 38 | } 39 | } 40 | 41 | return INSTANCE!! 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 15 | 16 | 27 | 28 | 35 | 36 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/main/res/layout/content_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 19 | 20 | 29 | 30 | 41 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'kotlin-android' 4 | id 'kotlin-android-extensions' 5 | id 'kotlin-kapt' 6 | } 7 | 8 | android { 9 | compileSdkVersion target_sdk 10 | 11 | defaultConfig { 12 | applicationId "com.lomza.moviesroom" 13 | minSdkVersion min_sdk 14 | targetSdkVersion target_sdk 15 | versionCode 1 16 | versionName "1.0" 17 | 18 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 19 | 20 | javaCompileOptions { 21 | annotationProcessorOptions { 22 | arguments = [ 23 | "room.schemaLocation": "$projectDir/src/main/assets/schemas".toString() 24 | ] 25 | } 26 | } 27 | } 28 | 29 | buildTypes { 30 | release { 31 | minifyEnabled false 32 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 33 | } 34 | } 35 | compileOptions { 36 | sourceCompatibility JavaVersion.VERSION_1_8 37 | targetCompatibility JavaVersion.VERSION_1_8 38 | } 39 | kotlinOptions { 40 | jvmTarget = '1.8' 41 | } 42 | } 43 | 44 | dependencies { 45 | def lifecycle_version = "2.2.0" 46 | def room_version = "2.2.5" 47 | 48 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 49 | implementation 'androidx.appcompat:appcompat:1.3.0-alpha01' 50 | implementation 'com.google.android.material:material:1.1.0' 51 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 52 | 53 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" 54 | implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" 55 | implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" 56 | 57 | implementation "androidx.room:room-runtime:$room_version" 58 | implementation "androidx.room:room-ktx:$room_version" 59 | kapt "androidx.room:room-compiler:$room_version" 60 | 61 | implementation "com.github.doyaaaaaken:kotlin-csv-jvm:0.9.0" 62 | } -------------------------------------------------------------------------------- /app/src/main/java/com/lomza/moviesroom/director/DirectorsListAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.lomza.moviesroom.director 2 | 3 | import android.content.Context 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.TextView 8 | import androidx.appcompat.app.AppCompatActivity 9 | import androidx.fragment.app.DialogFragment 10 | import androidx.recyclerview.widget.RecyclerView 11 | import com.lomza.moviesroom.R 12 | import com.lomza.moviesroom.db.Director 13 | import com.lomza.moviesroom.director.DirectorsListAdapter.DirectorsViewHolder 14 | 15 | /** 16 | * @author Antonina 17 | */ 18 | class DirectorsListAdapter(val context: Context) : RecyclerView.Adapter() { 19 | 20 | private val layoutInflater: LayoutInflater = LayoutInflater.from(context) 21 | private var directorList: List? = null 22 | 23 | fun setDirectorList(directorList: List) { 24 | this.directorList = directorList 25 | notifyDataSetChanged() 26 | } 27 | 28 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DirectorsViewHolder { 29 | val itemView = layoutInflater.inflate(R.layout.item_list_director, parent, false) 30 | return DirectorsViewHolder(itemView) 31 | } 32 | 33 | override fun onBindViewHolder(holder: DirectorsViewHolder, position: Int) { 34 | directorList?.let { 35 | val director = it[position] 36 | holder.directorText.text = director.fullName 37 | holder.itemView.setOnClickListener { 38 | val dialogFragment: DialogFragment = DirectorSaveDialogFragment.newInstance(director.fullName) 39 | dialogFragment.show( 40 | (context as AppCompatActivity).supportFragmentManager, 41 | DirectorSaveDialogFragment.TAG_DIALOG_DIRECTOR_SAVE 42 | ) 43 | } 44 | } 45 | } 46 | 47 | override fun getItemCount(): Int { 48 | return if (directorList == null) { 49 | 0 50 | } else { 51 | directorList!!.size 52 | } 53 | } 54 | 55 | class DirectorsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 56 | val directorText: TextView = itemView.findViewById(R.id.tvDirector) 57 | } 58 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /app/src/main/java/com/lomza/moviesroom/director/DirectorsListFragment.kt: -------------------------------------------------------------------------------- 1 | package com.lomza.moviesroom.director 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import androidx.lifecycle.Observer 9 | import androidx.lifecycle.ViewModelProvider 10 | import androidx.recyclerview.widget.DividerItemDecoration 11 | import androidx.recyclerview.widget.LinearLayoutManager 12 | import androidx.recyclerview.widget.RecyclerView 13 | import com.github.doyaaaaaken.kotlincsv.dsl.csvWriter 14 | import com.lomza.moviesroom.R 15 | import com.lomza.moviesroom.db.Director 16 | import kotlinx.coroutines.Dispatchers 17 | import kotlinx.coroutines.GlobalScope 18 | import kotlinx.coroutines.launch 19 | import java.io.File 20 | 21 | /** 22 | * @author Antonina 23 | */ 24 | class DirectorsListFragment : Fragment() { 25 | 26 | private lateinit var directorsListAdapter: DirectorsListAdapter 27 | private lateinit var directorsViewModel: DirectorsViewModel 28 | private lateinit var directorsList: List 29 | 30 | override fun onCreate(savedInstanceState: Bundle?) { 31 | super.onCreate(savedInstanceState) 32 | initData() 33 | } 34 | 35 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 36 | val view = inflater.inflate(R.layout.fragment_directors, container, false) 37 | initView(view) 38 | 39 | return view 40 | } 41 | 42 | private fun initData() { 43 | directorsViewModel = ViewModelProvider(this).get(DirectorsViewModel::class.java) 44 | directorsViewModel.directorList.observe(this, 45 | Observer { directors: List -> 46 | directorsList = directors 47 | directorsListAdapter.setDirectorList(directors) 48 | } 49 | ) 50 | } 51 | 52 | private fun initView(view: View) { 53 | val recyclerView: RecyclerView = view.findViewById(R.id.recyclerview_directors) 54 | directorsListAdapter = DirectorsListAdapter(requireContext()) 55 | recyclerView.adapter = directorsListAdapter 56 | recyclerView.addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL)) 57 | recyclerView.layoutManager = LinearLayoutManager(context) 58 | } 59 | 60 | fun removeData() { 61 | GlobalScope.launch(Dispatchers.IO) { directorsViewModel.deleteAll() } 62 | } 63 | 64 | fun exportDirectorsToCSVFile(csvFile: File) { 65 | csvWriter().open(csvFile, append = false) { 66 | // Header 67 | writeRow(listOf("[id]", "[${Director.TABLE_NAME}]")) 68 | directorsList.forEachIndexed { index, director -> 69 | writeRow(listOf(index, director.fullName)) 70 | } 71 | } 72 | } 73 | 74 | companion object { 75 | fun newInstance(): DirectorsListFragment = DirectorsListFragment() 76 | } 77 | } -------------------------------------------------------------------------------- /app/src/main/java/com/lomza/moviesroom/movie/MoviesListAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.lomza.moviesroom.movie 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import android.widget.TextView 7 | import androidx.appcompat.app.AppCompatActivity 8 | import androidx.fragment.app.DialogFragment 9 | import androidx.fragment.app.Fragment 10 | import androidx.recyclerview.widget.RecyclerView 11 | import com.lomza.moviesroom.R 12 | import com.lomza.moviesroom.db.Movie 13 | import com.lomza.moviesroom.db.MoviesDatabase 14 | import com.lomza.moviesroom.movie.MovieSaveDialogFragment.Companion.newInstance 15 | import com.lomza.moviesroom.movie.MoviesListAdapter.MoviesViewHolder 16 | import kotlinx.coroutines.Dispatchers 17 | import kotlinx.coroutines.runBlocking 18 | import kotlinx.coroutines.withContext 19 | 20 | /** 21 | * @author Antonina 22 | */ 23 | class MoviesListAdapter(private val parent: Fragment) : RecyclerView.Adapter() { 24 | 25 | private val layoutInflater: LayoutInflater = LayoutInflater.from(parent.requireContext()) 26 | private var movieList: List? = null 27 | 28 | fun setMovieList(movieList: List?) { 29 | this.movieList = movieList 30 | notifyDataSetChanged() 31 | } 32 | 33 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MoviesViewHolder { 34 | val itemView = layoutInflater.inflate(R.layout.item_list_movie, parent, false) 35 | return MoviesViewHolder(itemView) 36 | } 37 | 38 | override fun onBindViewHolder(holder: MoviesViewHolder, position: Int) { 39 | movieList?.let { list -> 40 | val movie = list[position] 41 | holder.titleText.text = movie.title 42 | runBlocking { 43 | val directorFullName = withContext(Dispatchers.Default) { 44 | getDirectorFullName(movie) 45 | } 46 | 47 | holder.directorText.text = directorFullName ?: "" 48 | 49 | holder.itemView.setOnClickListener { 50 | val dialogFragment: DialogFragment = newInstance(movie.title, directorFullName) 51 | dialogFragment.setTargetFragment(parent, 99) 52 | dialogFragment.show( 53 | (parent.activity as AppCompatActivity).supportFragmentManager, 54 | MovieSaveDialogFragment.TAG_DIALOG_MOVIE_SAVE 55 | ) 56 | } 57 | } 58 | } 59 | } 60 | 61 | private suspend fun getDirectorFullName(movie: Movie): String? { 62 | return MoviesDatabase.getDatabase(parent.requireContext()).directorDao().findDirectorById(movie.directorId)?.fullName 63 | } 64 | 65 | override fun getItemCount(): Int { 66 | return if (movieList == null) { 67 | 0 68 | } else { 69 | movieList!!.size 70 | } 71 | } 72 | 73 | class MoviesViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 74 | val titleText: TextView = itemView.findViewById(R.id.tvMovieTitle) 75 | val directorText: TextView = itemView.findViewById(R.id.tvMovieDirectorFullName) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/java/com/lomza/moviesroom/director/DirectorSaveDialogFragment.kt: -------------------------------------------------------------------------------- 1 | package com.lomza.moviesroom.director 2 | 3 | import android.app.Dialog 4 | import android.os.Bundle 5 | import android.text.TextUtils 6 | import android.widget.EditText 7 | import androidx.appcompat.app.AlertDialog 8 | import androidx.fragment.app.DialogFragment 9 | import com.lomza.moviesroom.R 10 | import com.lomza.moviesroom.db.Director 11 | import com.lomza.moviesroom.db.MoviesDatabase 12 | import kotlinx.coroutines.Dispatchers 13 | import kotlinx.coroutines.GlobalScope 14 | import kotlinx.coroutines.launch 15 | 16 | /** 17 | * @author Antonina 18 | */ 19 | class DirectorSaveDialogFragment : DialogFragment() { 20 | 21 | private var directorFullNameExtra: String? = null 22 | 23 | override fun onCreate(savedInstanceState: Bundle?) { 24 | super.onCreate(savedInstanceState) 25 | directorFullNameExtra = arguments!!.getString(EXTRA_DIRECTOR_FULL_NAME) 26 | } 27 | 28 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 29 | val alertDialogBuilder = AlertDialog.Builder(requireActivity()) 30 | val view = requireActivity().layoutInflater.inflate(R.layout.dialog_director, null) 31 | val directorEditText = view.findViewById(R.id.etDirectorFullName) 32 | directorEditText.setText(directorFullNameExtra) 33 | directorEditText.setSelection(directorFullNameExtra?.length ?: 0) 34 | alertDialogBuilder.setView(view) 35 | .setTitle(getString(R.string.dialog_director_title)) 36 | .setPositiveButton(R.string.save) { _, _ -> 37 | GlobalScope.launch(Dispatchers.IO) { saveDirector(directorEditText.text.toString()) } 38 | } 39 | .setNegativeButton(R.string.cancel) { dialog, _ -> dialog.cancel() } 40 | 41 | return alertDialogBuilder.create() 42 | } 43 | 44 | private suspend fun saveDirector(fullName: String) { 45 | if (TextUtils.isEmpty(fullName)) { 46 | return 47 | } 48 | val directorDao = MoviesDatabase.getDatabase(requireContext()).directorDao() 49 | if (directorFullNameExtra != null) { 50 | // clicked on item row -> update 51 | val directorToUpdate = directorDao.findDirectorByName(directorFullNameExtra) 52 | if (directorToUpdate != null) { 53 | if (directorToUpdate.fullName != fullName) { 54 | directorToUpdate.fullName = fullName 55 | directorDao.update(directorToUpdate) 56 | } 57 | } 58 | } else { 59 | directorDao.insert(Director(fullName = fullName)) 60 | } 61 | } 62 | 63 | companion object { 64 | private const val EXTRA_DIRECTOR_FULL_NAME = "director_full_name" 65 | const val TAG_DIALOG_DIRECTOR_SAVE = "dialog_director_save" 66 | 67 | fun newInstance(directorFullName: String?): DirectorSaveDialogFragment { 68 | val fragment = DirectorSaveDialogFragment() 69 | val args = Bundle() 70 | args.putString(EXTRA_DIRECTOR_FULL_NAME, directorFullName) 71 | fragment.arguments = args 72 | return fragment 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 25 | 42 | 43 | 44 | 45 | 46 | 47 | 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/lomza/moviesroom/movie/MoviesListFragment.kt: -------------------------------------------------------------------------------- 1 | package com.lomza.moviesroom.movie 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import androidx.lifecycle.Observer 9 | import androidx.lifecycle.ViewModelProvider 10 | import androidx.recyclerview.widget.DividerItemDecoration 11 | import androidx.recyclerview.widget.LinearLayoutManager 12 | import androidx.recyclerview.widget.RecyclerView 13 | import com.github.doyaaaaaken.kotlincsv.dsl.csvWriter 14 | import com.lomza.moviesroom.R 15 | import com.lomza.moviesroom.db.Director 16 | import com.lomza.moviesroom.db.Movie 17 | import com.lomza.moviesroom.db.MoviesDatabase 18 | import kotlinx.coroutines.Dispatchers 19 | import kotlinx.coroutines.GlobalScope 20 | import kotlinx.coroutines.launch 21 | import java.io.File 22 | 23 | /** 24 | * @author Antonina 25 | */ 26 | class MoviesListFragment : Fragment() { 27 | 28 | private lateinit var moviesListAdapter: MoviesListAdapter 29 | private lateinit var moviesViewModel: MoviesViewModel 30 | private lateinit var moviesList: List 31 | 32 | override fun onCreate(savedInstanceState: Bundle?) { 33 | super.onCreate(savedInstanceState) 34 | initData() 35 | } 36 | 37 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 38 | val view = inflater.inflate(R.layout.fragment_movies, container, false) 39 | initView(view) 40 | 41 | return view 42 | } 43 | 44 | private fun initData() { 45 | moviesViewModel = ViewModelProvider(this).get(MoviesViewModel::class.java) 46 | moviesViewModel.moviesList.observe(this, 47 | Observer { movies: List -> 48 | moviesList = movies 49 | moviesListAdapter.setMovieList(movies) 50 | } 51 | ) 52 | 53 | moviesViewModel.directorsList.observe(this, 54 | Observer { _ -> 55 | // we need to refresh the movies list in case when director's name changed 56 | moviesViewModel.moviesList.value?.let { 57 | moviesList = it 58 | moviesListAdapter.setMovieList(it) 59 | } 60 | } 61 | ) 62 | } 63 | 64 | private suspend fun getDirectorFullName(movie: Movie): String? { 65 | return MoviesDatabase.getDatabase(requireContext()).directorDao().findDirectorById(movie.directorId)?.fullName 66 | } 67 | 68 | private fun initView(view: View) { 69 | val recyclerView: RecyclerView = view.findViewById(R.id.recyclerview_movies) 70 | moviesListAdapter = MoviesListAdapter(this) 71 | recyclerView.adapter = moviesListAdapter 72 | recyclerView.addItemDecoration(DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL)) 73 | recyclerView.layoutManager = LinearLayoutManager(requireContext()) 74 | } 75 | 76 | fun removeData() { 77 | GlobalScope.launch(Dispatchers.IO) { moviesViewModel.deleteAll() } 78 | } 79 | 80 | fun exportMoviesWithDirectorsToCSVFile(csvFile: File) { 81 | csvWriter().open(csvFile, append = false) { 82 | // Header 83 | writeRow(listOf("[id]", "[${Movie.TABLE_NAME}]", "[${Director.TABLE_NAME}]")) 84 | moviesList.forEachIndexed { index, movie -> 85 | val directorName: String = moviesViewModel.directorsList.value?.find { it.id == movie.directorId }?.fullName ?: "" 86 | writeRow(listOf(index, movie.title, directorName)) 87 | } 88 | } 89 | } 90 | 91 | companion object { 92 | fun newInstance(): MoviesListFragment = MoviesListFragment() 93 | } 94 | } -------------------------------------------------------------------------------- /app/src/main/assets/schemas/com.lomza.moviesroom.db.MoviesDatabase/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 1, 5 | "identityHash": "627455825d506754f171b32a983cfc02", 6 | "entities": [ 7 | { 8 | "tableName": "movie", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`mid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `directorId` INTEGER NOT NULL, FOREIGN KEY(`directorId`) REFERENCES `director`(`did`) ON UPDATE NO ACTION ON DELETE CASCADE )", 10 | "fields": [ 11 | { 12 | "fieldPath": "id", 13 | "columnName": "mid", 14 | "affinity": "INTEGER", 15 | "notNull": true 16 | }, 17 | { 18 | "fieldPath": "title", 19 | "columnName": "title", 20 | "affinity": "TEXT", 21 | "notNull": true 22 | }, 23 | { 24 | "fieldPath": "directorId", 25 | "columnName": "directorId", 26 | "affinity": "INTEGER", 27 | "notNull": true 28 | } 29 | ], 30 | "primaryKey": { 31 | "columnNames": [ 32 | "mid" 33 | ], 34 | "autoGenerate": true 35 | }, 36 | "indices": [ 37 | { 38 | "name": "index_movie_title", 39 | "unique": false, 40 | "columnNames": [ 41 | "title" 42 | ], 43 | "createSql": "CREATE INDEX IF NOT EXISTS `index_movie_title` ON `${TABLE_NAME}` (`title`)" 44 | }, 45 | { 46 | "name": "index_movie_directorId", 47 | "unique": false, 48 | "columnNames": [ 49 | "directorId" 50 | ], 51 | "createSql": "CREATE INDEX IF NOT EXISTS `index_movie_directorId` ON `${TABLE_NAME}` (`directorId`)" 52 | } 53 | ], 54 | "foreignKeys": [ 55 | { 56 | "table": "director", 57 | "onDelete": "CASCADE", 58 | "onUpdate": "NO ACTION", 59 | "columns": [ 60 | "directorId" 61 | ], 62 | "referencedColumns": [ 63 | "did" 64 | ] 65 | } 66 | ] 67 | }, 68 | { 69 | "tableName": "director", 70 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`did` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `full_name` TEXT NOT NULL)", 71 | "fields": [ 72 | { 73 | "fieldPath": "id", 74 | "columnName": "did", 75 | "affinity": "INTEGER", 76 | "notNull": true 77 | }, 78 | { 79 | "fieldPath": "fullName", 80 | "columnName": "full_name", 81 | "affinity": "TEXT", 82 | "notNull": true 83 | } 84 | ], 85 | "primaryKey": { 86 | "columnNames": [ 87 | "did" 88 | ], 89 | "autoGenerate": true 90 | }, 91 | "indices": [ 92 | { 93 | "name": "index_director_full_name", 94 | "unique": true, 95 | "columnNames": [ 96 | "full_name" 97 | ], 98 | "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_director_full_name` ON `${TABLE_NAME}` (`full_name`)" 99 | } 100 | ], 101 | "foreignKeys": [] 102 | } 103 | ], 104 | "views": [], 105 | "setupQueries": [ 106 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 107 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '627455825d506754f171b32a983cfc02')" 108 | ] 109 | } 110 | } -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | xmlns:android 14 | 15 | ^$ 16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 24 | xmlns:.* 25 | 26 | ^$ 27 | 28 | 29 | BY_NAME 30 | 31 |
32 |
33 | 34 | 35 | 36 | .*:id 37 | 38 | http://schemas.android.com/apk/res/android 39 | 40 | 41 | 42 |
43 |
44 | 45 | 46 | 47 | .*:name 48 | 49 | http://schemas.android.com/apk/res/android 50 | 51 | 52 | 53 |
54 |
55 | 56 | 57 | 58 | name 59 | 60 | ^$ 61 | 62 | 63 | 64 |
65 |
66 | 67 | 68 | 69 | style 70 | 71 | ^$ 72 | 73 | 74 | 75 |
76 |
77 | 78 | 79 | 80 | .* 81 | 82 | ^$ 83 | 84 | 85 | BY_NAME 86 | 87 |
88 |
89 | 90 | 91 | 92 | .* 93 | 94 | http://schemas.android.com/apk/res/android 95 | 96 | 97 | ANDROID_ATTRIBUTE_ORDER 98 | 99 |
100 |
101 | 102 | 103 | 104 | .* 105 | 106 | .* 107 | 108 | 109 | BY_NAME 110 | 111 |
112 |
113 |
114 |
115 |
116 |
-------------------------------------------------------------------------------- /app/src/main/java/com/lomza/moviesroom/movie/MovieSaveDialogFragment.kt: -------------------------------------------------------------------------------- 1 | package com.lomza.moviesroom.movie 2 | 3 | import android.app.Dialog 4 | import android.os.Bundle 5 | import android.text.TextUtils 6 | import android.widget.EditText 7 | import androidx.appcompat.app.AlertDialog 8 | import androidx.fragment.app.DialogFragment 9 | import com.lomza.moviesroom.R 10 | import com.lomza.moviesroom.db.Director 11 | import com.lomza.moviesroom.db.Movie 12 | import com.lomza.moviesroom.db.MoviesDatabase 13 | import kotlinx.coroutines.Dispatchers 14 | import kotlinx.coroutines.GlobalScope 15 | import kotlinx.coroutines.launch 16 | 17 | /** 18 | * @author Antonina 19 | */ 20 | class MovieSaveDialogFragment : DialogFragment() { 21 | 22 | private var movieTitleExtra: String? = null 23 | private var movieDirectorFullNameExtra: String? = null 24 | 25 | override fun onCreate(savedInstanceState: Bundle?) { 26 | super.onCreate(savedInstanceState) 27 | 28 | movieTitleExtra = arguments!!.getString(EXTRA_MOVIE_TITLE) 29 | movieDirectorFullNameExtra = arguments!!.getString(EXTRA_MOVIE_DIRECTOR_FULL_NAME) 30 | } 31 | 32 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 33 | val alertDialogBuilder = AlertDialog.Builder(requireContext()) 34 | val view = requireActivity().layoutInflater.inflate(R.layout.dialog_movie, null) 35 | val movieEditText = view.findViewById(R.id.etMovieTitle) 36 | val movieDirectorEditText = view.findViewById(R.id.etMovieDirectorFullName) 37 | if (movieTitleExtra != null) { 38 | movieEditText.setText(movieTitleExtra) 39 | movieEditText.setSelection(movieTitleExtra!!.length) 40 | } 41 | if (movieDirectorFullNameExtra != null) { 42 | movieDirectorEditText.setText(movieDirectorFullNameExtra) 43 | movieDirectorEditText.setSelection(movieDirectorFullNameExtra!!.length) 44 | } 45 | alertDialogBuilder.setView(view) 46 | .setTitle(getString(R.string.dialog_movie_title)) 47 | .setPositiveButton(R.string.save) { _, _ -> 48 | GlobalScope.launch(Dispatchers.IO) { saveMovie(movieEditText.text.toString(), movieDirectorEditText.text.toString()) } 49 | } 50 | .setNegativeButton(R.string.cancel) { dialog, _ -> dialog.cancel() } 51 | 52 | return alertDialogBuilder.create() 53 | } 54 | 55 | private suspend fun saveMovie(movieTitle: String, movieDirectorFullName: String) { 56 | if (TextUtils.isEmpty(movieTitle) || TextUtils.isEmpty(movieDirectorFullName)) { 57 | return 58 | } 59 | val directorDao = MoviesDatabase.getDatabase(requireContext()).directorDao() 60 | val movieDao = MoviesDatabase.getDatabase(requireContext()).movieDao() 61 | var directorId: Long = -1L 62 | if (movieDirectorFullNameExtra != null) { 63 | // clicked on item row -> update 64 | val directorToUpdate = directorDao.findDirectorByName(movieDirectorFullNameExtra) 65 | if (directorToUpdate != null) { 66 | directorId = directorToUpdate.id 67 | if (directorToUpdate.fullName != movieDirectorFullName) { 68 | directorToUpdate.fullName = movieDirectorFullName 69 | directorDao.update(directorToUpdate) 70 | } 71 | } 72 | } else { 73 | // we need director id for movie object; in case director is already in DB, 74 | // insert() would return -1, so we manually check if it exists and get 75 | // the id of already saved director 76 | val newDirector = directorDao.findDirectorByName(movieDirectorFullName) 77 | directorId = newDirector?.id ?: directorDao.insert(Director(fullName = movieDirectorFullName)) 78 | } 79 | 80 | if (movieTitleExtra != null) { 81 | // clicked on item row -> update 82 | val movieToUpdate = movieDao.findMovieByTitle(movieTitleExtra) 83 | if (movieToUpdate != null) { 84 | if (movieToUpdate.title != movieTitle) { 85 | movieToUpdate.title = movieTitle 86 | if (directorId != -1L) { 87 | movieToUpdate.directorId = directorId 88 | } 89 | movieDao.update(movieToUpdate) 90 | } 91 | } 92 | } else { 93 | // we can have many movies with same title but different director 94 | movieDao.insert(Movie(title = movieTitle, directorId = directorId)) 95 | } 96 | } 97 | 98 | companion object { 99 | private const val EXTRA_MOVIE_TITLE = "movie_title" 100 | private const val EXTRA_MOVIE_DIRECTOR_FULL_NAME = "movie_director_full_name" 101 | const val TAG_DIALOG_MOVIE_SAVE = "dialog_movie_save" 102 | 103 | fun newInstance(movieTitle: String?, movieDirectorFullName: String?): MovieSaveDialogFragment { 104 | val fragment = MovieSaveDialogFragment() 105 | val args = Bundle().apply { 106 | putString(EXTRA_MOVIE_TITLE, movieTitle) 107 | putString(EXTRA_MOVIE_DIRECTOR_FULL_NAME, movieDirectorFullName) 108 | } 109 | fragment.arguments = args 110 | return fragment 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /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/java/com/lomza/moviesroom/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.lomza.moviesroom 2 | 3 | import android.os.Bundle 4 | import android.view.Menu 5 | import android.view.MenuItem 6 | import android.widget.Toast 7 | import androidx.annotation.NonNull 8 | import androidx.appcompat.app.AppCompatActivity 9 | import androidx.fragment.app.DialogFragment 10 | import androidx.fragment.app.Fragment 11 | import androidx.fragment.app.FragmentTransaction 12 | import com.google.android.material.bottomnavigation.BottomNavigationView 13 | import com.lomza.moviesroom.db.MoviesDatabase 14 | import com.lomza.moviesroom.db.rePopulateDb 15 | import com.lomza.moviesroom.director.DirectorSaveDialogFragment 16 | import com.lomza.moviesroom.director.DirectorsListFragment 17 | import com.lomza.moviesroom.movie.MovieSaveDialogFragment 18 | import com.lomza.moviesroom.movie.MoviesListFragment 19 | import kotlinx.android.synthetic.main.activity_main.* 20 | import kotlinx.android.synthetic.main.content_main.* 21 | import kotlinx.coroutines.Dispatchers 22 | import kotlinx.coroutines.GlobalScope 23 | import kotlinx.coroutines.launch 24 | 25 | class MainActivity : AppCompatActivity() { 26 | 27 | private var shownFragment: Fragment? = null 28 | 29 | override fun onCreate(savedInstanceState: Bundle?) { 30 | super.onCreate(savedInstanceState) 31 | setContentView(R.layout.activity_main) 32 | initToolbar(getString(R.string.app_name)) 33 | initView() 34 | if (savedInstanceState == null) { 35 | showFragment(MoviesListFragment.newInstance()) 36 | } 37 | } 38 | 39 | private fun initToolbar(@NonNull title: String?) { 40 | toolbar.title = title 41 | setSupportActionBar(toolbar) 42 | } 43 | 44 | private fun initView() { 45 | navigation.setOnNavigationItemSelectedListener(object : BottomNavigationView.OnNavigationItemSelectedListener { 46 | override fun onNavigationItemSelected(@NonNull item: MenuItem): Boolean { 47 | when (item.itemId) { 48 | R.id.navigation_movies -> { 49 | MOVIES_SHOWN = true 50 | showFragment(MoviesListFragment.newInstance()) 51 | return true 52 | } 53 | R.id.navigation_directors -> { 54 | MOVIES_SHOWN = false 55 | showFragment(DirectorsListFragment.newInstance()) 56 | return true 57 | } 58 | } 59 | return false 60 | } 61 | }) 62 | fab.setOnClickListener { showSaveDialog() } 63 | } 64 | 65 | private fun showFragment(fragment: Fragment) { 66 | val fragmentTransaction: FragmentTransaction = supportFragmentManager.beginTransaction() 67 | fragmentTransaction.replace(R.id.fragmentHolder, fragment) 68 | fragmentTransaction.commitNow() 69 | shownFragment = fragment 70 | } 71 | 72 | private fun showSaveDialog() { 73 | val dialogFragment: DialogFragment 74 | val tag: String 75 | if (MOVIES_SHOWN) { 76 | dialogFragment = MovieSaveDialogFragment.newInstance(null, null) 77 | tag = MovieSaveDialogFragment.TAG_DIALOG_MOVIE_SAVE 78 | } else { 79 | dialogFragment = DirectorSaveDialogFragment.newInstance(null) 80 | tag = DirectorSaveDialogFragment.TAG_DIALOG_DIRECTOR_SAVE 81 | } 82 | dialogFragment.show(supportFragmentManager, tag) 83 | } 84 | 85 | override fun onCreateOptionsMenu(menu: Menu?): Boolean { 86 | menuInflater.inflate(R.menu.overflow, menu) 87 | return true 88 | } 89 | 90 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 91 | return when (item.itemId) { 92 | R.id.action_delete_list_data -> { 93 | deleteCurrentListData() 94 | true 95 | } 96 | R.id.action_re_create_database -> { 97 | reCreateDatabase() 98 | true 99 | } 100 | R.id.action_export_to_csv_file -> { 101 | exportDatabaseToCSVFile() 102 | true 103 | } 104 | else -> super.onOptionsItemSelected(item) 105 | } 106 | } 107 | 108 | private fun deleteCurrentListData() { 109 | if (MOVIES_SHOWN) { 110 | (shownFragment as MoviesListFragment).removeData() 111 | } else { 112 | (shownFragment as DirectorsListFragment).removeData() 113 | } 114 | } 115 | 116 | private fun reCreateDatabase() { 117 | GlobalScope.launch(Dispatchers.IO) { 118 | rePopulateDb(MoviesDatabase.getDatabase(this@MainActivity)) 119 | } 120 | } 121 | private fun getCSVFileName() : String = 122 | if (MOVIES_SHOWN) "MoviesRoomExample.csv" else "DirectorsRoomExample.csv" 123 | 124 | private fun exportDatabaseToCSVFile() { 125 | val csvFile = generateFile(this, getCSVFileName()) 126 | if (csvFile != null) { 127 | if (MOVIES_SHOWN) { 128 | (shownFragment as MoviesListFragment).exportMoviesWithDirectorsToCSVFile(csvFile) 129 | } else { 130 | (shownFragment as DirectorsListFragment).exportDirectorsToCSVFile(csvFile) 131 | } 132 | 133 | Toast.makeText(this, getString(R.string.csv_file_generated_text), Toast.LENGTH_LONG).show() 134 | val intent = goToFileIntent(this, csvFile) 135 | startActivity(intent) 136 | } else { 137 | Toast.makeText(this, getString(R.string.csv_file_not_generated_text), Toast.LENGTH_LONG).show() 138 | } 139 | } 140 | 141 | companion object { 142 | private var MOVIES_SHOWN = true 143 | } 144 | } -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | --------------------------------------------------------------------------------