From 7d47c475584c5f42c28d7cb11cd43ef0c52ec72c Mon Sep 17 00:00:00 2001 From: Rd Date: Fri, 2 Jun 2023 12:16:50 +0530 Subject: [PATCH 01/52] Landing Page Design --- Paintroid/src/main/AndroidManifest.xml | 3 ++ .../catrobat/paintroid/LandingPageActivity.kt | 25 +++++++++++ .../drawable/ic_pocketpaint_edit_circle.xml | 44 +++++++++++++++++++ .../activity_pocketpaint_landing_page.xml | 42 ++++++++++++++++++ ...ocketpaint_layout_landing_page_top_bar.xml | 37 ++++++++++++++++ app/src/main/AndroidManifest.xml | 6 ++- 6 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt create mode 100644 Paintroid/src/main/res/drawable/ic_pocketpaint_edit_circle.xml create mode 100644 Paintroid/src/main/res/layout/activity_pocketpaint_landing_page.xml create mode 100644 Paintroid/src/main/res/layout/pocketpaint_layout_landing_page_top_bar.xml diff --git a/Paintroid/src/main/AndroidManifest.xml b/Paintroid/src/main/AndroidManifest.xml index bb8447c91a..596f3d2439 100644 --- a/Paintroid/src/main/AndroidManifest.xml +++ b/Paintroid/src/main/AndroidManifest.xml @@ -41,6 +41,9 @@ android:resource="@xml/filepaths"/> + (R.id.pocketpaint_image_preview) + val editCircleIcon = findViewById(R.id.pocketpaint_image_edit_circle) + + val imagePreviewClickListener = View.OnClickListener { + val intent = Intent(this, MainActivity::class.java) + startActivity(intent) + } + + imagePreview.setOnClickListener(imagePreviewClickListener) + editCircleIcon.setOnClickListener(imagePreviewClickListener) + } +} \ No newline at end of file diff --git a/Paintroid/src/main/res/drawable/ic_pocketpaint_edit_circle.xml b/Paintroid/src/main/res/drawable/ic_pocketpaint_edit_circle.xml new file mode 100644 index 0000000000..76fffdea87 --- /dev/null +++ b/Paintroid/src/main/res/drawable/ic_pocketpaint_edit_circle.xml @@ -0,0 +1,44 @@ + + + + + + + \ No newline at end of file diff --git a/Paintroid/src/main/res/layout/activity_pocketpaint_landing_page.xml b/Paintroid/src/main/res/layout/activity_pocketpaint_landing_page.xml new file mode 100644 index 0000000000..7543274caf --- /dev/null +++ b/Paintroid/src/main/res/layout/activity_pocketpaint_landing_page.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Paintroid/src/main/res/layout/pocketpaint_layout_landing_page_top_bar.xml b/Paintroid/src/main/res/layout/pocketpaint_layout_landing_page_top_bar.xml new file mode 100644 index 0000000000..9acbed0dfa --- /dev/null +++ b/Paintroid/src/main/res/layout/pocketpaint_layout_landing_page_top_bar.xml @@ -0,0 +1,37 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4ffe27c5a3..e6e6bdf8bc 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -39,12 +39,16 @@ + + From 9d736f0fa6281b96f99291bdd51995981c051013 Mon Sep 17 00:00:00 2001 From: Rd Date: Sun, 4 Jun 2023 09:04:35 +0530 Subject: [PATCH 02/52] Room Database Declaration --- Paintroid/build.gradle | 5 ++++ .../catrobat/paintroid/LandingPageActivity.kt | 8 ++++++ .../paintroid/data/local/dao/ProjectDao.kt | 25 +++++++++++++++++++ .../data/local/database/ProjectDatabase.kt | 15 +++++++++++ .../local/database/ProjectDatabaseProvider.kt | 20 +++++++++++++++ .../org/catrobat/paintroid/model/Project.kt | 18 +++++++++++++ .../tools/implementation/BaseTool.kt | 1 - .../paintroid/ui/tools/BrushToolView.kt | 1 + .../ui/tools/DefaultBrushToolOptionsView.kt | 1 + .../ui/tools/DefaultSmudgeToolOptionsView.kt | 1 + build.gradle | 2 +- 11 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt create mode 100644 Paintroid/src/main/java/org/catrobat/paintroid/data/local/database/ProjectDatabase.kt create mode 100644 Paintroid/src/main/java/org/catrobat/paintroid/data/local/database/ProjectDatabaseProvider.kt create mode 100644 Paintroid/src/main/java/org/catrobat/paintroid/model/Project.kt diff --git a/Paintroid/build.gradle b/Paintroid/build.gradle index 3bc0faf935..2e1745bbf8 100644 --- a/Paintroid/build.gradle +++ b/Paintroid/build.gradle @@ -21,6 +21,7 @@ apply plugin: 'com.android.library' apply plugin: 'com.hiya.jacoco-android' apply plugin: 'com.getkeepsafe.dexcount' apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' apply plugin: 'org.catrobat.gradle.androidemulators' apply plugin: 'maven-publish' @@ -151,6 +152,10 @@ dependencies { androidTestImplementation "androidx.test.uiautomator:uiautomator:2.2.0" testImplementation "androidx.test:core-ktx:1.4.0" implementation 'com.android.support.test.espresso:espresso-idling-resource:3.1.0' + + def room_version = "2.3.0" + implementation "androidx.room:room-ktx:$room_version" + kapt "androidx.room:room-compiler:$room_version" } tasks.withType(Javadoc).all { diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt b/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt index 62f943ea57..a90348d275 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt @@ -5,12 +5,20 @@ import android.os.Bundle import android.view.View import android.widget.ImageView import androidx.appcompat.app.AppCompatActivity +import org.catrobat.paintroid.data.local.database.ProjectDatabase +import org.catrobat.paintroid.data.local.database.ProjectDatabaseProvider class LandingPageActivity: AppCompatActivity() { + companion object { + lateinit var projectDB: ProjectDatabase + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_pocketpaint_landing_page) + projectDB = ProjectDatabaseProvider.getDatabase(applicationContext) + val imagePreview = findViewById(R.id.pocketpaint_image_preview) val editCircleIcon = findViewById(R.id.pocketpaint_image_edit_circle) diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt b/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt new file mode 100644 index 0000000000..ac9c72d340 --- /dev/null +++ b/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt @@ -0,0 +1,25 @@ +package org.catrobat.paintroid.data.local.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.Query +import org.catrobat.paintroid.model.Project + +@Dao +interface ProjectDao { + + @Insert + fun insertProject(project: Project) + + @Query("UPDATE Project SET path= :projectUri, imagePreviewPath= :imagePreviewPath WHERE name= :name") + fun updateProjectUri(name: String, imagePreviewPath: String, projectUri: String) + + @Query("SELECT * FROM Project ORDER BY lastModified DESC") + fun getProjects(): List + + @Query("DELETE FROM Project WHERE id= :id") + fun deleteProject(id: Int) + + @Query("DELETE FROM Project") + fun deleteAllProjects() +} \ No newline at end of file diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/data/local/database/ProjectDatabase.kt b/Paintroid/src/main/java/org/catrobat/paintroid/data/local/database/ProjectDatabase.kt new file mode 100644 index 0000000000..929fa5d019 --- /dev/null +++ b/Paintroid/src/main/java/org/catrobat/paintroid/data/local/database/ProjectDatabase.kt @@ -0,0 +1,15 @@ +package org.catrobat.paintroid.data.local.database + +import androidx.room.Database +import androidx.room.RoomDatabase +import org.catrobat.paintroid.data.local.dao.ProjectDao +import org.catrobat.paintroid.model.Project + +@Database( + entities = [Project::class], + version = 1 +) +abstract class ProjectDatabase: RoomDatabase() { + + abstract val dao: ProjectDao +} \ No newline at end of file diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/data/local/database/ProjectDatabaseProvider.kt b/Paintroid/src/main/java/org/catrobat/paintroid/data/local/database/ProjectDatabaseProvider.kt new file mode 100644 index 0000000000..8698cb60ac --- /dev/null +++ b/Paintroid/src/main/java/org/catrobat/paintroid/data/local/database/ProjectDatabaseProvider.kt @@ -0,0 +1,20 @@ +package org.catrobat.paintroid.data.local.database + +import android.content.Context +import androidx.room.Room + +object ProjectDatabaseProvider { + var projectDatabase: ProjectDatabase? = null + + fun getDatabase(context: Context): ProjectDatabase{ + return projectDatabase?: synchronized(this){ + projectDatabase ?: buildDatabase(context).also { projectDatabase = it } + } + } + + fun buildDatabase(context: Context): ProjectDatabase{ + return Room.databaseBuilder(context, ProjectDatabase::class.java, "projects.db") + .allowMainThreadQueries() + .build() + } +} \ No newline at end of file diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/model/Project.kt b/Paintroid/src/main/java/org/catrobat/paintroid/model/Project.kt new file mode 100644 index 0000000000..1beed5934a --- /dev/null +++ b/Paintroid/src/main/java/org/catrobat/paintroid/model/Project.kt @@ -0,0 +1,18 @@ +package org.catrobat.paintroid.model + +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity +data class Project( + var name: String, + val path: String, + var lastModified: String, + val creationDate: String, + val resolution: String, + val format: String, + val size: Int, + val imagePreviewPath: String, + @PrimaryKey(autoGenerate = true) + val id: Int = 0 +) \ No newline at end of file diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/BaseTool.kt b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/BaseTool.kt index f42992ef91..156d3c2aa3 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/BaseTool.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/BaseTool.kt @@ -39,7 +39,6 @@ import org.catrobat.paintroid.tools.common.ScrollBehavior import org.catrobat.paintroid.tools.options.ToolOptionsViewController abstract class BaseTool( - @JvmField open var contextCallback: ContextCallback, @JvmField var toolOptionsViewController: ToolOptionsViewController, @JvmField diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/ui/tools/BrushToolView.kt b/Paintroid/src/main/java/org/catrobat/paintroid/ui/tools/BrushToolView.kt index a7978223af..cdfb080742 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/ui/tools/BrushToolView.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/ui/tools/BrushToolView.kt @@ -124,6 +124,7 @@ class BrushToolView : View, BrushToolPreview { when (callback?.toolType) { ToolType.BRUSH, ToolType.CURSOR, ToolType.LINE, ToolType.WATERCOLOR -> drawLinePreview(canvas) ToolType.ERASER -> drawEraserPreview(canvas) + else -> Unit } } diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/ui/tools/DefaultBrushToolOptionsView.kt b/Paintroid/src/main/java/org/catrobat/paintroid/ui/tools/DefaultBrushToolOptionsView.kt index 6e6a03f9d8..ff369fb55c 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/ui/tools/DefaultBrushToolOptionsView.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/ui/tools/DefaultBrushToolOptionsView.kt @@ -150,6 +150,7 @@ class DefaultBrushToolOptionsView(rootView: ViewGroup) : BrushToolOptionsView { when (strokeCap) { Cap.ROUND -> strokeCapButtonsGroup.check(buttonCircle.id) Cap.SQUARE -> strokeCapButtonsGroup.check(buttonRect.id) + else -> Unit } } diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/ui/tools/DefaultSmudgeToolOptionsView.kt b/Paintroid/src/main/java/org/catrobat/paintroid/ui/tools/DefaultSmudgeToolOptionsView.kt index 07cb1c732b..6726254d37 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/ui/tools/DefaultSmudgeToolOptionsView.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/ui/tools/DefaultSmudgeToolOptionsView.kt @@ -189,6 +189,7 @@ class DefaultSmudgeToolOptionsView(rootView: ViewGroup) : SmudgeToolOptionsView when (strokeCap) { Cap.ROUND -> strokeButtonsGroup.check(buttonCircle.id) Cap.SQUARE -> strokeButtonsGroup.check(buttonRect.id) + else -> Unit } } diff --git a/build.gradle b/build.gradle index 9734277b42..bedbb5a8a3 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.5.30' + ext.kotlin_version = '1.7.10' repositories { mavenCentral() google() From 8999e2026e254d9d0b6655baf4f67e16d7a52a31 Mon Sep 17 00:00:00 2001 From: Rd Date: Sun, 4 Jun 2023 16:50:55 +0530 Subject: [PATCH 03/52] Save Project to Database --- .../java/org/catrobat/paintroid/FileIO.kt | 1 + .../org/catrobat/paintroid/MainActivity.kt | 3 +- .../paintroid/common/MainActivityConstants.kt | 8 +- .../contract/MainActivityContracts.kt | 14 +++ .../paintroid/data/local/dao/ProjectDao.kt | 4 +- .../paintroid/dialog/SaveInformationDialog.kt | 50 ++++++-- .../catrobat/paintroid/iotasks/SaveImage.kt | 110 ++++++++++++++++-- .../org/catrobat/paintroid/model/Project.kt | 6 +- .../presenter/MainActivityPresenter.kt | 47 ++++---- .../paintroid/ui/MainActivityInteractor.kt | 27 ++++- .../res/layout/dialog_pocketpaint_save.xml | 1 + .../menu/menu_pocketpaint_more_options.xml | 3 + Paintroid/src/main/res/values/string.xml | 2 + 13 files changed, 223 insertions(+), 53 deletions(-) diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/FileIO.kt b/Paintroid/src/main/java/org/catrobat/paintroid/FileIO.kt index 895c4484f2..2bc5845817 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/FileIO.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/FileIO.kt @@ -92,6 +92,7 @@ object FileIO { @JvmField var storeImageUri: Uri? = null + var storeImagePreviewUri: Uri? = null var temporaryFilePath: String? = null diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt index e393a5d841..85c98b3093 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt @@ -380,6 +380,7 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { when (item.itemId) { R.id.pocketpaint_options_export -> presenterMain.saveCopyClicked(true) R.id.pocketpaint_options_save_image -> presenterMain.saveImageClicked() + R.id.pocketpaint_options_save_project -> presenterMain.saveProjectClicked() R.id.pocketpaint_options_save_duplicate -> presenterMain.saveCopyClicked(false) R.id.pocketpaint_replace_image -> presenterMain.replaceImageClicked() R.id.pocketpaint_add_to_current_layer -> presenterMain.addImageToCurrentLayerClicked() @@ -638,7 +639,7 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { setSupportActionBar(toolbar) supportActionBar?.apply { setDisplayShowTitleEnabled(!isOpenedFromCatroid) - setDisplayHomeAsUpEnabled(isOpenedFromCatroid) + setDisplayHomeAsUpEnabled(true) setHomeButtonEnabled(true) setDisplayShowHomeEnabled(false) } diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/common/MainActivityConstants.kt b/Paintroid/src/main/java/org/catrobat/paintroid/common/MainActivityConstants.kt index 263e9e0f5e..c789142d90 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/common/MainActivityConstants.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/common/MainActivityConstants.kt @@ -25,6 +25,7 @@ const val SAVE_IMAGE_DEFAULT = 1 const val SAVE_IMAGE_NEW_EMPTY = 2 const val SAVE_IMAGE_LOAD_NEW = 3 const val SAVE_IMAGE_FINISH = 4 +const val SAVE_PROJECT_DEFAULT = 5 const val LOAD_IMAGE_DEFAULT = 1 const val LOAD_IMAGE_IMPORT_PNG = 2 @@ -43,6 +44,7 @@ const val PERMISSION_EXTERNAL_STORAGE_SAVE_CONFIRMED_NEW_EMPTY = 4 const val PERMISSION_EXTERNAL_STORAGE_SAVE_CONFIRMED_FINISH = 5 const val PERMISSION_REQUEST_CODE_REPLACE_PICTURE = 6 const val PERMISSION_REQUEST_CODE_IMPORT_PICTURE = 7 +const val PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT = 8 const val RESULT_INTRO_MW_NOT_SUPPORTED = 10 @@ -51,7 +53,8 @@ class MainActivityConstants private constructor() { SAVE_IMAGE_DEFAULT, SAVE_IMAGE_NEW_EMPTY, SAVE_IMAGE_LOAD_NEW, - SAVE_IMAGE_FINISH + SAVE_IMAGE_FINISH, + SAVE_PROJECT_DEFAULT ) @Retention(AnnotationRetention.SOURCE) annotation class SaveImageRequestCode @@ -83,7 +86,8 @@ class MainActivityConstants private constructor() { PERMISSION_EXTERNAL_STORAGE_SAVE_CONFIRMED_NEW_EMPTY, PERMISSION_EXTERNAL_STORAGE_SAVE_CONFIRMED_FINISH, PERMISSION_REQUEST_CODE_REPLACE_PICTURE, - PERMISSION_REQUEST_CODE_IMPORT_PICTURE + PERMISSION_REQUEST_CODE_IMPORT_PICTURE, + PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT ) @Retention(AnnotationRetention.SOURCE) annotation class PermissionRequestCode diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/contract/MainActivityContracts.kt b/Paintroid/src/main/java/org/catrobat/paintroid/contract/MainActivityContracts.kt index cb3670335f..be2f7c86b1 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/contract/MainActivityContracts.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/contract/MainActivityContracts.kt @@ -206,6 +206,8 @@ interface MainActivityContracts { fun saveImageClicked() + fun saveProjectClicked() + fun shareImageClicked() fun enterHideButtonsClicked() @@ -254,6 +256,8 @@ interface MainActivityContracts { fun saveImageConfirmClicked(requestCode: Int, uri: Uri?) + fun saveProjectConfirmClicked(requestCode: Int, uri: Uri?, imagePreviewUri: Uri?) + fun saveCopyConfirmClicked(requestCode: Int, uri: Uri?) fun undoClicked() @@ -344,6 +348,16 @@ interface MainActivityContracts { context: Context ) + fun saveProject( + callback: SaveImageCallback, + requestCode: Int, + layerModel: LayerContracts.Model, + commandSerializer: CommandSerializer, + uri: Uri?, + imagePreviewUri: Uri?, + context: Context + ) + fun loadFile( callback: LoadImageCallback, requestCode: Int, diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt b/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt index ac9c72d340..e773d8114a 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt @@ -11,8 +11,8 @@ interface ProjectDao { @Insert fun insertProject(project: Project) - @Query("UPDATE Project SET path= :projectUri, imagePreviewPath= :imagePreviewPath WHERE name= :name") - fun updateProjectUri(name: String, imagePreviewPath: String, projectUri: String) + @Query("UPDATE Project SET path= :projectUri, imagePreviewPath= :imagePreviewPath, lastModified= :lastModified WHERE name= :name") + fun updateProjectUri(name: String, imagePreviewPath: String, projectUri: String, lastModified: Long) @Query("SELECT * FROM Project ORDER BY lastModified DESC") fun getProjects(): List diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/dialog/SaveInformationDialog.kt b/Paintroid/src/main/java/org/catrobat/paintroid/dialog/SaveInformationDialog.kt index b58f914105..80af3580c3 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/dialog/SaveInformationDialog.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/dialog/SaveInformationDialog.kt @@ -25,12 +25,9 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.AdapterView +import android.widget.* import android.widget.AdapterView.OnItemSelectedListener -import android.widget.ArrayAdapter -import android.widget.SeekBar import android.widget.SeekBar.OnSeekBarChangeListener -import android.widget.Spinner import androidx.appcompat.app.AlertDialog import androidx.appcompat.widget.AppCompatEditText import androidx.appcompat.widget.AppCompatImageButton @@ -42,6 +39,7 @@ import org.catrobat.paintroid.FileIO.FileType.JPG import org.catrobat.paintroid.FileIO.FileType.CATROBAT import org.catrobat.paintroid.FileIO.FileType.ORA import org.catrobat.paintroid.R +import org.catrobat.paintroid.common.PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT import java.util.Locale private const val STANDARD_FILE_NAME = "image" @@ -70,10 +68,9 @@ class SaveInformationDialog : isStandard: Boolean, isExport: Boolean ): SaveInformationDialog { - if (isStandard) { - FileIO.filename = STANDARD_FILE_NAME - FileIO.compressFormat = Bitmap.CompressFormat.PNG - FileIO.fileType = PNG + when { + permissionCode == PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT -> setFileProperties(STANDARD_FILE_NAME, Bitmap.CompressFormat.PNG, CATROBAT) + isStandard -> setFileProperties(STANDARD_FILE_NAME, Bitmap.CompressFormat.PNG, PNG) } return SaveInformationDialog().apply { arguments = Bundle().apply { @@ -87,6 +84,12 @@ class SaveInformationDialog : } } } + + private fun setFileProperties(filename: String, compressFormat: Bitmap.CompressFormat, fileType: FileType) { + FileIO.filename = filename + FileIO.compressFormat = compressFormat + FileIO.fileType = fileType + } } override fun onCreate(savedInstanceState: Bundle?) { @@ -102,16 +105,45 @@ class SaveInformationDialog : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) initViews(view) + handlePermission(view) setSpinnerSelection() } + private fun handlePermission(view: View) { + if (permission == PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT) { + hideImageFormatViews(view) + } else { + showImageFormatViews() + } + } + + private fun hideImageFormatViews(view: View) { + val imageFormatTitle = view.findViewById(R.id.pocketpaint_image_format_title) + val imageFormatSaveInfo = view.findViewById(R.id.pocketpaint_btn_save_info) + val imageFormatSaveDivider = view.findViewById(R.id.pocketpaint_view_save_divider) + imageFormatTitle.visibility = View.GONE + imageFormatSaveInfo.visibility = View.GONE + imageFormatSaveDivider.visibility = View.GONE + spinner.visibility = View.GONE + } + + private fun showImageFormatViews() { + spinner.visibility = View.VISIBLE + } + + @SuppressLint("InflateParams") override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { inflater = requireActivity().layoutInflater val customLayout = inflater.inflate(R.layout.dialog_pocketpaint_save, null) + val dialogTitle = if(permission == PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT){ + R.string.dialog_save_project_title + } else { + R.string.dialog_save_image_title + } onViewCreated(customLayout, savedInstanceState) return AlertDialog.Builder(requireContext(), R.style.PocketPaintAlertDialog) - .setTitle(R.string.dialog_save_image_title) + .setTitle(dialogTitle) .setView(customLayout) .setPositiveButton(R.string.save_button_text) { _, _ -> FileIO.filename = imageName.text.toString() diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/SaveImage.kt b/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/SaveImage.kt index 70de33a490..cd63be8750 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/SaveImage.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/SaveImage.kt @@ -21,6 +21,7 @@ package org.catrobat.paintroid.iotasks import android.content.ContentResolver import android.content.Context import android.graphics.Bitmap +import android.graphics.BitmapFactory import android.net.Uri import android.util.Log import androidx.test.espresso.idling.CountingIdlingResource @@ -29,10 +30,13 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.catrobat.paintroid.FileIO +import org.catrobat.paintroid.LandingPageActivity.Companion.projectDB import org.catrobat.paintroid.command.serialization.CommandSerializer import org.catrobat.paintroid.contract.LayerContracts +import org.catrobat.paintroid.model.Project import java.io.IOException import java.lang.ref.WeakReference +import java.util.* class SaveImage( activity: SaveImageCallback, @@ -40,7 +44,9 @@ class SaveImage( private val layerModel: LayerContracts.Model, private val commandSerializer: CommandSerializer, private var uri: Uri?, + private var imagePreviewUri: Uri?, private val saveAsCopy: Boolean, + private val saveProject: Boolean, private val context: Context, private val scopeIO: CoroutineScope, private val idlingResource: CountingIdlingResource @@ -64,6 +70,20 @@ class SaveImage( } } + private fun getImagePreviewUri( + callback: SaveImageCallback, + bitmap: Bitmap? + ): Uri? { + val filename = FileIO.defaultFileName + return if (imagePreviewUri == null) { + val imagePreviewFilename = filename.replace(".catrobat-image", ".png") + val imageUri = FileIO.saveBitmapToFile(imagePreviewFilename, bitmap, callback.contentResolver, context) + imageUri + } else { + uri?.let { FileIO.saveBitmapToUri(it, bitmap, context) } + } + } + private fun saveOraFile( layers: List, uri: Uri, @@ -110,33 +130,72 @@ class SaveImage( } var currentUri: Uri? = null + var imagePreviewPath: Uri? = null scopeIO.launch { try { idlingResource.increment() val bitmap = layerModel.getBitmapOfAllLayers() val filename = FileIO.defaultFileName - currentUri = if (FileIO.fileType == FileIO.FileType.ORA) { - val layers = layerModel.layers - if (uri != null && filename.endsWith(FileIO.FileType.ORA.toExtension())) { + if (saveProject) { + FileIO.fileType = FileIO.FileType.CATROBAT + currentUri = if (uri != null) { uri?.let { - saveOraFile(layers, it, filename, bitmap, callback.contentResolver) + commandSerializer.overWriteFile(filename, it, callback.contentResolver) } } else { - val imageUri = exportOraFile(layers, filename, bitmap, callback.contentResolver) - imageUri + commandSerializer.writeToFile(filename) } - } else if (FileIO.fileType == FileIO.FileType.CATROBAT) { - if (uri != null) { + imagePreviewPath = getImagePreviewUri(callback, bitmap) + val date = Calendar.getInstance().timeInMillis + if(uri != null){ uri?.let { - commandSerializer.overWriteFile(filename, it, callback.contentResolver) + projectDB.dao.updateProjectUri(filename, imagePreviewPath.toString(), currentUri.toString(), date) } } else { - commandSerializer.writeToFile(filename) + val dimensions = getImageDimensions(imagePreviewPath) + val size = getImageSize(imagePreviewPath) + projectDB.dao.insertProject( + Project( + filename, + currentUri.toString(), + date, + date, + "${dimensions?.first} x ${dimensions?.second}", + FileIO.fileType.toString(), + size, + imagePreviewPath.toString() + ) + ) } } else { - getImageUri(callback, bitmap) + currentUri = if (FileIO.fileType == FileIO.FileType.ORA) { + val layers = layerModel.layers + if (uri != null && filename.endsWith(FileIO.FileType.ORA.toExtension())) { + uri?.let { + saveOraFile(layers, it, filename, bitmap, callback.contentResolver) + } + } else { + val imageUri = + exportOraFile(layers, filename, bitmap, callback.contentResolver) + imageUri + } + } else if (FileIO.fileType == FileIO.FileType.CATROBAT) { + if (uri != null) { + uri?.let { + commandSerializer.overWriteFile( + filename, + it, + callback.contentResolver + ) + } + } else { + commandSerializer.writeToFile(filename) + } + } else { + getImageUri(callback, bitmap) + } + idlingResource.decrement() } - idlingResource.decrement() } catch (e: Exception) { idlingResource.decrement() when (e) { @@ -153,6 +212,33 @@ class SaveImage( } } + private fun getImageDimensions(uri: Uri?): Pair? { + val inputStream = uri?.let { context.contentResolver.openInputStream(it) } + val options = BitmapFactory.Options() + options.inJustDecodeBounds = true + BitmapFactory.decodeStream(inputStream, null, options) + inputStream?.close() + + if (options.outWidth != -1 && options.outHeight != -1) { + return Pair(options.outWidth, options.outHeight) + } + + return null + } + + private fun getImageSize(uri: Uri?): Double { + var size = 0.0 + try { + val inputStream = uri?.let { context.contentResolver.openInputStream(it) } + val bytes = inputStream?.available()?.toLong() ?: 0 + size = bytes.toDouble() / (1024 * 1024) + inputStream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + return size + } + interface SaveImageCallback { val contentResolver: ContentResolver val isFinishing: Boolean diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/model/Project.kt b/Paintroid/src/main/java/org/catrobat/paintroid/model/Project.kt index 1beed5934a..ba375c32d3 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/model/Project.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/model/Project.kt @@ -7,11 +7,11 @@ import androidx.room.PrimaryKey data class Project( var name: String, val path: String, - var lastModified: String, - val creationDate: String, + var lastModified: Long, + val creationDate: Long, val resolution: String, val format: String, - val size: Int, + val size: Double, val imagePreviewPath: String, @PrimaryKey(autoGenerate = true) val id: Int = 0 diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt b/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt index 229b3333d3..f9d3ee409f 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt @@ -53,31 +53,12 @@ import org.catrobat.paintroid.colorpicker.ColorHistory import org.catrobat.paintroid.command.CommandFactory import org.catrobat.paintroid.command.CommandManager import org.catrobat.paintroid.command.serialization.CommandSerializer -import org.catrobat.paintroid.common.CREATE_FILE_DEFAULT -import org.catrobat.paintroid.common.LOAD_IMAGE_CATROID -import org.catrobat.paintroid.common.LOAD_IMAGE_DEFAULT -import org.catrobat.paintroid.common.LOAD_IMAGE_IMPORT_PNG +import org.catrobat.paintroid.common.* import org.catrobat.paintroid.common.MainActivityConstants.ActivityRequestCode import org.catrobat.paintroid.common.MainActivityConstants.CreateFileRequestCode import org.catrobat.paintroid.common.MainActivityConstants.LoadImageRequestCode import org.catrobat.paintroid.common.MainActivityConstants.PermissionRequestCode import org.catrobat.paintroid.common.MainActivityConstants.SaveImageRequestCode -import org.catrobat.paintroid.common.PERMISSION_EXTERNAL_STORAGE_SAVE -import org.catrobat.paintroid.common.PERMISSION_EXTERNAL_STORAGE_SAVE_CONFIRMED_FINISH -import org.catrobat.paintroid.common.PERMISSION_EXTERNAL_STORAGE_SAVE_CONFIRMED_LOAD_NEW -import org.catrobat.paintroid.common.PERMISSION_EXTERNAL_STORAGE_SAVE_CONFIRMED_NEW_EMPTY -import org.catrobat.paintroid.common.PERMISSION_EXTERNAL_STORAGE_SAVE_COPY -import org.catrobat.paintroid.common.PERMISSION_REQUEST_CODE_IMPORT_PICTURE -import org.catrobat.paintroid.common.PERMISSION_REQUEST_CODE_REPLACE_PICTURE -import org.catrobat.paintroid.common.REQUEST_CODE_IMPORT_PNG -import org.catrobat.paintroid.common.REQUEST_CODE_INTRO -import org.catrobat.paintroid.common.REQUEST_CODE_LOAD_PICTURE -import org.catrobat.paintroid.common.RESULT_INTRO_MW_NOT_SUPPORTED -import org.catrobat.paintroid.common.SAVE_IMAGE_DEFAULT -import org.catrobat.paintroid.common.SAVE_IMAGE_FINISH -import org.catrobat.paintroid.common.SAVE_IMAGE_LOAD_NEW -import org.catrobat.paintroid.common.SAVE_IMAGE_NEW_EMPTY -import org.catrobat.paintroid.common.TEMP_PICTURE_NAME import org.catrobat.paintroid.contract.MainActivityContracts import org.catrobat.paintroid.contract.MainActivityContracts.Interactor import org.catrobat.paintroid.contract.MainActivityContracts.MainView @@ -240,6 +221,14 @@ open class MainActivityPresenter( ) } + override fun saveProjectClicked() { + navigator.showSaveImageInformationDialogWhenStandalone( + PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT, + imageNumber, + false + ) + } + override fun shareImageClicked() { checkIfClippingToolNeedsAdjustment() view.refreshDrawingSurface() @@ -360,7 +349,8 @@ open class MainActivityPresenter( PERMISSION_EXTERNAL_STORAGE_SAVE_CONFIRMED_NEW_EMPTY, PERMISSION_EXTERNAL_STORAGE_SAVE_CONFIRMED_FINISH, PERMISSION_EXTERNAL_STORAGE_SAVE_COPY, - PERMISSION_EXTERNAL_STORAGE_SAVE -> checkForDefaultFilename() + PERMISSION_EXTERNAL_STORAGE_SAVE, + PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT -> checkForDefaultFilename() } } else { if (requestCode == PERMISSION_REQUEST_CODE_REPLACE_PICTURE) { @@ -504,6 +494,13 @@ open class MainActivityPresenter( ) checkForDefaultFilename() } + PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT -> { + saveProjectConfirmClicked( + SAVE_PROJECT_DEFAULT, + FileIO.storeImageUri, + FileIO.storeImagePreviewUri, + ) + } PERMISSION_EXTERNAL_STORAGE_SAVE_COPY -> { saveCopyConfirmClicked( SAVE_IMAGE_DEFAULT, @@ -562,6 +559,12 @@ open class MainActivityPresenter( interactor.saveImage(this, requestCode, workspace.layerModel, commandSerializer, uri, context) } + override fun saveProjectConfirmClicked(requestCode: Int, uri: Uri?, imagePreviewUri: Uri?) { + checkIfClippingToolNeedsAdjustment() + view.refreshDrawingSurface() + interactor.saveProject(this, requestCode, workspace.layerModel, commandSerializer, uri, imagePreviewUri, context) + } + override fun saveCopyConfirmClicked(requestCode: Int, uri: Uri?) { checkIfClippingToolNeedsAdjustment() view.refreshDrawingSurface() @@ -995,7 +998,7 @@ open class MainActivityPresenter( } when (requestCode) { SAVE_IMAGE_NEW_EMPTY -> onNewImage() - SAVE_IMAGE_DEFAULT -> { + SAVE_IMAGE_DEFAULT, SAVE_PROJECT_DEFAULT -> { } SAVE_IMAGE_FINISH -> { if (model.isOpenedFromCatroid) { diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/ui/MainActivityInteractor.kt b/Paintroid/src/main/java/org/catrobat/paintroid/ui/MainActivityInteractor.kt index f167239532..f6bce9c854 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/ui/MainActivityInteractor.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/ui/MainActivityInteractor.kt @@ -44,7 +44,12 @@ class MainActivityInteractor(private val idlingResource: CountingIdlingResource) uri: Uri?, context: Context ) { - SaveImage(callback, requestCode, layerModel, commandSerializer, uri, true, context, scopeIO, idlingResource).execute() + SaveImage(callback, requestCode, layerModel, commandSerializer, uri, null, true, + saveProject = false, + context = context, + scopeIO = scopeIO, + idlingResource = idlingResource + ).execute() } override fun createFile(callback: CreateFileCallback, requestCode: Int, filename: String) { @@ -59,7 +64,25 @@ class MainActivityInteractor(private val idlingResource: CountingIdlingResource) uri: Uri?, context: Context ) { - SaveImage(callback, requestCode, layerModel, commandSerializer, uri, false, context, scopeIO, idlingResource).execute() + SaveImage(callback, requestCode, layerModel, commandSerializer, uri, null, + saveAsCopy = false, + saveProject = false, + context = context, + scopeIO = scopeIO, + idlingResource = idlingResource + ).execute() + } + + override fun saveProject( + callback: SaveImageCallback, + requestCode: Int, + layerModel: LayerContracts.Model, + commandSerializer: CommandSerializer, + uri: Uri?, + imagePreviewUri: Uri?, + context: Context + ) { + SaveImage(callback, requestCode, layerModel, commandSerializer, uri, imagePreviewUri, false, true, context, scopeIO, idlingResource).execute() } override fun loadFile( diff --git a/Paintroid/src/main/res/layout/dialog_pocketpaint_save.xml b/Paintroid/src/main/res/layout/dialog_pocketpaint_save.xml index 3cb7da04bf..973f82a2c6 100644 --- a/Paintroid/src/main/res/layout/dialog_pocketpaint_save.xml +++ b/Paintroid/src/main/res/layout/dialog_pocketpaint_save.xml @@ -84,6 +84,7 @@ android:src="@drawable/ic_pocketpaint_save_info" /> + diff --git a/Paintroid/src/main/res/values/string.xml b/Paintroid/src/main/res/values/string.xml index de2ea72fb6..9d02e75721 100644 --- a/Paintroid/src/main/res/values/string.xml +++ b/Paintroid/src/main/res/values/string.xml @@ -54,6 +54,7 @@ Stickers @string/button_import_image @string/menu_save_image + @string/menu_save_project Tools Error load/save file Check image or SD-card! @@ -91,6 +92,7 @@ Add to current layer Save image Save copy + Save project Hide buttons Share image Send image via From 68ea0831944f3abd2f85548575c27ea1755206ff Mon Sep 17 00:00:00 2001 From: Rd Date: Mon, 5 Jun 2023 02:25:16 +0530 Subject: [PATCH 04/52] PAINTROID-609 Floating action buttons for landing page --- .../LandingPageActivityIntegrationTest.kt | 142 ++++++++++++++++++ .../catrobat/paintroid/LandingPageActivity.kt | 16 ++ .../org/catrobat/paintroid/MainActivity.kt | 32 ++-- .../res/drawable/ic_pocketpaint_download.xml | 5 + .../activity_pocketpaint_landing_page.xml | 46 ++++++ colorpicker/src/main/res/values/colors.xml | 1 + 6 files changed, 229 insertions(+), 13 deletions(-) create mode 100644 Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt create mode 100644 Paintroid/src/main/res/drawable/ic_pocketpaint_download.xml diff --git a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt new file mode 100644 index 0000000000..1aa7331baf --- /dev/null +++ b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt @@ -0,0 +1,142 @@ +package org.catrobat.paintroid.test.espresso + +import android.app.Activity +import android.app.Instrumentation +import android.content.ContentValues +import android.content.Intent +import android.graphics.Bitmap +import android.graphics.Color +import android.net.Uri +import android.os.Build +import android.os.Environment +import android.provider.MediaStore +import androidx.appcompat.widget.Toolbar +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.Espresso.pressBack +import androidx.test.espresso.action.ViewActions.* +import androidx.test.espresso.assertion.ViewAssertions +import androidx.test.espresso.intent.Intents +import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction +import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.rule.ActivityTestRule +import org.catrobat.paintroid.LandingPageActivity +import org.catrobat.paintroid.R +import org.catrobat.paintroid.test.espresso.util.BitmapLocationProvider +import org.catrobat.paintroid.test.espresso.util.DrawingSurfaceLocationProvider +import org.catrobat.paintroid.test.espresso.util.UiInteractions +import org.catrobat.paintroid.test.espresso.util.wrappers.DrawingSurfaceInteraction +import org.catrobat.paintroid.test.utils.ScreenshotOnFailRule +import org.junit.* +import org.junit.runner.RunWith +import java.io.File +import java.io.IOException + +@RunWith(AndroidJUnit4::class) +class LandingPageActivityIntegrationTest { + @get:Rule + var launchActivityRule = ActivityTestRule(LandingPageActivity::class.java) + + @get:Rule + var screenshotOnFailRule = ScreenshotOnFailRule() + + private lateinit var activity: LandingPageActivity + + companion object { + private lateinit var deletionFileList: ArrayList + } + + @Before + fun setUp() { + deletionFileList = ArrayList() + activity = launchActivityRule.activity + } + + @After + fun tearDown() { + for (file in deletionFileList) { + if (file != null && file.exists()) { + Assert.assertTrue(file.delete()) + } + } + } + + @Test + fun testTopAppBarDisplayed(){ + onView(ViewMatchers.isAssignableFrom(Toolbar::class.java)) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) + } + + @Test + fun testAppBarTitleDisplayPocketPaint() { + onView(withText("Pocket Paint")) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) + } + + @Test + fun testTwoFABDisplayed(){ + onView(withId(R.id.pocketpaint_fab_load_image)) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) + onView(withId(R.id.pocketpaint_fab_new_image)) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) + } + + @Test + fun testMyProjectsTextDisplayed(){ + onView(withText("My Projects")) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) + } + + @Test + fun testNewImage() { + onView(withId(R.id.pocketpaint_fab_new_image)).perform(click()) + DrawingSurfaceInteraction.onDrawingSurfaceView() + .perform(UiInteractions.touchAt(DrawingSurfaceLocationProvider.MIDDLE)) + DrawingSurfaceInteraction.onDrawingSurfaceView() + .checkPixelColor(Color.BLACK, BitmapLocationProvider.MIDDLE) + pressBack() + onView(withText(R.string.discard_button_text)).perform(click()) + onView(withId(R.id.pocketpaint_fab_new_image)).perform(click()) + DrawingSurfaceInteraction.onDrawingSurfaceView() + .checkPixelColor(Color.TRANSPARENT, BitmapLocationProvider.MIDDLE) + } + + @Test + fun testLoadImageIntentStarted() { + Intents.init() + val intent = Intent() + intent.data = createTestImageFile() + val resultOK = Instrumentation.ActivityResult(Activity.RESULT_OK, intent) + Intents.intending(hasAction(Intent.ACTION_GET_CONTENT)).respondWith(resultOK) + onView(withId(R.id.pocketpaint_fab_load_image)).perform(click()) + DrawingSurfaceInteraction.onDrawingSurfaceView() + .checkPixelColor(Color.BLACK, BitmapLocationProvider.MIDDLE) + Intents.release() + } + + private fun createTestImageFile(): Uri? { + val bitmap = Bitmap.createBitmap(400, 400, Bitmap.Config.ARGB_8888) + val contentValues = ContentValues() + contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, "testfile.jpg") + contentValues.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg") + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES) + } + val resolver = InstrumentationRegistry.getInstrumentation().targetContext.contentResolver + val imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues) + try { + val fos = imageUri?.let { resolver.openOutputStream(it) } + Assert.assertTrue(bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos)) + assert(fos != null) + fos?.close() + } catch (e: IOException) { + throw AssertionError("Picture file could not be created.", e) + } + val imageFile = File(imageUri?.path, "testfile.jpg") + deletionFileList.add(imageFile) + return imageUri + } +} \ No newline at end of file diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt b/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt index a90348d275..3927c2fe2d 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt @@ -5,12 +5,14 @@ import android.os.Bundle import android.view.View import android.widget.ImageView import androidx.appcompat.app.AppCompatActivity +import com.google.android.material.floatingactionbutton.FloatingActionButton import org.catrobat.paintroid.data.local.database.ProjectDatabase import org.catrobat.paintroid.data.local.database.ProjectDatabaseProvider class LandingPageActivity: AppCompatActivity() { companion object { lateinit var projectDB: ProjectDatabase + val FAB_ACTION = "" } override fun onCreate(savedInstanceState: Bundle?) { @@ -29,5 +31,19 @@ class LandingPageActivity: AppCompatActivity() { imagePreview.setOnClickListener(imagePreviewClickListener) editCircleIcon.setOnClickListener(imagePreviewClickListener) + + val mainActivityIntent = Intent(this, MainActivity::class.java) + + val newImage = findViewById(R.id.pocketpaint_fab_new_image) + newImage.setOnClickListener { + mainActivityIntent.putExtra(FAB_ACTION, "new_image") + startActivity(mainActivityIntent) + } + + val loadImage = findViewById(R.id.pocketpaint_fab_load_image) + loadImage.setOnClickListener { + mainActivityIntent.putExtra(FAB_ACTION, "load_image") + startActivity(mainActivityIntent) + } } } \ No newline at end of file diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt index 85c98b3093..43e5e95e2c 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt @@ -54,6 +54,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import org.catrobat.paintroid.LandingPageActivity.Companion.FAB_ACTION import org.catrobat.paintroid.colorpicker.ColorHistory import org.catrobat.paintroid.command.CommandFactory import org.catrobat.paintroid.command.CommandManager @@ -309,21 +310,26 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { presenterMain.initializeFromCleanState(null, null) } savedInstanceState == null -> { - val intent = intent - val picturePath = intent.getStringExtra(PAINTROID_PICTURE_PATH) - val pictureName = intent.getStringExtra(PAINTROID_PICTURE_NAME) - presenterMain.initializeFromCleanState(picturePath, pictureName) - - if (!model.isOpenedFromCatroid && presenterMain.checkForTemporaryFile() && (!isRunningEspressoTests || isTemporaryFileSavingTest)) { - val workspaceReturnValue = presenterMain.openTemporaryFile() - commandManager.loadCommandsCatrobatImage(workspaceReturnValue?.commandManagerModel) - model.colorHistory = workspaceReturnValue?.colorHistory ?: ColorHistory() - model.colorHistory.colors.lastOrNull()?.let { - toolReference.tool?.changePaintColor(it) - presenterMain.setBottomNavigationColor(it) + when (receivedIntent.getStringExtra(FAB_ACTION)) { + "new_image" -> presenterMain.onNewImage() + "load_image" -> presenterMain.replaceImageClicked() + else -> { + val intent = intent + val picturePath = intent.getStringExtra(PAINTROID_PICTURE_PATH) + val pictureName = intent.getStringExtra(PAINTROID_PICTURE_NAME) + presenterMain.initializeFromCleanState(picturePath, pictureName) + if (!model.isOpenedFromCatroid && presenterMain.checkForTemporaryFile() && (!isRunningEspressoTests || isTemporaryFileSavingTest)) { + val workspaceReturnValue = presenterMain.openTemporaryFile() + commandManager.loadCommandsCatrobatImage(workspaceReturnValue?.commandManagerModel) + model.colorHistory = workspaceReturnValue?.colorHistory ?: ColorHistory() + model.colorHistory.colors.lastOrNull()?.let { + toolReference.tool?.changePaintColor(it) + presenterMain.setBottomNavigationColor(it) + } + } + workspace.perspective.setBitmapDimensions(layerModel.width, layerModel.height) } } - workspace.perspective.setBitmapDimensions(layerModel.width, layerModel.height) } else -> { val isFullscreen = savedInstanceState.getBoolean(IS_FULLSCREEN_KEY, false) diff --git a/Paintroid/src/main/res/drawable/ic_pocketpaint_download.xml b/Paintroid/src/main/res/drawable/ic_pocketpaint_download.xml new file mode 100644 index 0000000000..dbc4c4ed5b --- /dev/null +++ b/Paintroid/src/main/res/drawable/ic_pocketpaint_download.xml @@ -0,0 +1,5 @@ + + + diff --git a/Paintroid/src/main/res/layout/activity_pocketpaint_landing_page.xml b/Paintroid/src/main/res/layout/activity_pocketpaint_landing_page.xml index 7543274caf..69973300ef 100644 --- a/Paintroid/src/main/res/layout/activity_pocketpaint_landing_page.xml +++ b/Paintroid/src/main/res/layout/activity_pocketpaint_landing_page.xml @@ -9,6 +9,7 @@ layout="@layout/pocketpaint_layout_landing_page_top_bar"/> + + + + + + + + \ No newline at end of file diff --git a/colorpicker/src/main/res/values/colors.xml b/colorpicker/src/main/res/values/colors.xml index 389a532506..eb5f3b2a00 100644 --- a/colorpicker/src/main/res/values/colors.xml +++ b/colorpicker/src/main/res/values/colors.xml @@ -37,6 +37,7 @@ #FFC5060E #FFEB4618 #FFF9921C + #FFAB08 #FFF3D605 #FF000000 #FFA3A3A3 From 03340255b4f06ff3c62be3603661a6957db706d3 Mon Sep 17 00:00:00 2001 From: Rd Date: Tue, 6 Jun 2023 14:04:29 +0530 Subject: [PATCH 05/52] PAINTROID-610 Show all projects of the user in the landing page --- .../catrobat/paintroid/LandingPageActivity.kt | 65 +++++++- .../org/catrobat/paintroid/MainActivity.kt | 29 +++- .../paintroid/adapter/ProjectAdapter.kt | 143 ++++++++++++++++++ .../catrobat/paintroid/common/Constants.kt | 2 + .../paintroid/data/local/dao/ProjectDao.kt | 2 +- .../paintroid/dialog/ProjectDeleteDialog.kt | 27 ++++ .../paintroid/dialog/ProjectDetailsDialog.kt | 34 +++++ .../catrobat/paintroid/iotasks/SaveImage.kt | 28 ++-- .../main/res/drawable/ic_pocketpaint_more.xml | 5 + .../res/layout/pocketpaint_item_project.xml | 76 ++++++++++ .../menu/menu_pocketpaint_project_details.xml | 9 ++ Paintroid/src/main/res/values/string.xml | 4 +- Paintroid/src/main/res/values/style.xml | 7 + 13 files changed, 409 insertions(+), 22 deletions(-) create mode 100644 Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt create mode 100644 Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDeleteDialog.kt create mode 100644 Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDetailsDialog.kt create mode 100644 Paintroid/src/main/res/drawable/ic_pocketpaint_more.xml create mode 100644 Paintroid/src/main/res/layout/pocketpaint_item_project.xml create mode 100644 Paintroid/src/main/res/menu/menu_pocketpaint_project_details.xml diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt b/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt index 3927c2fe2d..5ef93b2595 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt @@ -1,17 +1,27 @@ package org.catrobat.paintroid import android.content.Intent +import android.net.Uri import android.os.Bundle import android.view.View import android.widget.ImageView import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import com.google.android.material.floatingactionbutton.FloatingActionButton +import org.catrobat.paintroid.adapter.ProjectAdapter import org.catrobat.paintroid.data.local.database.ProjectDatabase import org.catrobat.paintroid.data.local.database.ProjectDatabaseProvider +import org.catrobat.paintroid.model.Project class LandingPageActivity: AppCompatActivity() { companion object { lateinit var projectDB: ProjectDatabase + private lateinit var projectsRecyclerView: RecyclerView + private lateinit var projectsList: ArrayList + lateinit var projectAdapter: ProjectAdapter + var latestProject: Project? = null + lateinit var imagePreview: ImageView val FAB_ACTION = "" } @@ -20,13 +30,32 @@ class LandingPageActivity: AppCompatActivity() { setContentView(R.layout.activity_pocketpaint_landing_page) projectDB = ProjectDatabaseProvider.getDatabase(applicationContext) + val allProjects = projectDB.dao.getProjects() + latestProject = allProjects.firstOrNull() - val imagePreview = findViewById(R.id.pocketpaint_image_preview) + init() + + imagePreview = findViewById(R.id.pocketpaint_image_preview) val editCircleIcon = findViewById(R.id.pocketpaint_image_edit_circle) + latestProject?.let { + imagePreview.setImageURI(Uri.parse(it.imagePreviewPath)) + imagePreview.scaleType = ImageView.ScaleType.CENTER + } + val imagePreviewClickListener = View.OnClickListener { - val intent = Intent(this, MainActivity::class.java) - startActivity(intent) + if (allProjects.isNotEmpty()){ + val loadProjectIntent = Intent(applicationContext, MainActivity::class.java).apply { + putExtra(FAB_ACTION, "load_project") + putExtra("PROJECT_URI", latestProject?.path) + putExtra("PROJECT_NAME", latestProject?.name) + putExtra("PROJECT_IMAGE_PREVIEW_URI", latestProject?.imagePreviewPath) + } + startActivity(loadProjectIntent) + }else { + val intent = Intent(this, MainActivity::class.java) + startActivity(intent) + } } imagePreview.setOnClickListener(imagePreviewClickListener) @@ -46,4 +75,34 @@ class LandingPageActivity: AppCompatActivity() { startActivity(mainActivityIntent) } } + + private fun init() { + projectsRecyclerView = findViewById(R.id.pocketpaint_projects_list) + projectsRecyclerView.layoutManager = LinearLayoutManager(this) + + projectsList = ArrayList() + projectDB.dao.getProjects().forEach { + projectsList.add(it) + } + + projectAdapter = ProjectAdapter(projectsList, supportFragmentManager) + projectsRecyclerView.adapter = projectAdapter + + projectAdapter.setOnItemClickListener(object: ProjectAdapter.OnItemClickListener{ + override fun onItemClick( + position: Int, + projectUri: String, + projectName: String, + projectImagePreviewUri: String + ) { + val loadProjectIntent = Intent(applicationContext, MainActivity::class.java).apply { + putExtra(FAB_ACTION, "load_project") + putExtra("PROJECT_URI", projectUri) + putExtra("PROJECT_NAME", projectName) + putExtra("PROJECT_IMAGE_PREVIEW_URI", projectImagePreviewUri) + } + startActivity(loadProjectIntent) + } + }) + } } \ No newline at end of file diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt index 43e5e95e2c..6353c83326 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt @@ -43,6 +43,7 @@ import androidx.annotation.VisibleForTesting import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.TooltipCompat +import androidx.core.net.toUri import androidx.core.widget.ContentLoadingProgressBar import androidx.drawerlayout.widget.DrawerLayout import androidx.recyclerview.widget.LinearLayoutManager @@ -64,9 +65,7 @@ import org.catrobat.paintroid.command.implementation.DefaultCommandFactory import org.catrobat.paintroid.command.implementation.DefaultCommandManager import org.catrobat.paintroid.command.implementation.LayerOpacityCommand import org.catrobat.paintroid.command.serialization.CommandSerializer -import org.catrobat.paintroid.common.CommonFactory -import org.catrobat.paintroid.common.PAINTROID_PICTURE_NAME -import org.catrobat.paintroid.common.PAINTROID_PICTURE_PATH +import org.catrobat.paintroid.common.* import org.catrobat.paintroid.contract.LayerContracts import org.catrobat.paintroid.contract.MainActivityContracts import org.catrobat.paintroid.contract.MainActivityContracts.MainView @@ -166,6 +165,10 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { private var userInteraction = false private var isTemporaryFileSavingTest = false + var projectName: String? = null + var projectUri: String? = null + var projectImagePreviewUri: String? = null + private val isRunningEspressoTests: Boolean by lazy { try { Class.forName("androidx.test.espresso.Espresso") @@ -313,6 +316,12 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { when (receivedIntent.getStringExtra(FAB_ACTION)) { "new_image" -> presenterMain.onNewImage() "load_image" -> presenterMain.replaceImageClicked() + "load_project" -> { + projectName = receivedIntent.getStringExtra("PROJECT_NAME") + projectUri = receivedIntent.getStringExtra("PROJECT_URI") + projectImagePreviewUri = receivedIntent.getStringExtra("PROJECT_IMAGE_PREVIEW_URI") + presenterMain.loadScaledImage(projectUri?.toUri(), REQUEST_CODE_LOAD_PICTURE) + } else -> { val intent = intent val picturePath = intent.getStringExtra(PAINTROID_PICTURE_PATH) @@ -406,7 +415,19 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { UserPreferences(getPreferences(MODE_PRIVATE)) ) R.id.pocketpaint_advanced_settings -> presenterMain.showAdvancedSettingsClicked() - android.R.id.home -> presenterMain.backToPocketCodeClicked() + android.R.id.home -> + if(projectName?.let { + FileIO.checkFileExists(FileIO.FileType.CATROBAT, + it, this.contentResolver) + } == true){ + FileIO.storeImageUri = Uri.parse(projectUri) + FileIO.storeImagePreviewUri = Uri.parse(projectImagePreviewUri) + presenterMain.switchBetweenVersions(PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT, false) + presenterMain.finishActivity() + } + else { + presenterMain.backToPocketCodeClicked() + } else -> return false } return true diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt b/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt new file mode 100644 index 0000000000..a98b00561b --- /dev/null +++ b/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt @@ -0,0 +1,143 @@ +package org.catrobat.paintroid.adapter + +import android.net.Uri +import android.os.Build +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.PopupMenu +import android.widget.TextView +import androidx.annotation.RequiresApi +import androidx.fragment.app.FragmentManager +import androidx.recyclerview.widget.RecyclerView +import org.catrobat.paintroid.LandingPageActivity.Companion.imagePreview +import org.catrobat.paintroid.LandingPageActivity.Companion.latestProject +import org.catrobat.paintroid.R +import org.catrobat.paintroid.common.PROJECT_DELETE_DIALOG_FRAGMENT_TAG +import org.catrobat.paintroid.common.PROJECT_DETAILS_DIALOG_FRAGMENT_TAG +import org.catrobat.paintroid.dialog.ProjectDeleteDialog +import org.catrobat.paintroid.dialog.ProjectDetailsDialog +import org.catrobat.paintroid.model.Project +import java.text.SimpleDateFormat +import java.util.* +import kotlin.collections.ArrayList + +class ProjectAdapter(var projectList: ArrayList, val supportFragmentManager: FragmentManager): RecyclerView.Adapter() { + private var itemClickListener: OnItemClickListener? = null + + class ItemViewHolder(itemView: View): RecyclerView.ViewHolder(itemView){ + val itemImageView: ImageView = itemView.findViewById(R.id.iv_pocket_paint_project_thumbnail_image) + val itemNameText: TextView = itemView.findViewById(R.id.tv_pocket_paint_project_name) + val itemLastModifiedText: TextView = itemView.findViewById(R.id.tv_pocket_paint_project_lastmodified) + val itemMoreOption : ImageView = itemView.findViewById(R.id.iv_pocket_paint_project_more) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder { + val view = LayoutInflater.from(parent.context).inflate(R.layout.pocketpaint_item_project, parent, false) + return ItemViewHolder(view) + } + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP_MR1) + override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { + val item = projectList[position] + val id = item.id + val name = item.name.substringBefore(".catrobat-image") + val resolution = item.resolution + val creationDate = item.creationDate + val lastModifiedDate = item.lastModified + val format = item.format + val size = item.size + + val dateTimeFormat = SimpleDateFormat("dd/MM/yyyy - HH:mm", Locale.getDefault()) + + val formattedLastModified = dateTimeFormat.format(Date(lastModifiedDate)) + val formattedCreationDate = dateTimeFormat.format(Date(creationDate)) + + val formattedSize = if (size >= 1.0) { + val formattedValue = String.format("%.2f", size) + "${formattedValue}MB" + } else { + val formattedValue = String.format("%.1f", size * 1024) + "${formattedValue}KB" + } + + holder.itemImageView.setImageURI(Uri.parse(item.imagePreviewPath)) + holder.itemNameText.text = name + holder.itemLastModifiedText.text = formattedLastModified + + val projectDetailsMenu = holder.itemMoreOption + projectDetailsMenu.setOnClickListener { view -> + val popupMenu = PopupMenu(view.context, view) + popupMenu.inflate(R.menu.menu_pocketpaint_project_details) + popupMenu.setOnMenuItemClickListener { menuItem -> + when(menuItem.itemId){ + R.id.project_details -> { + val projectDetails = ProjectDetailsDialog(name, resolution, formattedLastModified, formattedCreationDate, format, formattedSize) + projectDetails.show(supportFragmentManager, PROJECT_DETAILS_DIALOG_FRAGMENT_TAG) + true + } + R.id.project_delete -> { + val projectDelete = ProjectDeleteDialog(id, name) + projectDelete.show(supportFragmentManager, PROJECT_DELETE_DIALOG_FRAGMENT_TAG) + true + } + else -> false + } + } + popupMenu.show() + } + + holder.itemView.setOnClickListener { + val clickedItem = projectList[position] + val projectUri = clickedItem.path + val projectName = clickedItem.name + val projectImagePreviewUri = clickedItem.imagePreviewPath + itemClickListener?.onItemClick(position, projectUri, projectName, projectImagePreviewUri) + } + } + + fun insertProject(project: Project) { + projectList.add(0, project) + imagePreview.setImageURI(Uri.parse(project?.imagePreviewPath)) + notifyItemInserted(0) + } + + fun updateProject(filename: String, imagePreviewPath: String, projectUri: String, lastModified: Long) { + val index = projectList.indexOfFirst { it.name == filename } + if (index != -1) { + val updatedProject = projectList[index].copy( + path = projectUri, + imagePreviewPath = imagePreviewPath, + lastModified = lastModified, + ) + projectList[index] = updatedProject + notifyItemChanged(index) + } + } + + fun removeProject(projectId: Int) { + val iterator = projectList.iterator() + while (iterator.hasNext()) { + val project = iterator.next() + if (project.id == projectId) { + iterator.remove() + break + } + } + latestProject = projectList[0] + imagePreview.setImageURI(Uri.parse(latestProject?.imagePreviewPath)) + } + + override fun getItemCount(): Int { + return projectList.size + } + + interface OnItemClickListener{ + fun onItemClick(position: Int, projectUri: String, projectName: String, projectImagePreviewUri: String) + } + + fun setOnItemClickListener(listener: OnItemClickListener){ + itemClickListener = listener + } +} \ No newline at end of file diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/common/Constants.kt b/Paintroid/src/main/java/org/catrobat/paintroid/common/Constants.kt index 19b121b8b4..fc393db096 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/common/Constants.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/common/Constants.kt @@ -25,6 +25,8 @@ const val PAINTROID_PICTURE_PATH = "org.catrobat.extra.PAINTROID_PICTURE_PATH" const val PAINTROID_PICTURE_NAME = "org.catrobat.extra.PAINTROID_PICTURE_NAME" const val TEMP_PICTURE_NAME = "catroidTemp" const val MEDIA_GALLEY_URL = "https://share.catrob.at/pocketcode/media-library/looks" +const val PROJECT_DETAILS_DIALOG_FRAGMENT_TAG = "projectdetailsfragment" +const val PROJECT_DELETE_DIALOG_FRAGMENT_TAG = "projectdeletefragment" const val ABOUT_DIALOG_FRAGMENT_TAG = "aboutdialogfragment" const val LIKE_US_DIALOG_FRAGMENT_TAG = "likeusdialogfragment" const val RATE_US_DIALOG_FRAGMENT_TAG = "rateusdialogfragment" diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt b/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt index e773d8114a..3c8222668a 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt @@ -12,7 +12,7 @@ interface ProjectDao { fun insertProject(project: Project) @Query("UPDATE Project SET path= :projectUri, imagePreviewPath= :imagePreviewPath, lastModified= :lastModified WHERE name= :name") - fun updateProjectUri(name: String, imagePreviewPath: String, projectUri: String, lastModified: Long) + fun updateProject(name: String, imagePreviewPath: String, projectUri: String, lastModified: Long) @Query("SELECT * FROM Project ORDER BY lastModified DESC") fun getProjects(): List diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDeleteDialog.kt b/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDeleteDialog.kt new file mode 100644 index 0000000000..0c997cc512 --- /dev/null +++ b/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDeleteDialog.kt @@ -0,0 +1,27 @@ +package org.catrobat.paintroid.dialog + +import android.app.AlertDialog +import android.app.Dialog +import android.os.Bundle +import androidx.appcompat.app.AppCompatDialogFragment +import androidx.core.text.HtmlCompat +import org.catrobat.paintroid.LandingPageActivity.Companion.projectAdapter +import org.catrobat.paintroid.LandingPageActivity.Companion.projectDB +import org.catrobat.paintroid.R + +class ProjectDeleteDialog(private val projectId: Int, private val name: String) : AppCompatDialogFragment() { + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val message = getString(R.string.pocketpaint_project_delete_title_dialog, name) + + return AlertDialog.Builder(context, R.style.PocketPaintAlertDialog) + .setTitle(HtmlCompat.fromHtml(message, HtmlCompat.FROM_HTML_MODE_LEGACY)) + .setMessage(R.string.pocketpaint_project_delete_message_dialog) + .setPositiveButton(R.string.pocketpaint_ok) { _, _ -> + projectDB.dao.deleteProject(projectId) + projectAdapter.removeProject(projectId) + projectAdapter.notifyDataSetChanged() + } + .setNegativeButton(R.string.pocketpaint_cancel) { _, _ -> dismiss() } + .create() + } +} \ No newline at end of file diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDetailsDialog.kt b/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDetailsDialog.kt new file mode 100644 index 0000000000..c4012f8c74 --- /dev/null +++ b/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDetailsDialog.kt @@ -0,0 +1,34 @@ +package org.catrobat.paintroid.dialog + +import android.app.AlertDialog +import android.app.Dialog +import android.os.Bundle +import androidx.appcompat.app.AppCompatDialogFragment +import androidx.core.text.HtmlCompat +import org.catrobat.paintroid.R + +class ProjectDetailsDialog( + private val name: String, + private val resolution: String, + private val formattedLastModified: String, + private val formattedCreationDate: String, + private val format: String, + private val size: String +) : AppCompatDialogFragment() { + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val message = getString( + R.string.pocketpaint_project_details_dialog, + resolution, + formattedLastModified, + formattedCreationDate, + format, + size.toString() + ) + + return AlertDialog.Builder(context, R.style.PocketPaintAlertDialog) + .setTitle(name) + .setMessage(HtmlCompat.fromHtml(message, HtmlCompat.FROM_HTML_MODE_LEGACY)) + .setPositiveButton(R.string.pocketpaint_ok) { _, _ -> dismiss() } + .create() + } +} \ No newline at end of file diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/SaveImage.kt b/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/SaveImage.kt index cd63be8750..cbb9ea6306 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/SaveImage.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/SaveImage.kt @@ -30,6 +30,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.catrobat.paintroid.FileIO +import org.catrobat.paintroid.LandingPageActivity.Companion.projectAdapter import org.catrobat.paintroid.LandingPageActivity.Companion.projectDB import org.catrobat.paintroid.command.serialization.CommandSerializer import org.catrobat.paintroid.contract.LayerContracts @@ -80,7 +81,7 @@ class SaveImage( val imageUri = FileIO.saveBitmapToFile(imagePreviewFilename, bitmap, callback.contentResolver, context) imageUri } else { - uri?.let { FileIO.saveBitmapToUri(it, bitmap, context) } + imagePreviewUri?.let { FileIO.saveBitmapToUri(it, bitmap, context) } } } @@ -149,23 +150,24 @@ class SaveImage( val date = Calendar.getInstance().timeInMillis if(uri != null){ uri?.let { - projectDB.dao.updateProjectUri(filename, imagePreviewPath.toString(), currentUri.toString(), date) + projectDB.dao.updateProject(filename, imagePreviewPath.toString(), currentUri.toString(), date) + projectAdapter.updateProject(filename, imagePreviewPath.toString(), currentUri.toString(), date) } } else { val dimensions = getImageDimensions(imagePreviewPath) val size = getImageSize(imagePreviewPath) - projectDB.dao.insertProject( - Project( - filename, - currentUri.toString(), - date, - date, - "${dimensions?.first} x ${dimensions?.second}", - FileIO.fileType.toString(), - size, - imagePreviewPath.toString() - ) + val project = Project( + filename, + currentUri.toString(), + date, + date, + "${dimensions?.first} x ${dimensions?.second}", + FileIO.fileType.toString(), + size, + imagePreviewPath.toString() ) + projectDB.dao.insertProject(project) + projectAdapter.insertProject(project) } } else { currentUri = if (FileIO.fileType == FileIO.FileType.ORA) { diff --git a/Paintroid/src/main/res/drawable/ic_pocketpaint_more.xml b/Paintroid/src/main/res/drawable/ic_pocketpaint_more.xml new file mode 100644 index 0000000000..073ebd8795 --- /dev/null +++ b/Paintroid/src/main/res/drawable/ic_pocketpaint_more.xml @@ -0,0 +1,5 @@ + + + diff --git a/Paintroid/src/main/res/layout/pocketpaint_item_project.xml b/Paintroid/src/main/res/layout/pocketpaint_item_project.xml new file mode 100644 index 0000000000..aa8f35f4cd --- /dev/null +++ b/Paintroid/src/main/res/layout/pocketpaint_item_project.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Paintroid/src/main/res/menu/menu_pocketpaint_project_details.xml b/Paintroid/src/main/res/menu/menu_pocketpaint_project_details.xml new file mode 100644 index 0000000000..f648a2dc7d --- /dev/null +++ b/Paintroid/src/main/res/menu/menu_pocketpaint_project_details.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/Paintroid/src/main/res/values/string.xml b/Paintroid/src/main/res/values/string.xml index 9d02e75721..afed22db41 100644 --- a/Paintroid/src/main/res/values/string.xml +++ b/Paintroid/src/main/res/values/string.xml @@ -185,7 +185,9 @@ This format remembers layers. It can be opened by apps that support the Openraster format. catrobat-image Pocket Paint\'s native image format. This format remembers commands and layers. - + Resolution: %s<br />Last modified: %s<br />Creation date: %s<br />Format: %s<br />Size: %s + Delete %s + Do you really want to delete your Project? This app needs the requested permission to function properly. In order to save images to the local memory, the app needs read and write access to it. This app needs the requested permission to function properly. In order to save images to the local memory, the app needs read and write access to it. diff --git a/Paintroid/src/main/res/values/style.xml b/Paintroid/src/main/res/values/style.xml index 84a7bdaf90..404941502b 100644 --- a/Paintroid/src/main/res/values/style.xml +++ b/Paintroid/src/main/res/values/style.xml @@ -50,6 +50,8 @@ @android:transition/move @android:transition/move @style/AppBottomNavigation + @style/CustomPopupMenuStyle + @style/CustomPopupMenuStyle @style/appBarLayout @android:color/black @android:color/tab_indicator_text @@ -133,6 +135,11 @@ @android:color/white + + - + + From d43065f17b882977579003fe2321a8632180c568 Mon Sep 17 00:00:00 2001 From: Rd Date: Sat, 10 Jun 2023 22:20:16 +0530 Subject: [PATCH 07/52] Fixed Static Analysis Warnings --- .../paintroid/test/database/ProjectDaoTest.kt | 10 +- .../LandingPageActivityIntegrationTest.kt | 170 ++++++++++-------- .../catrobat/paintroid/LandingPageActivity.kt | 10 +- .../org/catrobat/paintroid/MainActivity.kt | 39 ++-- .../paintroid/adapter/ProjectAdapter.kt | 45 ++--- .../paintroid/data/local/dao/ProjectDao.kt | 2 +- .../data/local/database/ProjectDatabase.kt | 4 +- .../local/database/ProjectDatabaseProvider.kt | 8 +- .../paintroid/dialog/ProjectDeleteDialog.kt | 6 +- .../paintroid/dialog/ProjectDetailsDialog.kt | 2 +- .../paintroid/dialog/SaveInformationDialog.kt | 10 +- .../catrobat/paintroid/iotasks/SaveImage.kt | 99 +++++----- .../org/catrobat/paintroid/model/Project.kt | 2 +- .../paintroid/model/SaveImageOptions.kt | 5 + .../presenter/MainActivityPresenter.kt | 26 ++- .../paintroid/ui/MainActivityInteractor.kt | 47 +++-- 16 files changed, 278 insertions(+), 207 deletions(-) create mode 100644 Paintroid/src/main/java/org/catrobat/paintroid/model/SaveImageOptions.kt diff --git a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/database/ProjectDaoTest.kt b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/database/ProjectDaoTest.kt index 15ca0966e2..3d6b766c53 100644 --- a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/database/ProjectDaoTest.kt +++ b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/database/ProjectDaoTest.kt @@ -3,7 +3,9 @@ package org.catrobat.paintroid.test.database import androidx.room.Room import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 -import junit.framework.Assert.* +import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertTrue +import junit.framework.Assert.assertEquals import org.catrobat.paintroid.data.local.dao.ProjectDao import org.catrobat.paintroid.data.local.database.ProjectDatabase import org.catrobat.paintroid.model.Project @@ -27,7 +29,7 @@ class ProjectDaoTest { } @After - fun teardown(){ + fun teardown() { database.close() } @@ -114,9 +116,9 @@ class ProjectDaoTest { "catrobat/upath", 1) val allProjects = dao.getProjects() - val updatedProject = allProjects.find { it.name == "name"} + val updatedProject = allProjects.find { it.name == "name" } assertEquals("paintroid/upath", updatedProject?.imagePreviewPath) assertEquals("catrobat/upath", updatedProject?.path) assertEquals(1L, updatedProject?.lastModified) } -} \ No newline at end of file +} diff --git a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt index c7c54e3b06..2b97439ab9 100644 --- a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt +++ b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt @@ -19,17 +19,26 @@ import android.widget.TextView import androidx.appcompat.widget.Toolbar import androidx.core.graphics.drawable.toBitmap import androidx.recyclerview.widget.RecyclerView +import androidx.room.Room import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.pressBack import androidx.test.espresso.UiController import androidx.test.espresso.ViewAction -import androidx.test.espresso.action.ViewActions.* +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.action.ViewActions.replaceText +import androidx.test.espresso.action.ViewActions.closeSoftKeyboard +import androidx.test.espresso.action.ViewActions.scrollTo import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.contrib.RecyclerViewActions import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition import androidx.test.espresso.intent.Intents import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction -import androidx.test.espresso.matcher.ViewMatchers.* +import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.isRoot +import androidx.test.espresso.matcher.ViewMatchers.hasDescendant import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import androidx.test.rule.ActivityTestRule @@ -38,10 +47,12 @@ import org.catrobat.paintroid.R import org.catrobat.paintroid.adapter.ProjectAdapter import org.catrobat.paintroid.common.CATROBAT_IMAGE_ENDING import org.catrobat.paintroid.common.PNG_IMAGE_ENDING +import org.catrobat.paintroid.data.local.dao.ProjectDao +import org.catrobat.paintroid.data.local.database.ProjectDatabase import org.catrobat.paintroid.test.espresso.util.BitmapLocationProvider import org.catrobat.paintroid.test.espresso.util.DrawingSurfaceLocationProvider -import org.catrobat.paintroid.test.espresso.util.UiInteractions import org.catrobat.paintroid.test.espresso.util.UiInteractions.touchAt +import org.catrobat.paintroid.test.espresso.util.UiInteractions.waitFor import org.catrobat.paintroid.test.espresso.util.UiMatcher.atPosition import org.catrobat.paintroid.test.espresso.util.wrappers.DrawingSurfaceInteraction.onDrawingSurfaceView import org.catrobat.paintroid.test.espresso.util.wrappers.ToolBarViewInteraction @@ -53,7 +64,11 @@ import org.hamcrest.CoreMatchers.not import org.hamcrest.Description import org.hamcrest.Matcher import org.hamcrest.TypeSafeMatcher -import org.junit.* +import org.junit.Test +import org.junit.Rule +import org.junit.After +import org.junit.Before +import org.junit.Assert import org.junit.runner.RunWith import java.io.File import java.io.IOException @@ -62,6 +77,9 @@ import kotlin.collections.ArrayList @RunWith(AndroidJUnit4::class) class LandingPageActivityIntegrationTest { + private lateinit var database: ProjectDatabase + private lateinit var dao: ProjectDao + @get:Rule var launchActivityRule = ActivityTestRule(LandingPageActivity::class.java) @@ -78,12 +96,20 @@ class LandingPageActivityIntegrationTest { @Before fun setUp() { + database = Room.databaseBuilder(InstrumentationRegistry.getInstrumentation().targetContext, ProjectDatabase::class.java, "projects.db") + .allowMainThreadQueries() + .build() + dao = database.dao deletionFileList = ArrayList() activity = launchActivityRule.activity } @After fun tearDown() { + database.dao.deleteAllProjects() + database.clearAllTables() + database.close() + for (file in deletionFileList) { if (file != null && file.exists()) { Assert.assertTrue(file.delete()) @@ -108,7 +134,7 @@ class LandingPageActivityIntegrationTest { } @Test - fun testTopAppBarDisplayed(){ + fun testTopAppBarDisplayed() { onView(isAssignableFrom(Toolbar::class.java)) .check(matches(isDisplayed())) } @@ -120,7 +146,7 @@ class LandingPageActivityIntegrationTest { } @Test - fun testTwoFABDisplayed(){ + fun testTwoFABDisplayed() { onView(withId(R.id.pocketpaint_fab_load_image)) .check(matches(isDisplayed())) onView(withId(R.id.pocketpaint_fab_new_image)) @@ -128,7 +154,7 @@ class LandingPageActivityIntegrationTest { } @Test - fun testMyProjectsTextDisplayed(){ + fun testMyProjectsTextDisplayed() { onView(withText("My Projects")) .check(matches(isDisplayed())) } @@ -183,9 +209,8 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(UiInteractions.waitFor(300)) - onView(withContentDescription(R.string.abc_action_bar_up_description)) - .perform(click()); + onView(isRoot()).perform(waitFor(300)) + pressBack() onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) onView(withId(R.id.pocketpaint_projects_list)).check( @@ -207,9 +232,8 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(UiInteractions.waitFor(300)) - onView(withContentDescription(R.string.abc_action_bar_up_description)) - .perform(click()); + onView(isRoot()).perform(waitFor(300)) + pressBack() onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) onView(withId(R.id.pocketpaint_projects_list)) @@ -233,9 +257,8 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(UiInteractions.waitFor(300)) - onView(withContentDescription(R.string.abc_action_bar_up_description)) - .perform(click()); + onView(isRoot()).perform(waitFor(300)) + pressBack() onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) val imagesDirectory = @@ -265,42 +288,6 @@ class LandingPageActivityIntegrationTest { ) } - @Test - fun testSavedProjectOpen() { - onView(withId(R.id.pocketpaint_fab_new_image)) - .perform(click()) - onDrawingSurfaceView() - .perform(touchAt(DrawingSurfaceLocationProvider.MIDDLE)) - TopBarViewInteraction.onTopBarView() - .performOpenMoreOptions() - onView(withText(R.string.menu_save_project)) - .perform(click()) - onView(withId(R.id.pocketpaint_image_name_save_text)) - .perform(replaceText(PROJECT_NAME)) - onView(withText(R.string.save_button_text)) - .perform(click()) - onView(isRoot()).perform(UiInteractions.waitFor(300)) - onView(withContentDescription(R.string.abc_action_bar_up_description)) - .perform(click()); - onView(withId(R.id.pocketpaint_projects_list)) - .perform(RecyclerViewActions.scrollToPosition(position)) - onView(withId(R.id.pocketpaint_projects_list)) - .perform(actionOnItemAtPosition(position, click())) - onView( - allOf( - withId(R.id.pocketpaint_toolbar), - hasDescendant( - allOf( - isAssignableFrom(TextView::class.java), - withText(PROJECT_NAME) - ) - ) - ) - ).check( - matches(isDisplayed()) - ) - } - @Test fun testProjectOverFlowMenuDetailsDisplayed() { onView(withId(R.id.pocketpaint_fab_new_image)) @@ -315,9 +302,8 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(UiInteractions.waitFor(300)) - onView(withContentDescription(R.string.abc_action_bar_up_description)) - .perform(click()); + onView(isRoot()).perform(waitFor(300)) + pressBack() onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) onView(withId(R.id.pocketpaint_projects_list)).perform( @@ -358,9 +344,8 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(UiInteractions.waitFor(300)) - onView(withContentDescription(R.string.abc_action_bar_up_description)) - .perform(click()); + onView(isRoot()).perform(waitFor(300)) + pressBack() onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) onView(withId(R.id.pocketpaint_projects_list)).perform( @@ -385,7 +370,7 @@ class LandingPageActivityIntegrationTest { ) onView(withText(R.string.menu_project_detail_title)) .perform(click()) - onView(isRoot()).perform(UiInteractions.waitFor(300)) + onView(isRoot()).perform(waitFor(300)) onView(withText(android.R.string.ok)) .perform(click()) } @@ -404,9 +389,8 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(UiInteractions.waitFor(300)) - onView(withContentDescription(R.string.abc_action_bar_up_description)) - .perform(click()); + onView(isRoot()).perform(waitFor(300)) + pressBack() onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) onView(withId(R.id.pocketpaint_projects_list)).perform( @@ -447,9 +431,8 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(UiInteractions.waitFor(300)) - onView(withContentDescription(R.string.abc_action_bar_up_description)) - .perform(click()); + onView(isRoot()).perform(waitFor(300)) + pressBack() onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) onView(withId(R.id.pocketpaint_projects_list)).perform( @@ -500,13 +483,10 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(UiInteractions.waitFor(300)) - onView(withContentDescription(R.string.abc_action_bar_up_description)) - .perform(click()); - onView(withId(R.id.pocketpaint_projects_list)) - .perform(RecyclerViewActions.scrollToPosition(position)) + onView(isRoot()).perform(waitFor(300)) + pressBack() onView(withId(R.id.pocketpaint_projects_list)).perform( - actionOnItemAtPosition( + actionOnItemAtPosition( position, object : ViewAction { override fun getConstraints(): Matcher { @@ -527,17 +507,19 @@ class LandingPageActivityIntegrationTest { ) onView(withText(R.string.menu_project_delete_title)) .perform(click()) - onView(isRoot()).perform(UiInteractions.waitFor(100)) + onView(isRoot()).perform(waitFor(100)) onView(withId(android.R.id.button1)) .perform(closeSoftKeyboard()) .perform(scrollTo()) .perform(click()) + onView(isRoot()).perform(waitFor(300)) onView(withId(R.id.pocketpaint_projects_list)) - .perform(RecyclerViewActions.scrollToPosition(position)) + .perform(RecyclerViewActions.scrollToPosition(position)) + onView(isRoot()).perform(waitFor(300)) if (isRecyclerViewEmpty(recyclerViewMatcher)) { onView(withId(R.id.pocketpaint_projects_list)) .check(matches(not(hasDescendant(withId(R.id.iv_pocket_paint_project_thumbnail_image))))) - }else { + } else { onView(withId(R.id.pocketpaint_projects_list)) .check( matches( @@ -554,6 +536,38 @@ class LandingPageActivityIntegrationTest { } } + @Test + fun testSavedProjectOpen() { + onView(withId(R.id.pocketpaint_fab_new_image)) + .perform(click()) + onDrawingSurfaceView() + .perform(touchAt(DrawingSurfaceLocationProvider.MIDDLE)) + TopBarViewInteraction.onTopBarView() + .performOpenMoreOptions() + onView(withText(R.string.menu_save_project)) + .perform(click()) + onView(withId(R.id.pocketpaint_image_name_save_text)) + .perform(replaceText(PROJECT_NAME)) + onView(withText(R.string.save_button_text)) + .perform(click()) + onView(isRoot()).perform(waitFor(300)) + pressBack() + onView(withId(R.id.pocketpaint_projects_list)) + .perform(actionOnItemAtPosition(position, click())) + onView( + allOf( + withId(R.id.pocketpaint_toolbar), + hasDescendant( + allOf( + isAssignableFrom(TextView::class.java), + withText(PROJECT_NAME) + ) + ) + ) + ).check( + matches(isDisplayed()) + ) + } @Test fun testImagePreviewAfterProjectInsert() { onView(withId(R.id.pocketpaint_fab_new_image)) @@ -570,9 +584,8 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(UiInteractions.waitFor(300)) - onView(withContentDescription(R.string.abc_action_bar_up_description)) - .perform(click()); + onView(isRoot()).perform(waitFor(300)) + pressBack() onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) val imagesDirectory = @@ -634,5 +647,4 @@ class LandingPageActivityIntegrationTest { } return itemCount } - -} \ No newline at end of file +} diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt b/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt index 204553357b..18ad58fa99 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt @@ -14,7 +14,7 @@ import org.catrobat.paintroid.data.local.database.ProjectDatabase import org.catrobat.paintroid.data.local.database.ProjectDatabaseProvider import org.catrobat.paintroid.model.Project -class LandingPageActivity: AppCompatActivity() { +class LandingPageActivity : AppCompatActivity() { companion object { lateinit var projectDB: ProjectDatabase private lateinit var projectsRecyclerView: RecyclerView @@ -46,7 +46,7 @@ class LandingPageActivity: AppCompatActivity() { val imagePreviewClickListener = View.OnClickListener { allProjects = projectDB.dao.getProjects() latestProject = allProjects.firstOrNull() - if (allProjects.isNotEmpty()){ + if (allProjects.isNotEmpty()) { val loadProjectIntent = Intent(applicationContext, MainActivity::class.java).apply { putExtra(PROJECT_ACTION, "load_project") putExtra("PROJECT_URI", latestProject?.path) @@ -54,7 +54,7 @@ class LandingPageActivity: AppCompatActivity() { putExtra("PROJECT_IMAGE_PREVIEW_URI", latestProject?.imagePreviewPath) } startActivity(loadProjectIntent) - }else { + } else { val newProjectIntent = Intent(this, MainActivity::class.java).apply { putExtra(PROJECT_ACTION, "new_project") } @@ -92,7 +92,7 @@ class LandingPageActivity: AppCompatActivity() { projectAdapter = ProjectAdapter(this, projectsList, supportFragmentManager) projectsRecyclerView.adapter = projectAdapter - projectAdapter.setOnItemClickListener(object: ProjectAdapter.OnItemClickListener{ + projectAdapter.setOnItemClickListener(object : ProjectAdapter.OnItemClickListener { override fun onItemClick( position: Int, projectUri: String, @@ -109,4 +109,4 @@ class LandingPageActivity: AppCompatActivity() { } }) } -} \ No newline at end of file +} diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt index dc77a89a0c..40eb6d4169 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt @@ -65,7 +65,11 @@ import org.catrobat.paintroid.command.implementation.DefaultCommandFactory import org.catrobat.paintroid.command.implementation.DefaultCommandManager import org.catrobat.paintroid.command.implementation.LayerOpacityCommand import org.catrobat.paintroid.command.serialization.CommandSerializer -import org.catrobat.paintroid.common.* +import org.catrobat.paintroid.common.PAINTROID_PICTURE_NAME +import org.catrobat.paintroid.common.PAINTROID_PICTURE_PATH +import org.catrobat.paintroid.common.REQUEST_CODE_LOAD_PICTURE +import org.catrobat.paintroid.common.PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT +import org.catrobat.paintroid.common.CommonFactory import org.catrobat.paintroid.contract.LayerContracts import org.catrobat.paintroid.contract.MainActivityContracts import org.catrobat.paintroid.contract.MainActivityContracts.MainView @@ -187,7 +191,6 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { private const val APP_FRAGMENT_KEY = "customActivityState" private const val SHARED_PREFS_NAME = "preferences" private const val FIRST_LAUNCH_AFTER_INSTALL = "firstLaunchAfterInstall" - private var toolbar: Toolbar? = null var projectName: String? = null var projectUri: String? = null var projectImagePreviewUri: String? = null @@ -312,19 +315,12 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { workspace.resetPerspective() presenterMain.initializeFromCleanState(null, null) } - savedInstanceState == null -> { + savedInstanceState == null -> when (receivedIntent.getStringExtra(PROJECT_ACTION)) { "new_project" -> presenterMain.onNewImage() "new_image" -> presenterMain.onNewImage() "load_image" -> presenterMain.replaceImageClicked() - "load_project" -> { - projectName = receivedIntent.getStringExtra("PROJECT_NAME") - projectUri = receivedIntent.getStringExtra("PROJECT_URI") - projectImagePreviewUri = receivedIntent.getStringExtra("PROJECT_IMAGE_PREVIEW_URI") - val projectNameText = findViewById(R.id.pocketpaint_toolbar) - projectNameText.subtitle = projectName?.substringBefore(".catrobat-image") - presenterMain.loadScaledImage(projectUri?.toUri(), REQUEST_CODE_LOAD_PICTURE) - } + "load_project" -> loadProject(receivedIntent) else -> { val intent = intent val picturePath = intent.getStringExtra(PAINTROID_PICTURE_PATH) @@ -342,7 +338,6 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { workspace.perspective.setBitmapDimensions(layerModel.width, layerModel.height) } } - } else -> { val isFullscreen = savedInstanceState.getBoolean(IS_FULLSCREEN_KEY, false) val isSaved = savedInstanceState.getBoolean(IS_SAVED_KEY, false) @@ -376,6 +371,15 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { } } + private fun loadProject(receivedIntent: Intent) { + projectName = receivedIntent.getStringExtra("PROJECT_NAME") + projectUri = receivedIntent.getStringExtra("PROJECT_URI") + projectImagePreviewUri = receivedIntent.getStringExtra("PROJECT_IMAGE_PREVIEW_URI") + val projectNameText = findViewById(R.id.pocketpaint_toolbar) + projectNameText.subtitle = projectName?.substringBefore(".catrobat-image") + presenterMain.loadScaledImage(projectUri?.toUri(), REQUEST_CODE_LOAD_PICTURE) + } + override fun onWindowFocusChanged(hasFocus: Boolean) { super.onWindowFocusChanged(hasFocus) if (!hasFocus) { @@ -419,16 +423,15 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { ) R.id.pocketpaint_advanced_settings -> presenterMain.showAdvancedSettingsClicked() android.R.id.home -> - if(projectName?.let { + if (projectName?.let { FileIO.checkFileExists(FileIO.FileType.CATROBAT, it, this.contentResolver) - } == true){ + } == true) { FileIO.storeImageUri = Uri.parse(projectUri) FileIO.storeImagePreviewUri = Uri.parse(projectImagePreviewUri) presenterMain.switchBetweenVersions(PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT, false) presenterMain.finishActivity() - } - else { + } else { presenterMain.backToPocketCodeClicked() } else -> return false @@ -714,10 +717,10 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { override fun onBackPressed() { if (supportFragmentManager.isStateSaved) { super.onBackPressed() - }else if(projectName?.let { + } else if (projectName?.let { FileIO.checkFileExists(FileIO.FileType.CATROBAT, it, this.contentResolver) - } == true){ + } == true) { FileIO.storeImageUri = Uri.parse(projectUri) FileIO.storeImagePreviewUri = Uri.parse(projectImagePreviewUri) presenterMain.switchBetweenVersions(PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT, false) diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt b/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt index c42b806614..80c25676b8 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt @@ -3,7 +3,6 @@ package org.catrobat.paintroid.adapter import android.content.Context import android.net.Uri import android.os.Build -import android.os.Environment import android.provider.MediaStore import android.util.Log import android.view.LayoutInflater @@ -18,26 +17,31 @@ import androidx.recyclerview.widget.RecyclerView import org.catrobat.paintroid.LandingPageActivity.Companion.imagePreview import org.catrobat.paintroid.LandingPageActivity.Companion.latestProject import org.catrobat.paintroid.R -import org.catrobat.paintroid.common.CATROBAT_IMAGE_ENDING -import org.catrobat.paintroid.common.PNG_IMAGE_ENDING import org.catrobat.paintroid.common.PROJECT_DELETE_DIALOG_FRAGMENT_TAG import org.catrobat.paintroid.common.PROJECT_DETAILS_DIALOG_FRAGMENT_TAG import org.catrobat.paintroid.dialog.ProjectDeleteDialog import org.catrobat.paintroid.dialog.ProjectDetailsDialog import org.catrobat.paintroid.model.Project import java.io.File +import java.io.IOException import java.text.SimpleDateFormat import java.util.* import kotlin.collections.ArrayList -class ProjectAdapter(val context: Context, var projectList: ArrayList, val supportFragmentManager: FragmentManager): RecyclerView.Adapter() { +class ProjectAdapter(val context: Context, var projectList: ArrayList, private val supportFragmentManager: FragmentManager) : RecyclerView.Adapter() { private var itemClickListener: OnItemClickListener? = null - class ItemViewHolder(itemView: View): RecyclerView.ViewHolder(itemView){ + companion object { + private val TAG = ProjectAdapter::class.java.simpleName + private const val MEGABYTE = 1.0 + private const val KILOBYTE = 1024 + } + + class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val itemImageView: ImageView = itemView.findViewById(R.id.iv_pocket_paint_project_thumbnail_image) val itemNameText: TextView = itemView.findViewById(R.id.tv_pocket_paint_project_name) val itemLastModifiedText: TextView = itemView.findViewById(R.id.tv_pocket_paint_project_lastmodified) - val itemMoreOption : ImageView = itemView.findViewById(R.id.iv_pocket_paint_project_more) + val itemMoreOption: ImageView = itemView.findViewById(R.id.iv_pocket_paint_project_more) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder { @@ -61,22 +65,22 @@ class ProjectAdapter(val context: Context, var projectList: ArrayList, val formattedLastModified = dateTimeFormat.format(Date(lastModifiedDate)) val formattedCreationDate = dateTimeFormat.format(Date(creationDate)) - val formattedSize = if (size >= 1.0) { + val formattedSize = if (size >= MEGABYTE) { val formattedValue = String.format("%.2f", size) "${formattedValue}MB" } else { - val formattedValue = String.format("%.1f", size * 1024) + val formattedValue = String.format("%.1f", size * KILOBYTE) "${formattedValue}KB" } val imageFile = getFileFromUri(Uri.parse(item.imagePreviewPath), context) - val imageUri: Uri? = if(imageFile?.exists() == true) { + val imageUri: Uri? = if (imageFile?.exists() == true) { Uri.fromFile(imageFile) } else { null } - if(imageUri != null) { + if (imageUri != null) { holder.itemImageView.setImageURI(Uri.parse(item.imagePreviewPath)) } else { holder.itemImageView.setImageResource(R.drawable.pocketpaint_logo_small) @@ -89,14 +93,14 @@ class ProjectAdapter(val context: Context, var projectList: ArrayList, val popupMenu = PopupMenu(view.context, view) popupMenu.inflate(R.menu.menu_pocketpaint_project_details) popupMenu.setOnMenuItemClickListener { menuItem -> - when(menuItem.itemId){ + when (menuItem.itemId) { R.id.project_details -> { val projectDetails = ProjectDetailsDialog(name, resolution, formattedLastModified, formattedCreationDate, format, formattedSize) projectDetails.show(supportFragmentManager, PROJECT_DETAILS_DIALOG_FRAGMENT_TAG) true } R.id.project_delete -> { - val projectDelete = ProjectDeleteDialog(id, name) + val projectDelete = ProjectDeleteDialog(id, name, position) projectDelete.show(supportFragmentManager, PROJECT_DELETE_DIALOG_FRAGMENT_TAG) true } @@ -133,8 +137,8 @@ class ProjectAdapter(val context: Context, var projectList: ArrayList, try { file = File(uri.path) return file - } catch (e: Exception) { - e.printStackTrace() + } catch (e: IOException) { + Log.e(TAG, "Error occurred while accessing the file: ${e.message}") } return null } @@ -158,7 +162,8 @@ class ProjectAdapter(val context: Context, var projectList: ArrayList, } } - fun removeProject(projectId: Int) { + fun removeProject(projectId: Int, position: Int) { + projectList.removeAt(position) val iterator = projectList.iterator() while (iterator.hasNext()) { val project = iterator.next() @@ -175,15 +180,13 @@ class ProjectAdapter(val context: Context, var projectList: ArrayList, } } - override fun getItemCount(): Int { - return projectList.size - } + override fun getItemCount(): Int = projectList.size - interface OnItemClickListener{ + interface OnItemClickListener { fun onItemClick(position: Int, projectUri: String, projectName: String, projectImagePreviewUri: String) } - fun setOnItemClickListener(listener: OnItemClickListener){ + fun setOnItemClickListener(listener: OnItemClickListener) { itemClickListener = listener } -} \ No newline at end of file +} diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt b/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt index 3c8222668a..daca90a853 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt @@ -22,4 +22,4 @@ interface ProjectDao { @Query("DELETE FROM Project") fun deleteAllProjects() -} \ No newline at end of file +} diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/data/local/database/ProjectDatabase.kt b/Paintroid/src/main/java/org/catrobat/paintroid/data/local/database/ProjectDatabase.kt index 929fa5d019..c7a03158be 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/data/local/database/ProjectDatabase.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/data/local/database/ProjectDatabase.kt @@ -9,7 +9,7 @@ import org.catrobat.paintroid.model.Project entities = [Project::class], version = 1 ) -abstract class ProjectDatabase: RoomDatabase() { +abstract class ProjectDatabase : RoomDatabase() { abstract val dao: ProjectDao -} \ No newline at end of file +} diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/data/local/database/ProjectDatabaseProvider.kt b/Paintroid/src/main/java/org/catrobat/paintroid/data/local/database/ProjectDatabaseProvider.kt index 8698cb60ac..2a7b031076 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/data/local/database/ProjectDatabaseProvider.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/data/local/database/ProjectDatabaseProvider.kt @@ -6,15 +6,15 @@ import androidx.room.Room object ProjectDatabaseProvider { var projectDatabase: ProjectDatabase? = null - fun getDatabase(context: Context): ProjectDatabase{ - return projectDatabase?: synchronized(this){ + fun getDatabase(context: Context): ProjectDatabase { + return projectDatabase ?: synchronized(this) { projectDatabase ?: buildDatabase(context).also { projectDatabase = it } } } - fun buildDatabase(context: Context): ProjectDatabase{ + fun buildDatabase(context: Context): ProjectDatabase { return Room.databaseBuilder(context, ProjectDatabase::class.java, "projects.db") .allowMainThreadQueries() .build() } -} \ No newline at end of file +} diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDeleteDialog.kt b/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDeleteDialog.kt index 0c997cc512..3773854668 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDeleteDialog.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDeleteDialog.kt @@ -9,7 +9,7 @@ import org.catrobat.paintroid.LandingPageActivity.Companion.projectAdapter import org.catrobat.paintroid.LandingPageActivity.Companion.projectDB import org.catrobat.paintroid.R -class ProjectDeleteDialog(private val projectId: Int, private val name: String) : AppCompatDialogFragment() { +class ProjectDeleteDialog(private val projectId: Int, private val name: String, private val position: Int) : AppCompatDialogFragment() { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val message = getString(R.string.pocketpaint_project_delete_title_dialog, name) @@ -18,10 +18,10 @@ class ProjectDeleteDialog(private val projectId: Int, private val name: String) .setMessage(R.string.pocketpaint_project_delete_message_dialog) .setPositiveButton(R.string.pocketpaint_ok) { _, _ -> projectDB.dao.deleteProject(projectId) - projectAdapter.removeProject(projectId) + projectAdapter.removeProject(projectId, position) projectAdapter.notifyDataSetChanged() } .setNegativeButton(R.string.pocketpaint_cancel) { _, _ -> dismiss() } .create() } -} \ No newline at end of file +} diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDetailsDialog.kt b/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDetailsDialog.kt index c4012f8c74..7dce39c095 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDetailsDialog.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDetailsDialog.kt @@ -31,4 +31,4 @@ class ProjectDetailsDialog( .setPositiveButton(R.string.pocketpaint_ok) { _, _ -> dismiss() } .create() } -} \ No newline at end of file +} diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/dialog/SaveInformationDialog.kt b/Paintroid/src/main/java/org/catrobat/paintroid/dialog/SaveInformationDialog.kt index 80af3580c3..bb6a54ac4d 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/dialog/SaveInformationDialog.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/dialog/SaveInformationDialog.kt @@ -25,9 +25,14 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.* +import android.widget.ArrayAdapter +import android.widget.AdapterView import android.widget.AdapterView.OnItemSelectedListener +import android.widget.ImageButton +import android.widget.SeekBar import android.widget.SeekBar.OnSeekBarChangeListener +import android.widget.Spinner +import android.widget.TextView import androidx.appcompat.app.AlertDialog import androidx.appcompat.widget.AppCompatEditText import androidx.appcompat.widget.AppCompatImageButton @@ -131,12 +136,11 @@ class SaveInformationDialog : spinner.visibility = View.VISIBLE } - @SuppressLint("InflateParams") override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { inflater = requireActivity().layoutInflater val customLayout = inflater.inflate(R.layout.dialog_pocketpaint_save, null) - val dialogTitle = if(permission == PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT){ + val dialogTitle = if (permission == PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT) { R.string.dialog_save_project_title } else { R.string.dialog_save_image_title diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/SaveImage.kt b/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/SaveImage.kt index 97bb1ce2f0..771e108a2a 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/SaveImage.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/SaveImage.kt @@ -40,6 +40,7 @@ import org.catrobat.paintroid.command.serialization.CommandSerializer import org.catrobat.paintroid.common.PNG_IMAGE_ENDING import org.catrobat.paintroid.contract.LayerContracts import org.catrobat.paintroid.model.Project +import org.catrobat.paintroid.model.SaveImageOptions import java.io.File import java.io.IOException import java.lang.ref.WeakReference @@ -51,9 +52,7 @@ class SaveImage( private val layerModel: LayerContracts.Model, private val commandSerializer: CommandSerializer, private var uri: Uri?, - private var imagePreviewUri: Uri?, - private val saveAsCopy: Boolean, - private val saveProject: Boolean, + private val saveImageOptions: SaveImageOptions, private val context: Context, private val scopeIO: CoroutineScope, private val idlingResource: CountingIdlingResource @@ -62,6 +61,8 @@ class SaveImage( companion object { private val TAG = SaveImage::class.java.simpleName + private const val KILOBYTE = 1024 + var currentUri: Uri? = null } private fun getImageUri( @@ -86,12 +87,12 @@ class SaveImage( Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString() val pathToFile = imagesDirectory + File.separator + filename + "." + PNG_IMAGE_ENDING val imageFile = File(pathToFile) - return if (imagePreviewUri == null || !imageFile.exists()) { + return if (saveImageOptions.imagePreviewUri == null || !imageFile.exists()) { val imagePreviewFilename = filename.replace(".catrobat-image", ".png") val imageUri = FileIO.saveBitmapToFile(imagePreviewFilename, bitmap, callback.contentResolver, context) imageUri } else { - imagePreviewUri?.let { FileIO.saveBitmapToUri(it, bitmap, context) } + saveImageOptions.imagePreviewUri?.let { FileIO.saveBitmapToUri(it, bitmap, context) } } } @@ -140,48 +141,13 @@ class SaveImage( callback.onSaveImagePreExecute(requestCode) } - var currentUri: Uri? = null - var imagePreviewPath: Uri? = null scopeIO.launch { try { idlingResource.increment() val bitmap = layerModel.getBitmapOfAllLayers() val filename = FileIO.defaultFileName - if (saveProject) { - FileIO.fileType = FileIO.FileType.CATROBAT - currentUri = if (uri != null) { - uri?.let { - commandSerializer.overWriteFile(filename, it, callback.contentResolver) - } - } else { - commandSerializer.writeToFile(filename) - } - imagePreviewPath = getImagePreviewUri(callback, bitmap) - val date = Calendar.getInstance().timeInMillis - if(uri != null){ - uri?.let { - projectDB.dao.updateProject(filename, imagePreviewPath.toString(), currentUri.toString(), date) - projectAdapter.updateProject(filename, imagePreviewPath.toString(), currentUri.toString(), date) - } - } else { - val dimensions = getImageDimensions(imagePreviewPath) - val size = getImageSize(imagePreviewPath) - val project = Project( - filename, - currentUri.toString(), - date, - date, - "${dimensions?.first} x ${dimensions?.second}", - FileIO.fileType.toString(), - size, - imagePreviewPath.toString() - ) - projectDB.dao.insertProject(project) - projectAdapter.insertProject(project) - projectName = filename - projectUri = currentUri.toString() - projectImagePreviewUri = imagePreviewPath.toString() - } + if (saveImageOptions.saveProject) { + executeSaveProject(filename, bitmap, callback) } else { currentUri = if (FileIO.fileType == FileIO.FileType.ORA) { val layers = layerModel.layers @@ -197,11 +163,7 @@ class SaveImage( } else if (FileIO.fileType == FileIO.FileType.CATROBAT) { if (uri != null) { uri?.let { - commandSerializer.overWriteFile( - filename, - it, - callback.contentResolver - ) + commandSerializer.overWriteFile(filename, it, callback.contentResolver) } } else { commandSerializer.writeToFile(filename) @@ -221,12 +183,49 @@ class SaveImage( withContext(Dispatchers.Main) { if (!callback.isFinishing) { - callback.onSaveImagePostExecute(requestCode, currentUri, saveAsCopy) + callback.onSaveImagePostExecute(requestCode, currentUri, saveImageOptions.saveAsCopy) } } } } + private fun executeSaveProject(filename: String, bitmap: Bitmap?, callback: SaveImageCallback) { + FileIO.fileType = FileIO.FileType.CATROBAT + currentUri = if (uri != null) { + uri?.let { + commandSerializer.overWriteFile(filename, it, callback.contentResolver) + } + } else { + commandSerializer.writeToFile(filename) + } + val imagePreviewPath = getImagePreviewUri(callback, bitmap) + val date = Calendar.getInstance().timeInMillis + if (uri != null) { + uri?.let { + projectDB.dao.updateProject(filename, imagePreviewPath.toString(), currentUri.toString(), date) + projectAdapter.updateProject(filename, imagePreviewPath.toString(), currentUri.toString(), date) + } + } else { + val dimensions = getImageDimensions(imagePreviewPath) + val size = getImageSize(imagePreviewPath) + val project = Project( + filename, + currentUri.toString(), + date, + date, + "${dimensions?.first} x ${dimensions?.second}", + FileIO.fileType.toString(), + size, + imagePreviewPath.toString() + ) + projectDB.dao.insertProject(project) + projectAdapter.insertProject(project) + projectName = filename + projectUri = currentUri.toString() + projectImagePreviewUri = imagePreviewPath.toString() + } + } + private fun getImageDimensions(uri: Uri?): Pair? { val inputStream = uri?.let { context.contentResolver.openInputStream(it) } val options = BitmapFactory.Options() @@ -246,10 +245,10 @@ class SaveImage( try { val inputStream = uri?.let { context.contentResolver.openInputStream(it) } val bytes = inputStream?.available()?.toLong() ?: 0 - size = bytes.toDouble() / (1024 * 1024) + size = bytes.toDouble() / (KILOBYTE * KILOBYTE) inputStream?.close() } catch (e: IOException) { - e.printStackTrace() + Log.e(TAG, "Error occurred while getting the image size: ${e.message}") } return size } diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/model/Project.kt b/Paintroid/src/main/java/org/catrobat/paintroid/model/Project.kt index ba375c32d3..6a8e2e6389 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/model/Project.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/model/Project.kt @@ -15,4 +15,4 @@ data class Project( val imagePreviewPath: String, @PrimaryKey(autoGenerate = true) val id: Int = 0 -) \ No newline at end of file +) diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/model/SaveImageOptions.kt b/Paintroid/src/main/java/org/catrobat/paintroid/model/SaveImageOptions.kt new file mode 100644 index 0000000000..f6a2201770 --- /dev/null +++ b/Paintroid/src/main/java/org/catrobat/paintroid/model/SaveImageOptions.kt @@ -0,0 +1,5 @@ +package org.catrobat.paintroid.model + +import android.net.Uri + +data class SaveImageOptions(val imagePreviewUri: Uri?, val saveAsCopy: Boolean, val saveProject: Boolean) diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt b/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt index f9d3ee409f..e210576e15 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt @@ -53,12 +53,33 @@ import org.catrobat.paintroid.colorpicker.ColorHistory import org.catrobat.paintroid.command.CommandFactory import org.catrobat.paintroid.command.CommandManager import org.catrobat.paintroid.command.serialization.CommandSerializer -import org.catrobat.paintroid.common.* import org.catrobat.paintroid.common.MainActivityConstants.ActivityRequestCode import org.catrobat.paintroid.common.MainActivityConstants.CreateFileRequestCode import org.catrobat.paintroid.common.MainActivityConstants.LoadImageRequestCode import org.catrobat.paintroid.common.MainActivityConstants.PermissionRequestCode import org.catrobat.paintroid.common.MainActivityConstants.SaveImageRequestCode +import org.catrobat.paintroid.common.PERMISSION_REQUEST_CODE_REPLACE_PICTURE +import org.catrobat.paintroid.common.PERMISSION_REQUEST_CODE_IMPORT_PICTURE +import org.catrobat.paintroid.common.PERMISSION_EXTERNAL_STORAGE_SAVE_CONFIRMED_LOAD_NEW +import org.catrobat.paintroid.common.PERMISSION_EXTERNAL_STORAGE_SAVE_CONFIRMED_NEW_EMPTY +import org.catrobat.paintroid.common.PERMISSION_EXTERNAL_STORAGE_SAVE_CONFIRMED_FINISH +import org.catrobat.paintroid.common.PERMISSION_EXTERNAL_STORAGE_SAVE_COPY +import org.catrobat.paintroid.common.PERMISSION_EXTERNAL_STORAGE_SAVE +import org.catrobat.paintroid.common.PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT +import org.catrobat.paintroid.common.REQUEST_CODE_LOAD_PICTURE +import org.catrobat.paintroid.common.REQUEST_CODE_INTRO +import org.catrobat.paintroid.common.REQUEST_CODE_IMPORT_PNG +import org.catrobat.paintroid.common.LOAD_IMAGE_IMPORT_PNG +import org.catrobat.paintroid.common.LOAD_IMAGE_DEFAULT +import org.catrobat.paintroid.common.RESULT_INTRO_MW_NOT_SUPPORTED +import org.catrobat.paintroid.common.SAVE_IMAGE_DEFAULT +import org.catrobat.paintroid.common.SAVE_IMAGE_FINISH +import org.catrobat.paintroid.common.SAVE_IMAGE_LOAD_NEW +import org.catrobat.paintroid.common.SAVE_IMAGE_NEW_EMPTY +import org.catrobat.paintroid.common.SAVE_PROJECT_DEFAULT +import org.catrobat.paintroid.common.LOAD_IMAGE_CATROID +import org.catrobat.paintroid.common.CREATE_FILE_DEFAULT +import org.catrobat.paintroid.common.TEMP_PICTURE_NAME import org.catrobat.paintroid.contract.MainActivityContracts import org.catrobat.paintroid.contract.MainActivityContracts.Interactor import org.catrobat.paintroid.contract.MainActivityContracts.MainView @@ -494,13 +515,12 @@ open class MainActivityPresenter( ) checkForDefaultFilename() } - PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT -> { + PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT -> saveProjectConfirmClicked( SAVE_PROJECT_DEFAULT, FileIO.storeImageUri, FileIO.storeImagePreviewUri, ) - } PERMISSION_EXTERNAL_STORAGE_SAVE_COPY -> { saveCopyConfirmClicked( SAVE_IMAGE_DEFAULT, diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/ui/MainActivityInteractor.kt b/Paintroid/src/main/java/org/catrobat/paintroid/ui/MainActivityInteractor.kt index f6bce9c854..aeb9bd22ef 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/ui/MainActivityInteractor.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/ui/MainActivityInteractor.kt @@ -32,6 +32,7 @@ import org.catrobat.paintroid.iotasks.LoadImage import org.catrobat.paintroid.iotasks.LoadImage.LoadImageCallback import org.catrobat.paintroid.iotasks.SaveImage import org.catrobat.paintroid.iotasks.SaveImage.SaveImageCallback +import org.catrobat.paintroid.model.SaveImageOptions class MainActivityInteractor(private val idlingResource: CountingIdlingResource) : Interactor { private val scopeIO = CoroutineScope(Dispatchers.IO) @@ -44,11 +45,17 @@ class MainActivityInteractor(private val idlingResource: CountingIdlingResource) uri: Uri?, context: Context ) { - SaveImage(callback, requestCode, layerModel, commandSerializer, uri, null, true, - saveProject = false, - context = context, - scopeIO = scopeIO, - idlingResource = idlingResource + val saveImageOptions = SaveImageOptions(null, saveAsCopy = true, saveProject = false) + SaveImage( + callback, + requestCode, + layerModel, + commandSerializer, + uri, + saveImageOptions, + context, + scopeIO, + idlingResource ).execute() } @@ -64,12 +71,17 @@ class MainActivityInteractor(private val idlingResource: CountingIdlingResource) uri: Uri?, context: Context ) { - SaveImage(callback, requestCode, layerModel, commandSerializer, uri, null, - saveAsCopy = false, - saveProject = false, - context = context, - scopeIO = scopeIO, - idlingResource = idlingResource + val saveImageOptions = SaveImageOptions(null, saveAsCopy = false, saveProject = false) + SaveImage( + callback, + requestCode, + layerModel, + commandSerializer, + uri, + saveImageOptions, + context, + scopeIO, + idlingResource ).execute() } @@ -82,7 +94,18 @@ class MainActivityInteractor(private val idlingResource: CountingIdlingResource) imagePreviewUri: Uri?, context: Context ) { - SaveImage(callback, requestCode, layerModel, commandSerializer, uri, imagePreviewUri, false, true, context, scopeIO, idlingResource).execute() + val saveImageOptions = SaveImageOptions(imagePreviewUri, saveAsCopy = false, saveProject = true) + SaveImage( + callback, + requestCode, + layerModel, + commandSerializer, + uri, + saveImageOptions, + context, + scopeIO, + idlingResource + ).execute() } override fun loadFile( From 1a4b3cef85da528e49671cc5398c55370d490d3a Mon Sep 17 00:00:00 2001 From: Rd Date: Sun, 11 Jun 2023 09:51:53 +0530 Subject: [PATCH 08/52] Fixed Lint warnings --- .../catrobat/paintroid/dialog/ProjectDeleteDialog.kt | 2 +- .../res/layout/activity_pocketpaint_landing_page.xml | 12 ++++++++---- .../src/main/res/layout/pocketpaint_item_project.xml | 12 +++++++----- Paintroid/src/main/res/values/string.xml | 10 ++++++++++ 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDeleteDialog.kt b/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDeleteDialog.kt index 3773854668..afdd02bcbf 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDeleteDialog.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDeleteDialog.kt @@ -19,7 +19,7 @@ class ProjectDeleteDialog(private val projectId: Int, private val name: String, .setPositiveButton(R.string.pocketpaint_ok) { _, _ -> projectDB.dao.deleteProject(projectId) projectAdapter.removeProject(projectId, position) - projectAdapter.notifyDataSetChanged() + projectAdapter.notifyItemRemoved(position) } .setNegativeButton(R.string.pocketpaint_cancel) { _, _ -> dismiss() } .create() diff --git a/Paintroid/src/main/res/layout/activity_pocketpaint_landing_page.xml b/Paintroid/src/main/res/layout/activity_pocketpaint_landing_page.xml index 69973300ef..da82f64232 100644 --- a/Paintroid/src/main/res/layout/activity_pocketpaint_landing_page.xml +++ b/Paintroid/src/main/res/layout/activity_pocketpaint_landing_page.xml @@ -23,6 +23,7 @@ android:layout_height="match_parent" android:scaleType="center" android:background="@drawable/pocketpaint_checkeredbg_repeat" + android:contentDescription="@string/pocketpaint_landing_page_image_preview" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -30,9 +31,10 @@ @@ -61,6 +63,7 @@ android:backgroundTint="@color/pocketpaint_color_picker_orange3" android:elevation="5dp" android:src="@drawable/ic_pocketpaint_download" + android:contentDescription="@string/pocketpaint_landing_page_fab_load_image" app:layout_constraintBottom_toTopOf="@+id/pocketpaint_fab_new_image" app:layout_constraintEnd_toEndOf="parent" /> @@ -73,6 +76,7 @@ android:elevation="5dp" android:layout_margin="15dp" android:src="@drawable/ic_pocketpaint_plus_enabled" + android:contentDescription="@string/pocketpaint_landing_page_fab_new_image" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" /> diff --git a/Paintroid/src/main/res/layout/pocketpaint_item_project.xml b/Paintroid/src/main/res/layout/pocketpaint_item_project.xml index aa8f35f4cd..d7c6be92d8 100644 --- a/Paintroid/src/main/res/layout/pocketpaint_item_project.xml +++ b/Paintroid/src/main/res/layout/pocketpaint_item_project.xml @@ -26,9 +26,10 @@ android:id="@+id/iv_pocket_paint_project_thumbnail_image" android:layout_width="50dp" android:layout_height="80dp" - android:layout_marginLeft="10dp" + android:layout_marginStart="10dp" android:layout_marginTop="5dp" android:src="@drawable/pocketpaint_logo_small" + android:contentDescription="@string/pocketpaint_landing_project_image_thumbnail" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -36,10 +37,10 @@ android:id="@+id/tv_pocket_paint_project_name" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="Project Name" + android:text="@string/pocketpaint_project_name" android:textSize="20sp" android:textColor="@color/pocketpaint_color_picker_white" - android:layout_marginLeft="20dp" + android:layout_marginStart="20dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toEndOf="@+id/iv_pocket_paint_project_thumbnail_image" app:layout_constraintTop_toTopOf="parent" @@ -49,8 +50,8 @@ android:id="@+id/tv_pocket_paint_project_lastmodified" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginLeft="20dp" - android:text="Project Name" + android:layout_marginStart="20dp" + android:text="@string/pocketpaint_project_last_modified" android:textColor="@color/pocketpaint_lighter_gray" android:textSize="13sp" app:layout_constraintBottom_toBottomOf="parent" @@ -64,6 +65,7 @@ android:layout_height="32dp" android:src="@drawable/ic_pocketpaint_more" android:layout_marginTop="24dp" + android:contentDescription="@string/pocketpaint_landing_project_more_options" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.917" app:layout_constraintStart_toEndOf="@+id/tv_pocket_paint_project_name" diff --git a/Paintroid/src/main/res/values/string.xml b/Paintroid/src/main/res/values/string.xml index 055ffb48fc..b32072af02 100644 --- a/Paintroid/src/main/res/values/string.xml +++ b/Paintroid/src/main/res/values/string.xml @@ -51,6 +51,14 @@ Copy Cut + Image Preview + Edit Image + Load Image + New Image + Project Thumbnail + Project Options + My Projects + Stickers @string/button_import_image @string/menu_save_image @@ -190,6 +198,8 @@ Resolution: %s<br />Last modified: %s<br />Creation date: %s<br />Format: %s<br />Size: %s Delete %s Do you really want to delete your Project? + Project Name + DD/MM/YYYY HH:MM This app needs the requested permission to function properly. In order to save images to the local memory, the app needs read and write access to it. This app needs the requested permission to function properly. In order to save images to the local memory, the app needs read and write access to it. From 3cf1874ff08f29c023e3e2efd34a8407e58183c3 Mon Sep 17 00:00:00 2001 From: Rd Date: Sun, 11 Jun 2023 12:34:39 +0530 Subject: [PATCH 09/52] Fixed SprayTool context issue --- .../main/java/org/catrobat/paintroid/tools/Tool.kt | 2 +- .../paintroid/tools/implementation/BaseTool.kt | 13 +++++++++---- .../implementation/BaseToolWithRectangleShape.kt | 2 +- .../tools/implementation/BaseToolWithShape.kt | 6 +++--- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/tools/Tool.kt b/Paintroid/src/main/java/org/catrobat/paintroid/tools/Tool.kt index 5c320e929b..2f637d1639 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/tools/Tool.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/tools/Tool.kt @@ -55,7 +55,7 @@ interface Tool { pointY: Float, screenWidth: Int, screenHeight: Int - ): Point + ): Point? fun handleUpAnimations(coordinate: PointF?) fun handleDownAnimations(coordinate: PointF?) diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/BaseTool.kt b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/BaseTool.kt index 156d3c2aa3..61ff5a5914 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/BaseTool.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/BaseTool.kt @@ -54,7 +54,10 @@ abstract class BaseTool( protected val movedDistance: PointF @JvmField - protected var scrollBehavior: ScrollBehavior + protected var scrollTolerance: Int? = null + + @JvmField + protected var scrollBehavior: ScrollBehavior? = null @JvmField var previousEventCoordinate: PointF? @@ -63,8 +66,10 @@ abstract class BaseTool( protected var commandFactory: CommandFactory = DefaultCommandFactory() init { - val scrollTolerance = contextCallback.scrollTolerance - scrollBehavior = PointScrollBehavior(scrollTolerance) + contextCallback?.let { callback -> + scrollTolerance = callback.scrollTolerance + } + scrollBehavior = scrollTolerance?.let { PointScrollBehavior(it) } movedDistance = PointF(0f, 0f) previousEventCoordinate = PointF(0f, 0f) if (toolPaint != null && toolPaint.paint != null && toolPaint.paint.pathEffect != null) { @@ -117,7 +122,7 @@ abstract class BaseTool( pointY: Float, screenWidth: Int, screenHeight: Int - ): Point = scrollBehavior.getScrollDirection(pointX, pointY, screenWidth, screenHeight) + ): Point? = scrollBehavior?.getScrollDirection(pointX, pointY, screenWidth, screenHeight) override fun handToolMode(): Boolean = false } diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/BaseToolWithRectangleShape.kt b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/BaseToolWithRectangleShape.kt index 18bb4ec5e2..006262996c 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/BaseToolWithRectangleShape.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/BaseToolWithRectangleShape.kt @@ -740,7 +740,7 @@ abstract class BaseToolWithRectangleShape( pointY: Float, screenWidth: Int, screenHeight: Int - ): Point { + ): Point? { return if (currentAction == FloatingBoxAction.MOVE || currentAction == FloatingBoxAction.RESIZE) { super.getAutoScrollDirection(pointX, pointY, screenWidth, screenHeight) } else Point(0, 0) diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/BaseToolWithShape.kt b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/BaseToolWithShape.kt index 5d5bea966c..8b5762acc3 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/BaseToolWithShape.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/BaseToolWithShape.kt @@ -124,14 +124,14 @@ abstract class BaseToolWithShape @SuppressLint("VisibleForTests") constructor( pointY: Float, screenWidth: Int, screenHeight: Int - ): Point { + ): Point? { val surfaceToolPosition = workspace.getSurfacePointFromCanvasPoint(toolPosition) - return scrollBehavior.getScrollDirection( + return scrollBehavior?.getScrollDirection( surfaceToolPosition.x, surfaceToolPosition.y, screenWidth, screenHeight - ) + ) ?: null } abstract fun onClickOnButton() From 9cc2e8e364eef7b5798b1e048bfe3965cf40416d Mon Sep 17 00:00:00 2001 From: Rd Date: Sun, 11 Jun 2023 16:45:52 +0530 Subject: [PATCH 10/52] Landing Page Tests Trigger --- .../LandingPageActivityIntegrationTest.kt | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt index 2b97439ab9..bae782209b 100644 --- a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt +++ b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt @@ -26,18 +26,16 @@ import androidx.test.espresso.UiController import androidx.test.espresso.ViewAction import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.action.ViewActions.replaceText -import androidx.test.espresso.action.ViewActions.closeSoftKeyboard -import androidx.test.espresso.action.ViewActions.scrollTo import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.contrib.RecyclerViewActions import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition import androidx.test.espresso.intent.Intents import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction +import androidx.test.espresso.matcher.RootMatchers import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.espresso.matcher.ViewMatchers.withId -import androidx.test.espresso.matcher.ViewMatchers.isRoot import androidx.test.espresso.matcher.ViewMatchers.hasDescendant import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry @@ -52,7 +50,6 @@ import org.catrobat.paintroid.data.local.database.ProjectDatabase import org.catrobat.paintroid.test.espresso.util.BitmapLocationProvider import org.catrobat.paintroid.test.espresso.util.DrawingSurfaceLocationProvider import org.catrobat.paintroid.test.espresso.util.UiInteractions.touchAt -import org.catrobat.paintroid.test.espresso.util.UiInteractions.waitFor import org.catrobat.paintroid.test.espresso.util.UiMatcher.atPosition import org.catrobat.paintroid.test.espresso.util.wrappers.DrawingSurfaceInteraction.onDrawingSurfaceView import org.catrobat.paintroid.test.espresso.util.wrappers.ToolBarViewInteraction @@ -74,6 +71,8 @@ import java.io.File import java.io.IOException import kotlin.collections.ArrayList +private const val THREAD_WAITING_TIME: Long = 300 + @RunWith(AndroidJUnit4::class) class LandingPageActivityIntegrationTest { @@ -209,7 +208,7 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(waitFor(300)) + Thread.sleep(THREAD_WAITING_TIME) pressBack() onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) @@ -232,7 +231,7 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(waitFor(300)) + Thread.sleep(THREAD_WAITING_TIME) pressBack() onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) @@ -257,7 +256,7 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(waitFor(300)) + Thread.sleep(THREAD_WAITING_TIME) pressBack() onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) @@ -302,7 +301,7 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(waitFor(300)) + Thread.sleep(THREAD_WAITING_TIME) pressBack() onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) @@ -344,7 +343,7 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(waitFor(300)) + Thread.sleep(THREAD_WAITING_TIME) pressBack() onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) @@ -370,8 +369,9 @@ class LandingPageActivityIntegrationTest { ) onView(withText(R.string.menu_project_detail_title)) .perform(click()) - onView(isRoot()).perform(waitFor(300)) + Thread.sleep(THREAD_WAITING_TIME) onView(withText(android.R.string.ok)) + .inRoot(RootMatchers.isDialog()) .perform(click()) } @@ -389,7 +389,7 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(waitFor(300)) + Thread.sleep(THREAD_WAITING_TIME) pressBack() onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) @@ -431,7 +431,7 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(waitFor(300)) + Thread.sleep(THREAD_WAITING_TIME) pressBack() onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) @@ -457,7 +457,8 @@ class LandingPageActivityIntegrationTest { ) onView(withText(R.string.menu_project_delete_title)) .perform(click()) - onView(withText(R.string.cancel_button_text)) + onView(withId(android.R.id.button2)) + .inRoot(RootMatchers.isDialog()) .perform(click()) onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) @@ -483,7 +484,7 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(waitFor(300)) + Thread.sleep(THREAD_WAITING_TIME) pressBack() onView(withId(R.id.pocketpaint_projects_list)).perform( actionOnItemAtPosition( @@ -507,15 +508,12 @@ class LandingPageActivityIntegrationTest { ) onView(withText(R.string.menu_project_delete_title)) .perform(click()) - onView(isRoot()).perform(waitFor(100)) + Thread.sleep(THREAD_WAITING_TIME) onView(withId(android.R.id.button1)) - .perform(closeSoftKeyboard()) - .perform(scrollTo()) + .inRoot(RootMatchers.isDialog()) .perform(click()) - onView(isRoot()).perform(waitFor(300)) onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) - onView(isRoot()).perform(waitFor(300)) if (isRecyclerViewEmpty(recyclerViewMatcher)) { onView(withId(R.id.pocketpaint_projects_list)) .check(matches(not(hasDescendant(withId(R.id.iv_pocket_paint_project_thumbnail_image))))) @@ -550,7 +548,7 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(waitFor(300)) + Thread.sleep(THREAD_WAITING_TIME) pressBack() onView(withId(R.id.pocketpaint_projects_list)) .perform(actionOnItemAtPosition(position, click())) @@ -568,6 +566,7 @@ class LandingPageActivityIntegrationTest { matches(isDisplayed()) ) } + @Test fun testImagePreviewAfterProjectInsert() { onView(withId(R.id.pocketpaint_fab_new_image)) @@ -584,7 +583,7 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(waitFor(300)) + Thread.sleep(THREAD_WAITING_TIME) pressBack() onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) From 9e0643c4a6be252108a5f6fccf1a72d88da23966 Mon Sep 17 00:00:00 2001 From: Rd Date: Tue, 13 Jun 2023 05:53:53 +0530 Subject: [PATCH 11/52] Fix Device Tests - Landing Page --- .../LandingPageActivityIntegrationTest.kt | 210 ++++++------------ .../catrobat/paintroid/LandingPageActivity.kt | 1 + .../org/catrobat/paintroid/MainActivity.kt | 6 + .../paintroid/adapter/ProjectAdapter.kt | 10 +- 4 files changed, 88 insertions(+), 139 deletions(-) diff --git a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt index bae782209b..1270409e05 100644 --- a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt +++ b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt @@ -18,6 +18,7 @@ import android.widget.ImageView import android.widget.TextView import androidx.appcompat.widget.Toolbar import androidx.core.graphics.drawable.toBitmap +import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.room.Room import androidx.test.espresso.Espresso.onView @@ -26,20 +27,25 @@ import androidx.test.espresso.UiController import androidx.test.espresso.ViewAction import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.action.ViewActions.replaceText +import androidx.test.espresso.action.ViewActions.closeSoftKeyboard +import androidx.test.espresso.action.ViewActions.scrollTo import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.contrib.RecyclerViewActions import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition import androidx.test.espresso.intent.Intents import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction -import androidx.test.espresso.matcher.RootMatchers import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.isRoot import androidx.test.espresso.matcher.ViewMatchers.hasDescendant import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import androidx.test.rule.ActivityTestRule +import kotlinx.coroutines.Dispatchers.Main +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext import org.catrobat.paintroid.LandingPageActivity import org.catrobat.paintroid.R import org.catrobat.paintroid.adapter.ProjectAdapter @@ -47,9 +53,11 @@ import org.catrobat.paintroid.common.CATROBAT_IMAGE_ENDING import org.catrobat.paintroid.common.PNG_IMAGE_ENDING import org.catrobat.paintroid.data.local.dao.ProjectDao import org.catrobat.paintroid.data.local.database.ProjectDatabase +import org.catrobat.paintroid.model.Project import org.catrobat.paintroid.test.espresso.util.BitmapLocationProvider import org.catrobat.paintroid.test.espresso.util.DrawingSurfaceLocationProvider import org.catrobat.paintroid.test.espresso.util.UiInteractions.touchAt +import org.catrobat.paintroid.test.espresso.util.UiInteractions.waitFor import org.catrobat.paintroid.test.espresso.util.UiMatcher.atPosition import org.catrobat.paintroid.test.espresso.util.wrappers.DrawingSurfaceInteraction.onDrawingSurfaceView import org.catrobat.paintroid.test.espresso.util.wrappers.ToolBarViewInteraction @@ -69,10 +77,9 @@ import org.junit.Assert import org.junit.runner.RunWith import java.io.File import java.io.IOException +import java.util.* import kotlin.collections.ArrayList -private const val THREAD_WAITING_TIME: Long = 300 - @RunWith(AndroidJUnit4::class) class LandingPageActivityIntegrationTest { @@ -86,11 +93,25 @@ class LandingPageActivityIntegrationTest { var screenshotOnFailRule = ScreenshotOnFailRule() private lateinit var activity: LandingPageActivity + private lateinit var intent: Intent + private lateinit var recyclerView: RecyclerView + private lateinit var adapter: ProjectAdapter companion object { private lateinit var deletionFileList: ArrayList private const val PROJECT_NAME = "projectName" private const val position = 0 + private val project = Project( + "name", + "catrobat/path", + 0, + 0, + "0x0", + "CATROBAT", + 0.0, + "paintroid/path", + 1) + private val projectList = ArrayList().apply { add(project) } } @Before @@ -100,6 +121,7 @@ class LandingPageActivityIntegrationTest { .build() dao = database.dao deletionFileList = ArrayList() + intent = Intent() activity = launchActivityRule.activity } @@ -158,23 +180,6 @@ class LandingPageActivityIntegrationTest { .check(matches(isDisplayed())) } - @Test - fun testNewImage() { - onView(withId(R.id.pocketpaint_fab_new_image)) - .perform(click()) - onDrawingSurfaceView() - .perform(touchAt(DrawingSurfaceLocationProvider.MIDDLE)) - onDrawingSurfaceView() - .checkPixelColor(Color.BLACK, BitmapLocationProvider.MIDDLE) - pressBack() - onView(withText(R.string.discard_button_text)) - .perform(click()) - onView(withId(R.id.pocketpaint_fab_new_image)) - .perform(click()) - onDrawingSurfaceView() - .checkPixelColor(Color.TRANSPARENT, BitmapLocationProvider.MIDDLE) - } - @Test fun testLoadImageIntentStarted() { Intents.init() @@ -194,29 +199,6 @@ class LandingPageActivityIntegrationTest { .check(matches(isDisplayed())) } - @Test - fun testProjectInsertedAndDisplayed() { - onView(withId(R.id.pocketpaint_fab_new_image)) - .perform(click()) - onDrawingSurfaceView() - .perform(touchAt(DrawingSurfaceLocationProvider.MIDDLE)) - TopBarViewInteraction.onTopBarView() - .performOpenMoreOptions() - onView(withText(R.string.menu_save_project)) - .perform(click()) - onView(withId(R.id.pocketpaint_image_name_save_text)) - .perform(replaceText(PROJECT_NAME)) - onView(withText(R.string.save_button_text)) - .perform(click()) - Thread.sleep(THREAD_WAITING_TIME) - pressBack() - onView(withId(R.id.pocketpaint_projects_list)) - .perform(RecyclerViewActions.scrollToPosition(position)) - onView(withId(R.id.pocketpaint_projects_list)).check( - matches(atPosition(position, isDisplayed())) - ) - } - @Test fun testProjectInsertedWithProjectName() { onView(withId(R.id.pocketpaint_fab_new_image)) @@ -231,8 +213,10 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - Thread.sleep(THREAD_WAITING_TIME) + onView(isRoot()).perform(waitFor(1000)) pressBack() + launchActivityRule.launchActivity(intent) + onView(isRoot()).perform(waitFor(300)) onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) onView(withId(R.id.pocketpaint_projects_list)) @@ -244,6 +228,7 @@ class LandingPageActivityIntegrationTest { @Test fun testProjectInsertedWithImagePreview() { + val testName = UUID.randomUUID().toString() onView(withId(R.id.pocketpaint_fab_new_image)) .perform(click()) onDrawingSurfaceView() @@ -253,16 +238,18 @@ class LandingPageActivityIntegrationTest { onView(withText(R.string.menu_save_project)) .perform(click()) onView(withId(R.id.pocketpaint_image_name_save_text)) - .perform(replaceText(PROJECT_NAME)) + .perform(replaceText(testName)) onView(withText(R.string.save_button_text)) .perform(click()) - Thread.sleep(THREAD_WAITING_TIME) + onView(isRoot()).perform(waitFor(1000)) pressBack() + launchActivityRule.launchActivity(intent) + onView(isRoot()).perform(waitFor(300)) onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) val imagesDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString() - val imagePathToFile = imagesDirectory + File.separator + PROJECT_NAME + "." + PNG_IMAGE_ENDING + val imagePathToFile = imagesDirectory + File.separator + testName + "." + PNG_IMAGE_ENDING val imageFile = File(imagePathToFile) onView(withId(R.id.pocketpaint_projects_list)).perform( actionOnItemAtPosition( @@ -289,22 +276,7 @@ class LandingPageActivityIntegrationTest { @Test fun testProjectOverFlowMenuDetailsDisplayed() { - onView(withId(R.id.pocketpaint_fab_new_image)) - .perform(click()) - onDrawingSurfaceView() - .perform(touchAt(DrawingSurfaceLocationProvider.MIDDLE)) - TopBarViewInteraction.onTopBarView() - .performOpenMoreOptions() - onView(withText(R.string.menu_save_project)) - .perform(click()) - onView(withId(R.id.pocketpaint_image_name_save_text)) - .perform(replaceText(PROJECT_NAME)) - onView(withText(R.string.save_button_text)) - .perform(click()) - Thread.sleep(THREAD_WAITING_TIME) - pressBack() - onView(withId(R.id.pocketpaint_projects_list)) - .perform(RecyclerViewActions.scrollToPosition(position)) + insertProjectIntoRecyclerView() onView(withId(R.id.pocketpaint_projects_list)).perform( actionOnItemAtPosition( position, @@ -331,22 +303,7 @@ class LandingPageActivityIntegrationTest { @Test fun testProjectOverFlowMenuProjectDetail() { - onView(withId(R.id.pocketpaint_fab_new_image)) - .perform(click()) - onDrawingSurfaceView() - .perform(touchAt(DrawingSurfaceLocationProvider.MIDDLE)) - TopBarViewInteraction.onTopBarView() - .performOpenMoreOptions() - onView(withText(R.string.menu_save_project)) - .perform(click()) - onView(withId(R.id.pocketpaint_image_name_save_text)) - .perform(replaceText(PROJECT_NAME)) - onView(withText(R.string.save_button_text)) - .perform(click()) - Thread.sleep(THREAD_WAITING_TIME) - pressBack() - onView(withId(R.id.pocketpaint_projects_list)) - .perform(RecyclerViewActions.scrollToPosition(position)) + insertProjectIntoRecyclerView() onView(withId(R.id.pocketpaint_projects_list)).perform( actionOnItemAtPosition( position, @@ -369,30 +326,14 @@ class LandingPageActivityIntegrationTest { ) onView(withText(R.string.menu_project_detail_title)) .perform(click()) - Thread.sleep(THREAD_WAITING_TIME) + onView(isRoot()).perform(waitFor(300)) onView(withText(android.R.string.ok)) - .inRoot(RootMatchers.isDialog()) .perform(click()) } @Test fun testProjectOverFlowMenuDeleteDisplayed() { - onView(withId(R.id.pocketpaint_fab_new_image)) - .perform(click()) - onDrawingSurfaceView() - .perform(touchAt(DrawingSurfaceLocationProvider.MIDDLE)) - TopBarViewInteraction.onTopBarView() - .performOpenMoreOptions() - onView(withText(R.string.menu_save_project)) - .perform(click()) - onView(withId(R.id.pocketpaint_image_name_save_text)) - .perform(replaceText(PROJECT_NAME)) - onView(withText(R.string.save_button_text)) - .perform(click()) - Thread.sleep(THREAD_WAITING_TIME) - pressBack() - onView(withId(R.id.pocketpaint_projects_list)) - .perform(RecyclerViewActions.scrollToPosition(position)) + insertProjectIntoRecyclerView() onView(withId(R.id.pocketpaint_projects_list)).perform( actionOnItemAtPosition( position, @@ -419,22 +360,7 @@ class LandingPageActivityIntegrationTest { @Test fun testProjectOverFlowMenuProjectDeleteCancel() { - onView(withId(R.id.pocketpaint_fab_new_image)) - .perform(click()) - onDrawingSurfaceView() - .perform(touchAt(DrawingSurfaceLocationProvider.MIDDLE)) - TopBarViewInteraction.onTopBarView() - .performOpenMoreOptions() - onView(withText(R.string.menu_save_project)) - .perform(click()) - onView(withId(R.id.pocketpaint_image_name_save_text)) - .perform(replaceText(PROJECT_NAME)) - onView(withText(R.string.save_button_text)) - .perform(click()) - Thread.sleep(THREAD_WAITING_TIME) - pressBack() - onView(withId(R.id.pocketpaint_projects_list)) - .perform(RecyclerViewActions.scrollToPosition(position)) + insertProjectIntoRecyclerView() onView(withId(R.id.pocketpaint_projects_list)).perform( actionOnItemAtPosition( position, @@ -457,35 +383,22 @@ class LandingPageActivityIntegrationTest { ) onView(withText(R.string.menu_project_delete_title)) .perform(click()) - onView(withId(android.R.id.button2)) - .inRoot(RootMatchers.isDialog()) + onView(withText(R.string.cancel_button_text)) .perform(click()) onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) onView(withId(R.id.pocketpaint_projects_list)) .check(matches(atPosition(position, hasDescendant(allOf( isAssignableFrom(TextView::class.java), - withText(PROJECT_NAME) + withText(project.name) ))))) } @Test fun testProjectOverFlowMenuProjectDelete() { val recyclerViewMatcher = withId(R.id.pocketpaint_projects_list) - onView(withId(R.id.pocketpaint_fab_new_image)) - .perform(click()) - onDrawingSurfaceView() - .perform(touchAt(DrawingSurfaceLocationProvider.MIDDLE)) - TopBarViewInteraction.onTopBarView() - .performOpenMoreOptions() - onView(withText(R.string.menu_save_project)) - .perform(click()) - onView(withId(R.id.pocketpaint_image_name_save_text)) - .perform(replaceText(PROJECT_NAME)) - onView(withText(R.string.save_button_text)) - .perform(click()) - Thread.sleep(THREAD_WAITING_TIME) - pressBack() + insertProjectIntoRecyclerView() + dao.insertProject(project) onView(withId(R.id.pocketpaint_projects_list)).perform( actionOnItemAtPosition( position, @@ -508,12 +421,15 @@ class LandingPageActivityIntegrationTest { ) onView(withText(R.string.menu_project_delete_title)) .perform(click()) - Thread.sleep(THREAD_WAITING_TIME) + onView(isRoot()).perform(waitFor(100)) onView(withId(android.R.id.button1)) - .inRoot(RootMatchers.isDialog()) + .perform(closeSoftKeyboard()) + .perform(scrollTo()) .perform(click()) + onView(isRoot()).perform(waitFor(300)) onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) + onView(isRoot()).perform(waitFor(300)) if (isRecyclerViewEmpty(recyclerViewMatcher)) { onView(withId(R.id.pocketpaint_projects_list)) .check(matches(not(hasDescendant(withId(R.id.iv_pocket_paint_project_thumbnail_image))))) @@ -525,7 +441,7 @@ class LandingPageActivityIntegrationTest { position, hasDescendant( allOf( isAssignableFrom(TextView::class.java), - not(withText(PROJECT_NAME)) + not(withText(project.name)) ) ) ) @@ -548,8 +464,10 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - Thread.sleep(THREAD_WAITING_TIME) + onView(isRoot()).perform(waitFor(1000)) pressBack() + launchActivityRule.launchActivity(intent) + onView(isRoot()).perform(waitFor(1000)) onView(withId(R.id.pocketpaint_projects_list)) .perform(actionOnItemAtPosition(position, click())) onView( @@ -569,6 +487,7 @@ class LandingPageActivityIntegrationTest { @Test fun testImagePreviewAfterProjectInsert() { + val testName = UUID.randomUUID().toString() onView(withId(R.id.pocketpaint_fab_new_image)) .perform(click()) ToolBarViewInteraction.onToolBarView() @@ -580,21 +499,38 @@ class LandingPageActivityIntegrationTest { onView(withText(R.string.menu_save_project)) .perform(click()) onView(withId(R.id.pocketpaint_image_name_save_text)) - .perform(replaceText(PROJECT_NAME)) + .perform(replaceText(testName)) onView(withText(R.string.save_button_text)) .perform(click()) - Thread.sleep(THREAD_WAITING_TIME) + onView(isRoot()).perform(waitFor(1000)) pressBack() + launchActivityRule.launchActivity(intent) + onView(isRoot()).perform(waitFor(300)) onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) val imagesDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString() - val imagePathToFile = imagesDirectory + File.separator + PROJECT_NAME + "." + PNG_IMAGE_ENDING + val imagePathToFile = imagesDirectory + File.separator + testName + "." + PNG_IMAGE_ENDING val imageFile = File(imagePathToFile) val expectedBitmap = BitmapFactory.decodeFile(imageFile.absolutePath) onView(withId(R.id.pocketpaint_image_preview)).check(matches(withBitmap(expectedBitmap))) } + private fun insertProjectIntoRecyclerView() { + runBlocking { + recyclerView = activity.findViewById(R.id.pocketpaint_projects_list) + adapter = ProjectAdapter( + InstrumentationRegistry.getInstrumentation().targetContext, + projectList, + activity.supportFragmentManager + ) + withContext(Main) { + recyclerView.layoutManager = LinearLayoutManager(activity) + recyclerView.adapter = adapter + } + } + } + private fun createTestImageFile(): Uri? { val bitmap = Bitmap.createBitmap(400, 400, Bitmap.Config.ARGB_8888) val contentValues = ContentValues() diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt b/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt index 18ad58fa99..77f37a86d3 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt @@ -15,6 +15,7 @@ import org.catrobat.paintroid.data.local.database.ProjectDatabaseProvider import org.catrobat.paintroid.model.Project class LandingPageActivity : AppCompatActivity() { + companion object { lateinit var projectDB: ProjectDatabase private lateinit var projectsRecyclerView: RecyclerView diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt index 40eb6d4169..9442c6b400 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt @@ -725,11 +725,17 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { FileIO.storeImagePreviewUri = Uri.parse(projectImagePreviewUri) presenterMain.switchBetweenVersions(PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT, false) presenterMain.finishActivity() + launchLandingPageActivity() } else if (!supportFragmentManager.popBackStackImmediate()) { presenterMain.onBackPressed() } } + private fun launchLandingPageActivity() { + val intent = Intent(this, LandingPageActivity::class.java) + startActivity(intent) + } + public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) presenterMain.handleActivityResult(requestCode, resultCode, data) diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt b/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt index 80c25676b8..db9f70b113 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt @@ -28,7 +28,11 @@ import java.text.SimpleDateFormat import java.util.* import kotlin.collections.ArrayList -class ProjectAdapter(val context: Context, var projectList: ArrayList, private val supportFragmentManager: FragmentManager) : RecyclerView.Adapter() { +class ProjectAdapter( + val context: Context, + var projectList: ArrayList, + private val supportFragmentManager: FragmentManager +) : RecyclerView.Adapter() { private var itemClickListener: OnItemClickListener? = null companion object { @@ -163,7 +167,9 @@ class ProjectAdapter(val context: Context, var projectList: ArrayList, } fun removeProject(projectId: Int, position: Int) { - projectList.removeAt(position) + if (projectList.isNotEmpty()) { + projectList.removeAt(position) + } val iterator = projectList.iterator() while (iterator.hasNext()) { val project = iterator.next() From 0c3d0bfb2bf04829eb49cedbf8d0609c86697022 Mon Sep 17 00:00:00 2001 From: Rd Date: Tue, 13 Jun 2023 08:27:04 +0530 Subject: [PATCH 12/52] Trigger From fbfe6ffed1c3141ba722d2a20712b6fd51efcc92 Mon Sep 17 00:00:00 2001 From: Rd Date: Tue, 13 Jun 2023 09:07:44 +0530 Subject: [PATCH 13/52] Trigger 2 From 12cb4555c1e92621c7c78d1055a050b3292fd58e Mon Sep 17 00:00:00 2001 From: Rd Date: Tue, 13 Jun 2023 09:25:11 +0530 Subject: [PATCH 14/52] Fix FAILED:compileDebugAndroidTestKotlin --- .../espresso/LandingPageActivityIntegrationTest.kt | 12 ++++++------ .../paintroid/test/espresso/util/UiInteractions.java | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt index 1270409e05..dffdff325a 100644 --- a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt +++ b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt @@ -59,7 +59,7 @@ import org.catrobat.paintroid.test.espresso.util.DrawingSurfaceLocationProvider import org.catrobat.paintroid.test.espresso.util.UiInteractions.touchAt import org.catrobat.paintroid.test.espresso.util.UiInteractions.waitFor import org.catrobat.paintroid.test.espresso.util.UiMatcher.atPosition -import org.catrobat.paintroid.test.espresso.util.wrappers.DrawingSurfaceInteraction.onDrawingSurfaceView +import org.catrobat.paintroid.test.espresso.util.wrappers.DrawingSurfaceInteraction import org.catrobat.paintroid.test.espresso.util.wrappers.ToolBarViewInteraction import org.catrobat.paintroid.test.espresso.util.wrappers.TopBarViewInteraction import org.catrobat.paintroid.test.utils.ScreenshotOnFailRule @@ -188,7 +188,7 @@ class LandingPageActivityIntegrationTest { val resultOK = Instrumentation.ActivityResult(Activity.RESULT_OK, intent) Intents.intending(hasAction(Intent.ACTION_GET_CONTENT)).respondWith(resultOK) onView(withId(R.id.pocketpaint_fab_load_image)).perform(click()) - onDrawingSurfaceView() + DrawingSurfaceInteraction.onDrawingSurfaceView() .checkPixelColor(Color.BLACK, BitmapLocationProvider.MIDDLE) Intents.release() } @@ -203,7 +203,7 @@ class LandingPageActivityIntegrationTest { fun testProjectInsertedWithProjectName() { onView(withId(R.id.pocketpaint_fab_new_image)) .perform(click()) - onDrawingSurfaceView() + DrawingSurfaceInteraction.onDrawingSurfaceView() .perform(touchAt(DrawingSurfaceLocationProvider.MIDDLE)) TopBarViewInteraction.onTopBarView() .performOpenMoreOptions() @@ -231,7 +231,7 @@ class LandingPageActivityIntegrationTest { val testName = UUID.randomUUID().toString() onView(withId(R.id.pocketpaint_fab_new_image)) .perform(click()) - onDrawingSurfaceView() + DrawingSurfaceInteraction.onDrawingSurfaceView() .perform(touchAt(DrawingSurfaceLocationProvider.MIDDLE)) TopBarViewInteraction.onTopBarView() .performOpenMoreOptions() @@ -454,7 +454,7 @@ class LandingPageActivityIntegrationTest { fun testSavedProjectOpen() { onView(withId(R.id.pocketpaint_fab_new_image)) .perform(click()) - onDrawingSurfaceView() + DrawingSurfaceInteraction.onDrawingSurfaceView() .perform(touchAt(DrawingSurfaceLocationProvider.MIDDLE)) TopBarViewInteraction.onTopBarView() .performOpenMoreOptions() @@ -492,7 +492,7 @@ class LandingPageActivityIntegrationTest { .perform(click()) ToolBarViewInteraction.onToolBarView() .performSelectTool(ToolType.FILL) - onDrawingSurfaceView() + DrawingSurfaceInteraction.onDrawingSurfaceView() .perform(touchAt(DrawingSurfaceLocationProvider.MIDDLE)) TopBarViewInteraction.onTopBarView() .performOpenMoreOptions() diff --git a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/util/UiInteractions.java b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/util/UiInteractions.java index 60bece8b8b..fc871281b1 100644 --- a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/util/UiInteractions.java +++ b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/util/UiInteractions.java @@ -138,8 +138,9 @@ public static ViewAction clickOutside(final Direction direction) { return new float[]{r.left - 50, r.centerY()}; case RIGHT: return new float[]{r.right + 50, r.centerY()}; + default: + return null; } - return null; }, Press.FINGER, 0, 1) ); } From 6dbcc693bcf70a072ba4b56d58c534ddc422acd0 Mon Sep 17 00:00:00 2001 From: Rd Date: Mon, 3 Jul 2023 20:20:52 +0530 Subject: [PATCH 15/52] Refinements on Deletion, Updation, Image Creation --- .idea/codeStyles/Project.xml | 124 ------------------ .../paintroid/test/database/ProjectDaoTest.kt | 17 +++ .../paintroid/adapter/ProjectAdapter.kt | 39 ++++-- .../paintroid/data/local/dao/ProjectDao.kt | 3 + .../paintroid/dialog/ProjectDeleteDialog.kt | 40 +++++- .../catrobat/paintroid/iotasks/SaveImage.kt | 18 ++- 6 files changed, 100 insertions(+), 141 deletions(-) delete mode 100644 .idea/codeStyles/Project.xml diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml deleted file mode 100644 index ab75be245f..0000000000 --- a/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/database/ProjectDaoTest.kt b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/database/ProjectDaoTest.kt index 3d6b766c53..700cc8a14c 100644 --- a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/database/ProjectDaoTest.kt +++ b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/database/ProjectDaoTest.kt @@ -50,6 +50,23 @@ class ProjectDaoTest { assertTrue(allProjects.contains(project)) } + @Test + fun getProject() { + val project = Project( + "name", + "catrobat/path", + 0, + 0, + "0x0", + "CATROBAT", + 0.0, + "paintroid/path", + 1) + dao.insertProject(project) + val insertedProject = dao.getProject(project.name) + assertTrue(insertedProject == project) + } + @Test fun deleteProject() { val project = Project( diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt b/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt index db9f70b113..41d24402a2 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt @@ -1,5 +1,6 @@ package org.catrobat.paintroid.adapter +import android.annotation.SuppressLint import android.content.Context import android.net.Uri import android.os.Build @@ -14,9 +15,13 @@ import android.widget.TextView import androidx.annotation.RequiresApi import androidx.fragment.app.FragmentManager import androidx.recyclerview.widget.RecyclerView +import kotlinx.coroutines.Dispatchers.Main +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext import org.catrobat.paintroid.LandingPageActivity.Companion.imagePreview import org.catrobat.paintroid.LandingPageActivity.Companion.latestProject import org.catrobat.paintroid.R +import org.catrobat.paintroid.common.CATROBAT_IMAGE_ENDING import org.catrobat.paintroid.common.PROJECT_DELETE_DIALOG_FRAGMENT_TAG import org.catrobat.paintroid.common.PROJECT_DETAILS_DIALOG_FRAGMENT_TAG import org.catrobat.paintroid.dialog.ProjectDeleteDialog @@ -57,7 +62,7 @@ class ProjectAdapter( override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { val item = projectList[position] val id = item.id - val name = item.name.substringBefore(".catrobat-image") + val name = item.name.substringBefore(".$CATROBAT_IMAGE_ENDING") val resolution = item.resolution val creationDate = item.creationDate val lastModifiedDate = item.lastModified @@ -104,7 +109,7 @@ class ProjectAdapter( true } R.id.project_delete -> { - val projectDelete = ProjectDeleteDialog(id, name, position) + val projectDelete = ProjectDeleteDialog(id, name, imageUri, position) projectDelete.show(supportFragmentManager, PROJECT_DELETE_DIALOG_FRAGMENT_TAG) true } @@ -133,13 +138,13 @@ class ProjectAdapter( if (it.moveToFirst()) { val columnIndex = it.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA) filePath = it.getString(columnIndex) - file = File(filePath) + file = File(filePath.toString()) return file } } } try { - file = File(uri.path) + file = File(uri.path.toString()) return file } catch (e: IOException) { Log.e(TAG, "Error occurred while accessing the file: ${e.message}") @@ -147,12 +152,19 @@ class ProjectAdapter( return null } + @SuppressLint("NotifyDataSetChanged") fun insertProject(project: Project) { projectList.add(0, project) - imagePreview.setImageURI(Uri.parse(project?.imagePreviewPath)) - notifyItemInserted(0) + runBlocking { + withContext(Main) { + notifyItemInserted(0) + notifyDataSetChanged() + imagePreview.setImageURI(Uri.parse(project.imagePreviewPath)) + } + } } + @SuppressLint("NotifyDataSetChanged") fun updateProject(filename: String, imagePreviewPath: String, projectUri: String, lastModified: Long) { val index = projectList.indexOfFirst { it.name == filename } if (index != -1) { @@ -161,11 +173,20 @@ class ProjectAdapter( imagePreviewPath = imagePreviewPath, lastModified = lastModified, ) - projectList[index] = updatedProject - notifyItemChanged(index) + projectList.removeAt(index) + projectList.add(0, updatedProject) + runBlocking { + withContext(Main) { + notifyItemChanged(index) + notifyDataSetChanged() + imagePreview.setImageDrawable(null) + imagePreview.setImageURI(Uri.parse(imagePreviewPath)) + } + } } } + @SuppressLint("NotifyDataSetChanged") fun removeProject(projectId: Int, position: Int) { if (projectList.isNotEmpty()) { projectList.removeAt(position) @@ -178,6 +199,8 @@ class ProjectAdapter( break } } + notifyItemRemoved(position) + notifyDataSetChanged() if (projectList.isNotEmpty()) { latestProject = projectList[0] imagePreview.setImageURI(Uri.parse(latestProject?.imagePreviewPath)) diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt b/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt index daca90a853..4a4e1570dd 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt @@ -17,6 +17,9 @@ interface ProjectDao { @Query("SELECT * FROM Project ORDER BY lastModified DESC") fun getProjects(): List + @Query("SELECT * FROM Project WHERE name= :name") + fun getProject(name: String): Project + @Query("DELETE FROM Project WHERE id= :id") fun deleteProject(id: Int) diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDeleteDialog.kt b/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDeleteDialog.kt index afdd02bcbf..530c0d617c 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDeleteDialog.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDeleteDialog.kt @@ -2,14 +2,20 @@ package org.catrobat.paintroid.dialog import android.app.AlertDialog import android.app.Dialog +import android.net.Uri +import android.os.Build import android.os.Bundle +import android.os.Environment import androidx.appcompat.app.AppCompatDialogFragment import androidx.core.text.HtmlCompat import org.catrobat.paintroid.LandingPageActivity.Companion.projectAdapter import org.catrobat.paintroid.LandingPageActivity.Companion.projectDB import org.catrobat.paintroid.R +import org.catrobat.paintroid.common.CATROBAT_IMAGE_ENDING +import org.catrobat.paintroid.common.PNG_IMAGE_ENDING +import java.io.File -class ProjectDeleteDialog(private val projectId: Int, private val name: String, private val position: Int) : AppCompatDialogFragment() { +class ProjectDeleteDialog(private val projectId: Int, private val name: String, private val imageUri: Uri?, private val position: Int) : AppCompatDialogFragment() { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val message = getString(R.string.pocketpaint_project_delete_title_dialog, name) @@ -18,10 +24,40 @@ class ProjectDeleteDialog(private val projectId: Int, private val name: String, .setMessage(R.string.pocketpaint_project_delete_message_dialog) .setPositiveButton(R.string.pocketpaint_ok) { _, _ -> projectDB.dao.deleteProject(projectId) + if (imageUri != null) { + deleteProjectFile(name, imageUri) + deleteProjectImage(name, imageUri) + } projectAdapter.removeProject(projectId, position) - projectAdapter.notifyItemRemoved(position) } .setNegativeButton(R.string.pocketpaint_cancel) { _, _ -> dismiss() } .create() } + + private fun deleteProjectFile(name: String, uri: Uri) { + val file = getFile(name, uri, Environment.DIRECTORY_DOWNLOADS, CATROBAT_IMAGE_ENDING) + deleteFile(file) + } + + private fun deleteProjectImage(name: String, uri: Uri) { + val file = getFile(name, uri, Environment.DIRECTORY_PICTURES, PNG_IMAGE_ENDING) + deleteFile(file) + } + + private fun getFile(name: String, uri: Uri, directoryType: String, fileExtension: String): File? { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + val dir = Environment.getExternalStoragePublicDirectory(directoryType) + File(dir, "$name.$fileExtension") + } else { + File(uri.path.toString()) + } + } + + private fun deleteFile(file: File?) { + file?.let { + if (it.exists()) { + it.delete() + } + } + } } diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/SaveImage.kt b/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/SaveImage.kt index 771e108a2a..9fe9a6d23b 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/SaveImage.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/SaveImage.kt @@ -37,6 +37,7 @@ import org.catrobat.paintroid.MainActivity.Companion.projectImagePreviewUri import org.catrobat.paintroid.MainActivity.Companion.projectName import org.catrobat.paintroid.MainActivity.Companion.projectUri import org.catrobat.paintroid.command.serialization.CommandSerializer +import org.catrobat.paintroid.common.CATROBAT_IMAGE_ENDING import org.catrobat.paintroid.common.PNG_IMAGE_ENDING import org.catrobat.paintroid.contract.LayerContracts import org.catrobat.paintroid.model.Project @@ -83,16 +84,18 @@ class SaveImage( bitmap: Bitmap? ): Uri? { val filename = FileIO.defaultFileName - val imagesDirectory = - Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString() - val pathToFile = imagesDirectory + File.separator + filename + "." + PNG_IMAGE_ENDING + val name = filename.removeSuffix(".$CATROBAT_IMAGE_ENDING") + val pngFileName = "$name.$PNG_IMAGE_ENDING" + val imagesDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString() + val pathToFile = imagesDirectory + File.separator + pngFileName val imageFile = File(pathToFile) return if (saveImageOptions.imagePreviewUri == null || !imageFile.exists()) { - val imagePreviewFilename = filename.replace(".catrobat-image", ".png") - val imageUri = FileIO.saveBitmapToFile(imagePreviewFilename, bitmap, callback.contentResolver, context) + val imageUri = FileIO.saveBitmapToFile(pngFileName, bitmap, callback.contentResolver, context) imageUri } else { - saveImageOptions.imagePreviewUri?.let { FileIO.saveBitmapToUri(it, bitmap, context) } + saveImageOptions.imagePreviewUri?.let { + FileIO.saveBitmapToUri(it, bitmap, context) + } } } @@ -219,7 +222,8 @@ class SaveImage( imagePreviewPath.toString() ) projectDB.dao.insertProject(project) - projectAdapter.insertProject(project) + val insertedProject = projectDB.dao.getProject(filename) + projectAdapter.insertProject(insertedProject) projectName = filename projectUri = currentUri.toString() projectImagePreviewUri = imagePreviewPath.toString() From 4bb405afd6a581b45dcfbbc4991446238063318e Mon Sep 17 00:00:00 2001 From: Rd Date: Thu, 13 Jul 2023 20:28:53 +0530 Subject: [PATCH 16/52] Fixed Crash on returning to Landing Page --- Paintroid/build.gradle | 2 +- .../org/catrobat/paintroid/presenter/MainActivityPresenter.kt | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Paintroid/build.gradle b/Paintroid/build.gradle index 2e1745bbf8..1c2b512b56 100644 --- a/Paintroid/build.gradle +++ b/Paintroid/build.gradle @@ -153,7 +153,7 @@ dependencies { testImplementation "androidx.test:core-ktx:1.4.0" implementation 'com.android.support.test.espresso:espresso-idling-resource:3.1.0' - def room_version = "2.3.0" + def room_version = "2.4.0-alpha03" implementation "androidx.room:room-ktx:$room_version" kapt "androidx.room:room-compiler:$room_version" } diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt b/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt index e210576e15..6c681fa9de 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt @@ -47,6 +47,7 @@ import androidx.core.view.GravityCompat import androidx.test.espresso.idling.CountingIdlingResource import org.catrobat.paintroid.FileIO import org.catrobat.paintroid.MainActivity +import org.catrobat.paintroid.MainActivity.Companion.projectName import org.catrobat.paintroid.R import org.catrobat.paintroid.UserPreferences import org.catrobat.paintroid.colorpicker.ColorHistory @@ -334,6 +335,7 @@ open class MainActivityPresenter( } override fun onNewImage() { + projectName = null val metrics = view.displayMetrics resetPerspectiveAfterNextCommand = true model.savedPictureUri = null From 523e9ab500bd11385224a2c9540f6d1afcfe360c Mon Sep 17 00:00:00 2001 From: Rd Date: Thu, 13 Jul 2023 22:10:41 +0530 Subject: [PATCH 17/52] testImagePreviewAfterProjectInsert bitmap check --- .../LandingPageActivityIntegrationTest.kt | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt index dffdff325a..a694723135 100644 --- a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt +++ b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt @@ -17,7 +17,6 @@ import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView import androidx.appcompat.widget.Toolbar -import androidx.core.graphics.drawable.toBitmap import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.room.Room @@ -66,9 +65,7 @@ import org.catrobat.paintroid.test.utils.ScreenshotOnFailRule import org.catrobat.paintroid.tools.ToolType import org.hamcrest.CoreMatchers.allOf import org.hamcrest.CoreMatchers.not -import org.hamcrest.Description import org.hamcrest.Matcher -import org.hamcrest.TypeSafeMatcher import org.junit.Test import org.junit.Rule import org.junit.After @@ -512,8 +509,24 @@ class LandingPageActivityIntegrationTest { Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString() val imagePathToFile = imagesDirectory + File.separator + testName + "." + PNG_IMAGE_ENDING val imageFile = File(imagePathToFile) - val expectedBitmap = BitmapFactory.decodeFile(imageFile.absolutePath) - onView(withId(R.id.pocketpaint_image_preview)).check(matches(withBitmap(expectedBitmap))) + onView(withId(R.id.pocketpaint_image_preview)).perform( + object : ViewAction { + override fun getConstraints(): Matcher { + return isAssignableFrom(ImageView::class.java) + } + + override fun getDescription(): String { + return "Check if image is correctly displayed" + } + + override fun perform(uiController: UiController?, view: View?) { + val imageView = view as? ImageView + val expectedBitmap = BitmapFactory.decodeFile(imageFile.absolutePath) + val actualBitmap = (imageView?.drawable as? BitmapDrawable)?.bitmap + actualBitmap?.sameAs(expectedBitmap) + } + } + ) } private fun insertProjectIntoRecyclerView() { @@ -554,20 +567,6 @@ class LandingPageActivityIntegrationTest { return imageUri } - private fun withBitmap(expectedBitmap: Bitmap): Matcher { - return object : TypeSafeMatcher() { - override fun describeTo(description: Description) { - description.appendText("with bitmap: ") - } - - override fun matchesSafely(view: View): Boolean { - if (view !is ImageView) return false - val actualBitmap = view.drawable.toBitmap() - return expectedBitmap.sameAs(actualBitmap) - } - } - } - private fun isRecyclerViewEmpty(recyclerViewMatcher: Matcher): Boolean { val itemCount = getRecyclerViewItemCount(recyclerViewMatcher) return itemCount == 0 From e417b813a3f6ab14cb13a8362eae6c22a4b0edfc Mon Sep 17 00:00:00 2001 From: Rd Date: Sat, 15 Jul 2023 12:58:47 +0530 Subject: [PATCH 18/52] more options integration test - swipe up --- .../paintroid/test/espresso/MoreOptionsIntegrationTest.kt | 3 +++ .../espresso/util/wrappers/OptionsMenuViewInteraction.kt | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/MoreOptionsIntegrationTest.kt b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/MoreOptionsIntegrationTest.kt index 7beb1c5c94..b8ebdfa762 100644 --- a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/MoreOptionsIntegrationTest.kt +++ b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/MoreOptionsIntegrationTest.kt @@ -256,12 +256,14 @@ class MoreOptionsIntegrationTest { @Test fun testOnOffSmoothOptions() { + OptionsMenuViewInteraction.onOptionsMenu().swipeUp() Espresso.onView(withText(R.string.menu_advanced)).perform(ViewActions.click()) Espresso.onView(withId(R.id.pocketpaint_smoothing)).perform(ViewActions.click()) Espresso.onView(withText(R.string.pocketpaint_ok)).perform(ViewActions.click()) Assert.assertFalse("Smoothing is still on!", AdvancedSettingsAlgorithms.smoothing) TopBarViewInteraction.onTopBarView() .performOpenMoreOptions() + OptionsMenuViewInteraction.onOptionsMenu().swipeUp() Espresso.onView(withText(R.string.menu_advanced)).perform(ViewActions.click()) Espresso.onView(withId(R.id.pocketpaint_smoothing)).perform(ViewActions.click()) Espresso.onView(withText(R.string.pocketpaint_ok)).perform(ViewActions.click()) @@ -270,6 +272,7 @@ class MoreOptionsIntegrationTest { @Test fun testNoChangeOnSmoothingWhenCancel() { + OptionsMenuViewInteraction.onOptionsMenu().swipeUp() Espresso.onView(withText(R.string.menu_advanced)).perform(ViewActions.click()) Espresso.onView(withId(R.id.pocketpaint_smoothing)).perform(ViewActions.click()) Espresso.onView(withText(R.string.cancel_button_text)).perform(ViewActions.click()) diff --git a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/util/wrappers/OptionsMenuViewInteraction.kt b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/util/wrappers/OptionsMenuViewInteraction.kt index f683fedf96..07fcf8f7d8 100644 --- a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/util/wrappers/OptionsMenuViewInteraction.kt +++ b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/util/wrappers/OptionsMenuViewInteraction.kt @@ -4,6 +4,7 @@ import androidx.annotation.StringRes import androidx.appcompat.widget.MenuPopupWindow.MenuDropDownListView import androidx.test.espresso.Espresso import androidx.test.espresso.ViewInteraction +import androidx.test.espresso.action.ViewActions import androidx.test.espresso.assertion.ViewAssertions import org.catrobat.paintroid.test.espresso.util.UiMatcher import org.hamcrest.CoreMatchers @@ -28,6 +29,11 @@ class OptionsMenuViewInteraction private constructor() { return this } + fun swipeUp(): OptionsMenuViewInteraction { + optionsMenu.perform(ViewActions.swipeUp()) + return this + } + companion object { lateinit var optionsMenu: ViewInteraction fun onOptionsMenu(): OptionsMenuViewInteraction = OptionsMenuViewInteraction() From f985faea659ed0ae535ccdd8b567af8e63a0241c Mon Sep 17 00:00:00 2001 From: Rd Date: Sat, 15 Jul 2023 13:40:11 +0530 Subject: [PATCH 19/52] Trigger Build From 4fae7cb6370243f09e88d5fccd353cecca6f602c Mon Sep 17 00:00:00 2001 From: Rd Date: Sat, 15 Jul 2023 16:11:02 +0530 Subject: [PATCH 20/52] Room version roll back --- Paintroid/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Paintroid/build.gradle b/Paintroid/build.gradle index 1c2b512b56..2e1745bbf8 100644 --- a/Paintroid/build.gradle +++ b/Paintroid/build.gradle @@ -153,7 +153,7 @@ dependencies { testImplementation "androidx.test:core-ktx:1.4.0" implementation 'com.android.support.test.espresso:espresso-idling-resource:3.1.0' - def room_version = "2.4.0-alpha03" + def room_version = "2.3.0" implementation "androidx.room:room-ktx:$room_version" kapt "androidx.room:room-compiler:$room_version" } From 0a81db3182d4f8ff1e4c3af8ed7719bfcd7b0ec4 Mon Sep 17 00:00:00 2001 From: Rd Date: Sat, 22 Jul 2023 19:10:12 +0530 Subject: [PATCH 21/52] Fixed Saving Projects with API 28 error --- .../catrobat/paintroid/LandingPageActivity.kt | 29 ++++++++++++------- .../org/catrobat/paintroid/MainActivity.kt | 27 ++++++++++++----- .../paintroid/adapter/ProjectAdapter.kt | 1 + .../catrobat/paintroid/common/Constants.kt | 7 +++++ .../catrobat/paintroid/iotasks/LoadImage.kt | 9 ++++-- .../presenter/MainActivityPresenter.kt | 1 + 6 files changed, 53 insertions(+), 21 deletions(-) diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt b/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt index 77f37a86d3..9b8d79a6e0 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt @@ -10,6 +10,13 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.google.android.material.floatingactionbutton.FloatingActionButton import org.catrobat.paintroid.adapter.ProjectAdapter +import org.catrobat.paintroid.common.LOAD_IMAGE +import org.catrobat.paintroid.common.LOAD_NEW_IMAGE +import org.catrobat.paintroid.common.LOAD_PROJECT +import org.catrobat.paintroid.common.NEW_PROJECT +import org.catrobat.paintroid.common.PROJECT_NAME +import org.catrobat.paintroid.common.PROJECT_URI +import org.catrobat.paintroid.common.PROJECT_IMAGE_PREVIEW_URI import org.catrobat.paintroid.data.local.database.ProjectDatabase import org.catrobat.paintroid.data.local.database.ProjectDatabaseProvider import org.catrobat.paintroid.model.Project @@ -49,15 +56,15 @@ class LandingPageActivity : AppCompatActivity() { latestProject = allProjects.firstOrNull() if (allProjects.isNotEmpty()) { val loadProjectIntent = Intent(applicationContext, MainActivity::class.java).apply { - putExtra(PROJECT_ACTION, "load_project") - putExtra("PROJECT_URI", latestProject?.path) - putExtra("PROJECT_NAME", latestProject?.name) - putExtra("PROJECT_IMAGE_PREVIEW_URI", latestProject?.imagePreviewPath) + putExtra(PROJECT_ACTION, LOAD_PROJECT) + putExtra(PROJECT_URI, latestProject?.path) + putExtra(PROJECT_NAME, latestProject?.name) + putExtra(PROJECT_IMAGE_PREVIEW_URI, latestProject?.imagePreviewPath) } startActivity(loadProjectIntent) } else { val newProjectIntent = Intent(this, MainActivity::class.java).apply { - putExtra(PROJECT_ACTION, "new_project") + putExtra(PROJECT_ACTION, NEW_PROJECT) } startActivity(newProjectIntent) } @@ -70,13 +77,13 @@ class LandingPageActivity : AppCompatActivity() { val newImage = findViewById(R.id.pocketpaint_fab_new_image) newImage.setOnClickListener { - mainActivityIntent.putExtra(PROJECT_ACTION, "new_image") + mainActivityIntent.putExtra(PROJECT_ACTION, LOAD_NEW_IMAGE) startActivity(mainActivityIntent) } val loadImage = findViewById(R.id.pocketpaint_fab_load_image) loadImage.setOnClickListener { - mainActivityIntent.putExtra(PROJECT_ACTION, "load_image") + mainActivityIntent.putExtra(PROJECT_ACTION, LOAD_IMAGE) startActivity(mainActivityIntent) } } @@ -101,10 +108,10 @@ class LandingPageActivity : AppCompatActivity() { projectImagePreviewUri: String ) { val loadProjectIntent = Intent(applicationContext, MainActivity::class.java).apply { - putExtra(PROJECT_ACTION, "load_project") - putExtra("PROJECT_URI", projectUri) - putExtra("PROJECT_NAME", projectName) - putExtra("PROJECT_IMAGE_PREVIEW_URI", projectImagePreviewUri) + putExtra(PROJECT_ACTION, LOAD_PROJECT) + putExtra(PROJECT_URI, projectUri) + putExtra(PROJECT_NAME, projectName) + putExtra(PROJECT_IMAGE_PREVIEW_URI, projectImagePreviewUri) } startActivity(loadProjectIntent) } diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt index 9442c6b400..c49b4ceb3e 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt @@ -70,6 +70,14 @@ import org.catrobat.paintroid.common.PAINTROID_PICTURE_PATH import org.catrobat.paintroid.common.REQUEST_CODE_LOAD_PICTURE import org.catrobat.paintroid.common.PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT import org.catrobat.paintroid.common.CommonFactory +import org.catrobat.paintroid.common.CATROBAT_IMAGE_ENDING +import org.catrobat.paintroid.common.NEW_PROJECT +import org.catrobat.paintroid.common.LOAD_NEW_IMAGE +import org.catrobat.paintroid.common.LOAD_IMAGE +import org.catrobat.paintroid.common.LOAD_PROJECT +import org.catrobat.paintroid.common.PROJECT_NAME +import org.catrobat.paintroid.common.PROJECT_URI +import org.catrobat.paintroid.common.PROJECT_IMAGE_PREVIEW_URI import org.catrobat.paintroid.contract.LayerContracts import org.catrobat.paintroid.contract.MainActivityContracts import org.catrobat.paintroid.contract.MainActivityContracts.MainView @@ -317,10 +325,10 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { } savedInstanceState == null -> when (receivedIntent.getStringExtra(PROJECT_ACTION)) { - "new_project" -> presenterMain.onNewImage() - "new_image" -> presenterMain.onNewImage() - "load_image" -> presenterMain.replaceImageClicked() - "load_project" -> loadProject(receivedIntent) + NEW_PROJECT -> presenterMain.onNewImage() + LOAD_NEW_IMAGE -> presenterMain.onNewImage() + LOAD_IMAGE -> presenterMain.replaceImageClicked() + LOAD_PROJECT -> loadProject(receivedIntent) else -> { val intent = intent val picturePath = intent.getStringExtra(PAINTROID_PICTURE_PATH) @@ -372,11 +380,11 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { } private fun loadProject(receivedIntent: Intent) { - projectName = receivedIntent.getStringExtra("PROJECT_NAME") - projectUri = receivedIntent.getStringExtra("PROJECT_URI") - projectImagePreviewUri = receivedIntent.getStringExtra("PROJECT_IMAGE_PREVIEW_URI") + projectName = receivedIntent.getStringExtra(PROJECT_NAME) + projectUri = receivedIntent.getStringExtra(PROJECT_URI) + projectImagePreviewUri = receivedIntent.getStringExtra(PROJECT_IMAGE_PREVIEW_URI) val projectNameText = findViewById(R.id.pocketpaint_toolbar) - projectNameText.subtitle = projectName?.substringBefore(".catrobat-image") + projectNameText.subtitle = projectName?.substringBefore(".$CATROBAT_IMAGE_ENDING") presenterMain.loadScaledImage(projectUri?.toUri(), REQUEST_CODE_LOAD_PICTURE) } @@ -427,10 +435,12 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { FileIO.checkFileExists(FileIO.FileType.CATROBAT, it, this.contentResolver) } == true) { + FileIO.filename = projectName!!.removeSuffix(".$CATROBAT_IMAGE_ENDING") FileIO.storeImageUri = Uri.parse(projectUri) FileIO.storeImagePreviewUri = Uri.parse(projectImagePreviewUri) presenterMain.switchBetweenVersions(PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT, false) presenterMain.finishActivity() + projectName = null } else { presenterMain.backToPocketCodeClicked() } @@ -726,6 +736,7 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { presenterMain.switchBetweenVersions(PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT, false) presenterMain.finishActivity() launchLandingPageActivity() + projectName = null } else if (!supportFragmentManager.popBackStackImmediate()) { presenterMain.onBackPressed() } diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt b/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt index 41d24402a2..40abf58989 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt @@ -90,6 +90,7 @@ class ProjectAdapter( } if (imageUri != null) { + holder.itemImageView.setImageDrawable(null) holder.itemImageView.setImageURI(Uri.parse(item.imagePreviewPath)) } else { holder.itemImageView.setImageResource(R.drawable.pocketpaint_logo_small) diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/common/Constants.kt b/Paintroid/src/main/java/org/catrobat/paintroid/common/Constants.kt index b1a2eddaf0..2e041abb75 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/common/Constants.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/common/Constants.kt @@ -27,6 +27,13 @@ const val TEMP_PICTURE_NAME = "catroidTemp" const val MEDIA_GALLEY_URL = "https://share.catrob.at/pocketcode/media-library/looks" const val PROJECT_DETAILS_DIALOG_FRAGMENT_TAG = "projectdetailsfragment" const val PROJECT_DELETE_DIALOG_FRAGMENT_TAG = "projectdeletefragment" +const val PROJECT_URI = "projecturi" +const val PROJECT_NAME = "projectname" +const val PROJECT_IMAGE_PREVIEW_URI = "projectimagepreviewuri" +const val NEW_PROJECT = "newproject" +const val LOAD_NEW_IMAGE = "loadnewimage" +const val LOAD_IMAGE = "loadimage" +const val LOAD_PROJECT = "loadproject" const val ABOUT_DIALOG_FRAGMENT_TAG = "aboutdialogfragment" const val LIKE_US_DIALOG_FRAGMENT_TAG = "likeusdialogfragment" const val RATE_US_DIALOG_FRAGMENT_TAG = "rateusdialogfragment" diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/LoadImage.kt b/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/LoadImage.kt index 45840c45c6..7552485087 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/LoadImage.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/LoadImage.kt @@ -33,6 +33,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.catrobat.paintroid.FileIO import org.catrobat.paintroid.command.serialization.CommandSerializer +import org.catrobat.paintroid.common.CATROBAT_IMAGE_ENDING class LoadImage( callback: LoadImageCallback, @@ -52,8 +53,12 @@ class LoadImage( resolver.getType(uri) } else { val fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri.toString()) - MimeTypeMap.getSingleton() - .getMimeTypeFromExtension(fileExtension.toLowerCase(Locale.US)) + if (fileExtension.equals(CATROBAT_IMAGE_ENDING)) { + "application/octet-stream" + } else { + MimeTypeMap.getSingleton() + .getMimeTypeFromExtension(fileExtension.toLowerCase(Locale.US)) + } } private fun getBitmapReturnValue( diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt b/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt index 6c681fa9de..28aba2af26 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt @@ -155,6 +155,7 @@ open class MainActivityPresenter( var toolOptionsViewWasShown = false override fun replaceImageClicked() { + projectName = null checkIfClippingToolNeedsAdjustment() switchBetweenVersions(PERMISSION_REQUEST_CODE_REPLACE_PICTURE, false) setFirstCheckBoxInLayerMenu() From 7f7cd6d37426482fd4c5557bb3a95a9136508035 Mon Sep 17 00:00:00 2001 From: Rd Date: Sat, 22 Jul 2023 19:18:27 +0530 Subject: [PATCH 22/52] restored Project.xml file --- .idea/codeStyles/Project.xml | 124 +++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 .idea/codeStyles/Project.xml diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000000..32539daf87 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,124 @@ + + + + + + + + + + \ No newline at end of file From 1f46dcf1d6977c7171b0d2f799ed3ba3a699a056 Mon Sep 17 00:00:00 2001 From: Rd Date: Sun, 30 Jul 2023 18:57:01 +0530 Subject: [PATCH 23/52] Fixed Saving Project with API 21, 22, 23, 24 error --- .../main/java/org/catrobat/paintroid/MainActivity.kt | 10 ++++++++++ .../command/serialization/CommandSerializer.kt | 11 ++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt index c49b4ceb3e..fa4b8b6d04 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt @@ -54,6 +54,7 @@ import com.google.android.material.navigation.NavigationView import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.launch import org.catrobat.paintroid.LandingPageActivity.Companion.PROJECT_ACTION import org.catrobat.paintroid.colorpicker.ColorHistory @@ -202,6 +203,7 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { var projectName: String? = null var projectUri: String? = null var projectImagePreviewUri: String? = null + var downloadManagerIdRemoved: Boolean = true } override val presenter: MainActivityContracts.Presenter @@ -435,10 +437,18 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { FileIO.checkFileExists(FileIO.FileType.CATROBAT, it, this.contentResolver) } == true) { + downloadManagerIdRemoved = true FileIO.filename = projectName!!.removeSuffix(".$CATROBAT_IMAGE_ENDING") FileIO.storeImageUri = Uri.parse(projectUri) FileIO.storeImagePreviewUri = Uri.parse(projectImagePreviewUri) presenterMain.switchBetweenVersions(PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT, false) + if (VERSION.SDK_INT <= Build.VERSION_CODES.M) { + while (downloadManagerIdRemoved) { + runBlocking { + delay(100) + } + } + } presenterMain.finishActivity() projectName = null } else { diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/command/serialization/CommandSerializer.kt b/Paintroid/src/main/java/org/catrobat/paintroid/command/serialization/CommandSerializer.kt index 4db959c653..2584fa3007 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/command/serialization/CommandSerializer.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/command/serialization/CommandSerializer.kt @@ -35,6 +35,9 @@ import android.provider.MediaStore import com.esotericsoftware.kryo.Kryo import com.esotericsoftware.kryo.io.Input import com.esotericsoftware.kryo.io.Output +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import org.catrobat.paintroid.MainActivity.Companion.downloadManagerIdRemoved import org.catrobat.paintroid.colorpicker.ColorHistory import org.catrobat.paintroid.command.Command import org.catrobat.paintroid.command.CommandManager @@ -211,7 +214,13 @@ open class CommandSerializer(private val activityContext: Context, private val c val id = sharedPreferences.getLong(uri.path, -1) if (id > -1) { val downloadManager = OpenRasterFileFormatConversion.mainActivity.baseContext.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager - downloadManager.remove(id) + runBlocking { + downloadManager.remove(id) + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) { + delay(1000) + downloadManagerIdRemoved = false + } + } } if (!isDeleted) { throw AssertionError("No file to delete was found!") From 0ac5146a59837b22cdbf896c0778e4d395ecb26e Mon Sep 17 00:00:00 2001 From: Rd Date: Sun, 30 Jul 2023 19:24:08 +0530 Subject: [PATCH 24/52] Fixed Static warnings --- Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt | 3 ++- .../paintroid/command/serialization/CommandSerializer.kt | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt index fa4b8b6d04..15b93d0a2b 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt @@ -204,6 +204,7 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { var projectUri: String? = null var projectImagePreviewUri: String? = null var downloadManagerIdRemoved: Boolean = true + const val DOWNLOAD_MANAGER_ID_DELAY = 100L } override val presenter: MainActivityContracts.Presenter @@ -445,7 +446,7 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { if (VERSION.SDK_INT <= Build.VERSION_CODES.M) { while (downloadManagerIdRemoved) { runBlocking { - delay(100) + delay(DOWNLOAD_MANAGER_ID_DELAY) } } } diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/command/serialization/CommandSerializer.kt b/Paintroid/src/main/java/org/catrobat/paintroid/command/serialization/CommandSerializer.kt index 2584fa3007..d1f2cd2b83 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/command/serialization/CommandSerializer.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/command/serialization/CommandSerializer.kt @@ -87,6 +87,7 @@ open class CommandSerializer(private val activityContext: Context, private val c companion object { const val CURRENT_IMAGE_VERSION = 2 const val MAGIC_VALUE = "CATROBAT" + const val DOWNLOAD_MANAGER_DELAY_TIME = 1000L } val kryo = Kryo() @@ -217,7 +218,7 @@ open class CommandSerializer(private val activityContext: Context, private val c runBlocking { downloadManager.remove(id) if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) { - delay(1000) + delay(DOWNLOAD_MANAGER_DELAY_TIME) downloadManagerIdRemoved = false } } From 0cca7948a5434792deb443001dcf9f70b99adf91 Mon Sep 17 00:00:00 2001 From: Rd Date: Sun, 30 Jul 2023 20:02:38 +0530 Subject: [PATCH 25/52] Trigger after config error From dca4ca3fee000a66279b257f4b037e1719724d50 Mon Sep 17 00:00:00 2001 From: Rd Date: Sun, 30 Jul 2023 20:23:40 +0530 Subject: [PATCH 26/52] Trigger after thread idle From 777ba14a6832ee49797de7614f54dfad98edcdbf Mon Sep 17 00:00:00 2001 From: Rd Date: Sun, 30 Jul 2023 20:29:00 +0530 Subject: [PATCH 27/52] Trigger From 98cb404dfea102c1d8e2dc7e0da53008054f7729 Mon Sep 17 00:00:00 2001 From: Rd Date: Sun, 30 Jul 2023 20:41:11 +0530 Subject: [PATCH 28/52] Trigger From 415efbb87cb40027ccfe30eb98dfa9e6f952d145 Mon Sep 17 00:00:00 2001 From: Rd Date: Fri, 2 Jun 2023 12:16:50 +0530 Subject: [PATCH 29/52] Landing Page Design --- Paintroid/src/main/AndroidManifest.xml | 3 ++ .../catrobat/paintroid/LandingPageActivity.kt | 25 +++++++++++ .../drawable/ic_pocketpaint_edit_circle.xml | 44 +++++++++++++++++++ .../activity_pocketpaint_landing_page.xml | 42 ++++++++++++++++++ ...ocketpaint_layout_landing_page_top_bar.xml | 37 ++++++++++++++++ app/src/main/AndroidManifest.xml | 6 ++- 6 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt create mode 100644 Paintroid/src/main/res/drawable/ic_pocketpaint_edit_circle.xml create mode 100644 Paintroid/src/main/res/layout/activity_pocketpaint_landing_page.xml create mode 100644 Paintroid/src/main/res/layout/pocketpaint_layout_landing_page_top_bar.xml diff --git a/Paintroid/src/main/AndroidManifest.xml b/Paintroid/src/main/AndroidManifest.xml index 416ef07bbc..938b412f29 100644 --- a/Paintroid/src/main/AndroidManifest.xml +++ b/Paintroid/src/main/AndroidManifest.xml @@ -42,6 +42,9 @@ android:resource="@xml/filepaths"/> + (R.id.pocketpaint_image_preview) + val editCircleIcon = findViewById(R.id.pocketpaint_image_edit_circle) + + val imagePreviewClickListener = View.OnClickListener { + val intent = Intent(this, MainActivity::class.java) + startActivity(intent) + } + + imagePreview.setOnClickListener(imagePreviewClickListener) + editCircleIcon.setOnClickListener(imagePreviewClickListener) + } +} \ No newline at end of file diff --git a/Paintroid/src/main/res/drawable/ic_pocketpaint_edit_circle.xml b/Paintroid/src/main/res/drawable/ic_pocketpaint_edit_circle.xml new file mode 100644 index 0000000000..76fffdea87 --- /dev/null +++ b/Paintroid/src/main/res/drawable/ic_pocketpaint_edit_circle.xml @@ -0,0 +1,44 @@ + + + + + + + \ No newline at end of file diff --git a/Paintroid/src/main/res/layout/activity_pocketpaint_landing_page.xml b/Paintroid/src/main/res/layout/activity_pocketpaint_landing_page.xml new file mode 100644 index 0000000000..7543274caf --- /dev/null +++ b/Paintroid/src/main/res/layout/activity_pocketpaint_landing_page.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Paintroid/src/main/res/layout/pocketpaint_layout_landing_page_top_bar.xml b/Paintroid/src/main/res/layout/pocketpaint_layout_landing_page_top_bar.xml new file mode 100644 index 0000000000..9acbed0dfa --- /dev/null +++ b/Paintroid/src/main/res/layout/pocketpaint_layout_landing_page_top_bar.xml @@ -0,0 +1,37 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4ffe27c5a3..e6e6bdf8bc 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -39,12 +39,16 @@ + + From fd4a97a50a039b62c0bb3d8fea11e9115aacdc08 Mon Sep 17 00:00:00 2001 From: Rd Date: Sun, 4 Jun 2023 09:04:35 +0530 Subject: [PATCH 30/52] Room Database Declaration --- Paintroid/build.gradle | 5 ++++ .../catrobat/paintroid/LandingPageActivity.kt | 8 ++++++ .../paintroid/data/local/dao/ProjectDao.kt | 25 +++++++++++++++++++ .../data/local/database/ProjectDatabase.kt | 15 +++++++++++ .../local/database/ProjectDatabaseProvider.kt | 20 +++++++++++++++ .../org/catrobat/paintroid/model/Project.kt | 18 +++++++++++++ .../tools/implementation/BaseTool.kt | 1 - .../paintroid/ui/tools/BrushToolView.kt | 1 + .../ui/tools/DefaultBrushToolOptionsView.kt | 1 + .../ui/tools/DefaultSmudgeToolOptionsView.kt | 1 + build.gradle | 2 +- 11 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt create mode 100644 Paintroid/src/main/java/org/catrobat/paintroid/data/local/database/ProjectDatabase.kt create mode 100644 Paintroid/src/main/java/org/catrobat/paintroid/data/local/database/ProjectDatabaseProvider.kt create mode 100644 Paintroid/src/main/java/org/catrobat/paintroid/model/Project.kt diff --git a/Paintroid/build.gradle b/Paintroid/build.gradle index 3bc0faf935..2e1745bbf8 100644 --- a/Paintroid/build.gradle +++ b/Paintroid/build.gradle @@ -21,6 +21,7 @@ apply plugin: 'com.android.library' apply plugin: 'com.hiya.jacoco-android' apply plugin: 'com.getkeepsafe.dexcount' apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' apply plugin: 'org.catrobat.gradle.androidemulators' apply plugin: 'maven-publish' @@ -151,6 +152,10 @@ dependencies { androidTestImplementation "androidx.test.uiautomator:uiautomator:2.2.0" testImplementation "androidx.test:core-ktx:1.4.0" implementation 'com.android.support.test.espresso:espresso-idling-resource:3.1.0' + + def room_version = "2.3.0" + implementation "androidx.room:room-ktx:$room_version" + kapt "androidx.room:room-compiler:$room_version" } tasks.withType(Javadoc).all { diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt b/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt index 62f943ea57..a90348d275 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt @@ -5,12 +5,20 @@ import android.os.Bundle import android.view.View import android.widget.ImageView import androidx.appcompat.app.AppCompatActivity +import org.catrobat.paintroid.data.local.database.ProjectDatabase +import org.catrobat.paintroid.data.local.database.ProjectDatabaseProvider class LandingPageActivity: AppCompatActivity() { + companion object { + lateinit var projectDB: ProjectDatabase + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_pocketpaint_landing_page) + projectDB = ProjectDatabaseProvider.getDatabase(applicationContext) + val imagePreview = findViewById(R.id.pocketpaint_image_preview) val editCircleIcon = findViewById(R.id.pocketpaint_image_edit_circle) diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt b/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt new file mode 100644 index 0000000000..ac9c72d340 --- /dev/null +++ b/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt @@ -0,0 +1,25 @@ +package org.catrobat.paintroid.data.local.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.Query +import org.catrobat.paintroid.model.Project + +@Dao +interface ProjectDao { + + @Insert + fun insertProject(project: Project) + + @Query("UPDATE Project SET path= :projectUri, imagePreviewPath= :imagePreviewPath WHERE name= :name") + fun updateProjectUri(name: String, imagePreviewPath: String, projectUri: String) + + @Query("SELECT * FROM Project ORDER BY lastModified DESC") + fun getProjects(): List + + @Query("DELETE FROM Project WHERE id= :id") + fun deleteProject(id: Int) + + @Query("DELETE FROM Project") + fun deleteAllProjects() +} \ No newline at end of file diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/data/local/database/ProjectDatabase.kt b/Paintroid/src/main/java/org/catrobat/paintroid/data/local/database/ProjectDatabase.kt new file mode 100644 index 0000000000..929fa5d019 --- /dev/null +++ b/Paintroid/src/main/java/org/catrobat/paintroid/data/local/database/ProjectDatabase.kt @@ -0,0 +1,15 @@ +package org.catrobat.paintroid.data.local.database + +import androidx.room.Database +import androidx.room.RoomDatabase +import org.catrobat.paintroid.data.local.dao.ProjectDao +import org.catrobat.paintroid.model.Project + +@Database( + entities = [Project::class], + version = 1 +) +abstract class ProjectDatabase: RoomDatabase() { + + abstract val dao: ProjectDao +} \ No newline at end of file diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/data/local/database/ProjectDatabaseProvider.kt b/Paintroid/src/main/java/org/catrobat/paintroid/data/local/database/ProjectDatabaseProvider.kt new file mode 100644 index 0000000000..8698cb60ac --- /dev/null +++ b/Paintroid/src/main/java/org/catrobat/paintroid/data/local/database/ProjectDatabaseProvider.kt @@ -0,0 +1,20 @@ +package org.catrobat.paintroid.data.local.database + +import android.content.Context +import androidx.room.Room + +object ProjectDatabaseProvider { + var projectDatabase: ProjectDatabase? = null + + fun getDatabase(context: Context): ProjectDatabase{ + return projectDatabase?: synchronized(this){ + projectDatabase ?: buildDatabase(context).also { projectDatabase = it } + } + } + + fun buildDatabase(context: Context): ProjectDatabase{ + return Room.databaseBuilder(context, ProjectDatabase::class.java, "projects.db") + .allowMainThreadQueries() + .build() + } +} \ No newline at end of file diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/model/Project.kt b/Paintroid/src/main/java/org/catrobat/paintroid/model/Project.kt new file mode 100644 index 0000000000..1beed5934a --- /dev/null +++ b/Paintroid/src/main/java/org/catrobat/paintroid/model/Project.kt @@ -0,0 +1,18 @@ +package org.catrobat.paintroid.model + +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity +data class Project( + var name: String, + val path: String, + var lastModified: String, + val creationDate: String, + val resolution: String, + val format: String, + val size: Int, + val imagePreviewPath: String, + @PrimaryKey(autoGenerate = true) + val id: Int = 0 +) \ No newline at end of file diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/BaseTool.kt b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/BaseTool.kt index f42992ef91..156d3c2aa3 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/BaseTool.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/BaseTool.kt @@ -39,7 +39,6 @@ import org.catrobat.paintroid.tools.common.ScrollBehavior import org.catrobat.paintroid.tools.options.ToolOptionsViewController abstract class BaseTool( - @JvmField open var contextCallback: ContextCallback, @JvmField var toolOptionsViewController: ToolOptionsViewController, @JvmField diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/ui/tools/BrushToolView.kt b/Paintroid/src/main/java/org/catrobat/paintroid/ui/tools/BrushToolView.kt index a7978223af..cdfb080742 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/ui/tools/BrushToolView.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/ui/tools/BrushToolView.kt @@ -124,6 +124,7 @@ class BrushToolView : View, BrushToolPreview { when (callback?.toolType) { ToolType.BRUSH, ToolType.CURSOR, ToolType.LINE, ToolType.WATERCOLOR -> drawLinePreview(canvas) ToolType.ERASER -> drawEraserPreview(canvas) + else -> Unit } } diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/ui/tools/DefaultBrushToolOptionsView.kt b/Paintroid/src/main/java/org/catrobat/paintroid/ui/tools/DefaultBrushToolOptionsView.kt index 6e6a03f9d8..ff369fb55c 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/ui/tools/DefaultBrushToolOptionsView.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/ui/tools/DefaultBrushToolOptionsView.kt @@ -150,6 +150,7 @@ class DefaultBrushToolOptionsView(rootView: ViewGroup) : BrushToolOptionsView { when (strokeCap) { Cap.ROUND -> strokeCapButtonsGroup.check(buttonCircle.id) Cap.SQUARE -> strokeCapButtonsGroup.check(buttonRect.id) + else -> Unit } } diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/ui/tools/DefaultSmudgeToolOptionsView.kt b/Paintroid/src/main/java/org/catrobat/paintroid/ui/tools/DefaultSmudgeToolOptionsView.kt index 07cb1c732b..6726254d37 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/ui/tools/DefaultSmudgeToolOptionsView.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/ui/tools/DefaultSmudgeToolOptionsView.kt @@ -189,6 +189,7 @@ class DefaultSmudgeToolOptionsView(rootView: ViewGroup) : SmudgeToolOptionsView when (strokeCap) { Cap.ROUND -> strokeButtonsGroup.check(buttonCircle.id) Cap.SQUARE -> strokeButtonsGroup.check(buttonRect.id) + else -> Unit } } diff --git a/build.gradle b/build.gradle index ab728a00a2..b69683e16d 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.5.30' + ext.kotlin_version = '1.7.10' repositories { mavenCentral() google() From 95cf07e2dd3a38b517d37ade127e1e3caa010e86 Mon Sep 17 00:00:00 2001 From: Rd Date: Sun, 4 Jun 2023 16:50:55 +0530 Subject: [PATCH 31/52] Save Project to Database --- .../java/org/catrobat/paintroid/FileIO.kt | 1 + .../org/catrobat/paintroid/MainActivity.kt | 3 +- .../paintroid/common/MainActivityConstants.kt | 8 +- .../contract/MainActivityContracts.kt | 14 +++ .../paintroid/data/local/dao/ProjectDao.kt | 4 +- .../paintroid/dialog/SaveInformationDialog.kt | 50 ++++++-- .../catrobat/paintroid/iotasks/SaveImage.kt | 110 ++++++++++++++++-- .../org/catrobat/paintroid/model/Project.kt | 6 +- .../presenter/MainActivityPresenter.kt | 47 ++++---- .../paintroid/ui/MainActivityInteractor.kt | 27 ++++- .../res/layout/dialog_pocketpaint_save.xml | 1 + .../menu/menu_pocketpaint_more_options.xml | 3 + Paintroid/src/main/res/values/string.xml | 2 + 13 files changed, 223 insertions(+), 53 deletions(-) diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/FileIO.kt b/Paintroid/src/main/java/org/catrobat/paintroid/FileIO.kt index 864f069b0c..6734a6a3a8 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/FileIO.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/FileIO.kt @@ -92,6 +92,7 @@ object FileIO { @JvmField var storeImageUri: Uri? = null + var storeImagePreviewUri: Uri? = null var temporaryFilePath: String? = null diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt index e393a5d841..85c98b3093 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt @@ -380,6 +380,7 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { when (item.itemId) { R.id.pocketpaint_options_export -> presenterMain.saveCopyClicked(true) R.id.pocketpaint_options_save_image -> presenterMain.saveImageClicked() + R.id.pocketpaint_options_save_project -> presenterMain.saveProjectClicked() R.id.pocketpaint_options_save_duplicate -> presenterMain.saveCopyClicked(false) R.id.pocketpaint_replace_image -> presenterMain.replaceImageClicked() R.id.pocketpaint_add_to_current_layer -> presenterMain.addImageToCurrentLayerClicked() @@ -638,7 +639,7 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { setSupportActionBar(toolbar) supportActionBar?.apply { setDisplayShowTitleEnabled(!isOpenedFromCatroid) - setDisplayHomeAsUpEnabled(isOpenedFromCatroid) + setDisplayHomeAsUpEnabled(true) setHomeButtonEnabled(true) setDisplayShowHomeEnabled(false) } diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/common/MainActivityConstants.kt b/Paintroid/src/main/java/org/catrobat/paintroid/common/MainActivityConstants.kt index 263e9e0f5e..c789142d90 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/common/MainActivityConstants.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/common/MainActivityConstants.kt @@ -25,6 +25,7 @@ const val SAVE_IMAGE_DEFAULT = 1 const val SAVE_IMAGE_NEW_EMPTY = 2 const val SAVE_IMAGE_LOAD_NEW = 3 const val SAVE_IMAGE_FINISH = 4 +const val SAVE_PROJECT_DEFAULT = 5 const val LOAD_IMAGE_DEFAULT = 1 const val LOAD_IMAGE_IMPORT_PNG = 2 @@ -43,6 +44,7 @@ const val PERMISSION_EXTERNAL_STORAGE_SAVE_CONFIRMED_NEW_EMPTY = 4 const val PERMISSION_EXTERNAL_STORAGE_SAVE_CONFIRMED_FINISH = 5 const val PERMISSION_REQUEST_CODE_REPLACE_PICTURE = 6 const val PERMISSION_REQUEST_CODE_IMPORT_PICTURE = 7 +const val PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT = 8 const val RESULT_INTRO_MW_NOT_SUPPORTED = 10 @@ -51,7 +53,8 @@ class MainActivityConstants private constructor() { SAVE_IMAGE_DEFAULT, SAVE_IMAGE_NEW_EMPTY, SAVE_IMAGE_LOAD_NEW, - SAVE_IMAGE_FINISH + SAVE_IMAGE_FINISH, + SAVE_PROJECT_DEFAULT ) @Retention(AnnotationRetention.SOURCE) annotation class SaveImageRequestCode @@ -83,7 +86,8 @@ class MainActivityConstants private constructor() { PERMISSION_EXTERNAL_STORAGE_SAVE_CONFIRMED_NEW_EMPTY, PERMISSION_EXTERNAL_STORAGE_SAVE_CONFIRMED_FINISH, PERMISSION_REQUEST_CODE_REPLACE_PICTURE, - PERMISSION_REQUEST_CODE_IMPORT_PICTURE + PERMISSION_REQUEST_CODE_IMPORT_PICTURE, + PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT ) @Retention(AnnotationRetention.SOURCE) annotation class PermissionRequestCode diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/contract/MainActivityContracts.kt b/Paintroid/src/main/java/org/catrobat/paintroid/contract/MainActivityContracts.kt index 84fef55bab..0af3838001 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/contract/MainActivityContracts.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/contract/MainActivityContracts.kt @@ -207,6 +207,8 @@ interface MainActivityContracts { fun saveImageClicked() + fun saveProjectClicked() + fun shareImageClicked() fun enterHideButtonsClicked() @@ -255,6 +257,8 @@ interface MainActivityContracts { fun saveImageConfirmClicked(requestCode: Int, uri: Uri?) + fun saveProjectConfirmClicked(requestCode: Int, uri: Uri?, imagePreviewUri: Uri?) + fun saveCopyConfirmClicked(requestCode: Int, uri: Uri?) fun undoClicked() @@ -345,6 +349,16 @@ interface MainActivityContracts { context: Context ) + fun saveProject( + callback: SaveImageCallback, + requestCode: Int, + layerModel: LayerContracts.Model, + commandSerializer: CommandSerializer, + uri: Uri?, + imagePreviewUri: Uri?, + context: Context + ) + fun loadFile( callback: LoadImageCallback, requestCode: Int, diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt b/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt index ac9c72d340..e773d8114a 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt @@ -11,8 +11,8 @@ interface ProjectDao { @Insert fun insertProject(project: Project) - @Query("UPDATE Project SET path= :projectUri, imagePreviewPath= :imagePreviewPath WHERE name= :name") - fun updateProjectUri(name: String, imagePreviewPath: String, projectUri: String) + @Query("UPDATE Project SET path= :projectUri, imagePreviewPath= :imagePreviewPath, lastModified= :lastModified WHERE name= :name") + fun updateProjectUri(name: String, imagePreviewPath: String, projectUri: String, lastModified: Long) @Query("SELECT * FROM Project ORDER BY lastModified DESC") fun getProjects(): List diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/dialog/SaveInformationDialog.kt b/Paintroid/src/main/java/org/catrobat/paintroid/dialog/SaveInformationDialog.kt index b58f914105..80af3580c3 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/dialog/SaveInformationDialog.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/dialog/SaveInformationDialog.kt @@ -25,12 +25,9 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.AdapterView +import android.widget.* import android.widget.AdapterView.OnItemSelectedListener -import android.widget.ArrayAdapter -import android.widget.SeekBar import android.widget.SeekBar.OnSeekBarChangeListener -import android.widget.Spinner import androidx.appcompat.app.AlertDialog import androidx.appcompat.widget.AppCompatEditText import androidx.appcompat.widget.AppCompatImageButton @@ -42,6 +39,7 @@ import org.catrobat.paintroid.FileIO.FileType.JPG import org.catrobat.paintroid.FileIO.FileType.CATROBAT import org.catrobat.paintroid.FileIO.FileType.ORA import org.catrobat.paintroid.R +import org.catrobat.paintroid.common.PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT import java.util.Locale private const val STANDARD_FILE_NAME = "image" @@ -70,10 +68,9 @@ class SaveInformationDialog : isStandard: Boolean, isExport: Boolean ): SaveInformationDialog { - if (isStandard) { - FileIO.filename = STANDARD_FILE_NAME - FileIO.compressFormat = Bitmap.CompressFormat.PNG - FileIO.fileType = PNG + when { + permissionCode == PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT -> setFileProperties(STANDARD_FILE_NAME, Bitmap.CompressFormat.PNG, CATROBAT) + isStandard -> setFileProperties(STANDARD_FILE_NAME, Bitmap.CompressFormat.PNG, PNG) } return SaveInformationDialog().apply { arguments = Bundle().apply { @@ -87,6 +84,12 @@ class SaveInformationDialog : } } } + + private fun setFileProperties(filename: String, compressFormat: Bitmap.CompressFormat, fileType: FileType) { + FileIO.filename = filename + FileIO.compressFormat = compressFormat + FileIO.fileType = fileType + } } override fun onCreate(savedInstanceState: Bundle?) { @@ -102,16 +105,45 @@ class SaveInformationDialog : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) initViews(view) + handlePermission(view) setSpinnerSelection() } + private fun handlePermission(view: View) { + if (permission == PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT) { + hideImageFormatViews(view) + } else { + showImageFormatViews() + } + } + + private fun hideImageFormatViews(view: View) { + val imageFormatTitle = view.findViewById(R.id.pocketpaint_image_format_title) + val imageFormatSaveInfo = view.findViewById(R.id.pocketpaint_btn_save_info) + val imageFormatSaveDivider = view.findViewById(R.id.pocketpaint_view_save_divider) + imageFormatTitle.visibility = View.GONE + imageFormatSaveInfo.visibility = View.GONE + imageFormatSaveDivider.visibility = View.GONE + spinner.visibility = View.GONE + } + + private fun showImageFormatViews() { + spinner.visibility = View.VISIBLE + } + + @SuppressLint("InflateParams") override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { inflater = requireActivity().layoutInflater val customLayout = inflater.inflate(R.layout.dialog_pocketpaint_save, null) + val dialogTitle = if(permission == PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT){ + R.string.dialog_save_project_title + } else { + R.string.dialog_save_image_title + } onViewCreated(customLayout, savedInstanceState) return AlertDialog.Builder(requireContext(), R.style.PocketPaintAlertDialog) - .setTitle(R.string.dialog_save_image_title) + .setTitle(dialogTitle) .setView(customLayout) .setPositiveButton(R.string.save_button_text) { _, _ -> FileIO.filename = imageName.text.toString() diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/SaveImage.kt b/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/SaveImage.kt index 70de33a490..cd63be8750 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/SaveImage.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/SaveImage.kt @@ -21,6 +21,7 @@ package org.catrobat.paintroid.iotasks import android.content.ContentResolver import android.content.Context import android.graphics.Bitmap +import android.graphics.BitmapFactory import android.net.Uri import android.util.Log import androidx.test.espresso.idling.CountingIdlingResource @@ -29,10 +30,13 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.catrobat.paintroid.FileIO +import org.catrobat.paintroid.LandingPageActivity.Companion.projectDB import org.catrobat.paintroid.command.serialization.CommandSerializer import org.catrobat.paintroid.contract.LayerContracts +import org.catrobat.paintroid.model.Project import java.io.IOException import java.lang.ref.WeakReference +import java.util.* class SaveImage( activity: SaveImageCallback, @@ -40,7 +44,9 @@ class SaveImage( private val layerModel: LayerContracts.Model, private val commandSerializer: CommandSerializer, private var uri: Uri?, + private var imagePreviewUri: Uri?, private val saveAsCopy: Boolean, + private val saveProject: Boolean, private val context: Context, private val scopeIO: CoroutineScope, private val idlingResource: CountingIdlingResource @@ -64,6 +70,20 @@ class SaveImage( } } + private fun getImagePreviewUri( + callback: SaveImageCallback, + bitmap: Bitmap? + ): Uri? { + val filename = FileIO.defaultFileName + return if (imagePreviewUri == null) { + val imagePreviewFilename = filename.replace(".catrobat-image", ".png") + val imageUri = FileIO.saveBitmapToFile(imagePreviewFilename, bitmap, callback.contentResolver, context) + imageUri + } else { + uri?.let { FileIO.saveBitmapToUri(it, bitmap, context) } + } + } + private fun saveOraFile( layers: List, uri: Uri, @@ -110,33 +130,72 @@ class SaveImage( } var currentUri: Uri? = null + var imagePreviewPath: Uri? = null scopeIO.launch { try { idlingResource.increment() val bitmap = layerModel.getBitmapOfAllLayers() val filename = FileIO.defaultFileName - currentUri = if (FileIO.fileType == FileIO.FileType.ORA) { - val layers = layerModel.layers - if (uri != null && filename.endsWith(FileIO.FileType.ORA.toExtension())) { + if (saveProject) { + FileIO.fileType = FileIO.FileType.CATROBAT + currentUri = if (uri != null) { uri?.let { - saveOraFile(layers, it, filename, bitmap, callback.contentResolver) + commandSerializer.overWriteFile(filename, it, callback.contentResolver) } } else { - val imageUri = exportOraFile(layers, filename, bitmap, callback.contentResolver) - imageUri + commandSerializer.writeToFile(filename) } - } else if (FileIO.fileType == FileIO.FileType.CATROBAT) { - if (uri != null) { + imagePreviewPath = getImagePreviewUri(callback, bitmap) + val date = Calendar.getInstance().timeInMillis + if(uri != null){ uri?.let { - commandSerializer.overWriteFile(filename, it, callback.contentResolver) + projectDB.dao.updateProjectUri(filename, imagePreviewPath.toString(), currentUri.toString(), date) } } else { - commandSerializer.writeToFile(filename) + val dimensions = getImageDimensions(imagePreviewPath) + val size = getImageSize(imagePreviewPath) + projectDB.dao.insertProject( + Project( + filename, + currentUri.toString(), + date, + date, + "${dimensions?.first} x ${dimensions?.second}", + FileIO.fileType.toString(), + size, + imagePreviewPath.toString() + ) + ) } } else { - getImageUri(callback, bitmap) + currentUri = if (FileIO.fileType == FileIO.FileType.ORA) { + val layers = layerModel.layers + if (uri != null && filename.endsWith(FileIO.FileType.ORA.toExtension())) { + uri?.let { + saveOraFile(layers, it, filename, bitmap, callback.contentResolver) + } + } else { + val imageUri = + exportOraFile(layers, filename, bitmap, callback.contentResolver) + imageUri + } + } else if (FileIO.fileType == FileIO.FileType.CATROBAT) { + if (uri != null) { + uri?.let { + commandSerializer.overWriteFile( + filename, + it, + callback.contentResolver + ) + } + } else { + commandSerializer.writeToFile(filename) + } + } else { + getImageUri(callback, bitmap) + } + idlingResource.decrement() } - idlingResource.decrement() } catch (e: Exception) { idlingResource.decrement() when (e) { @@ -153,6 +212,33 @@ class SaveImage( } } + private fun getImageDimensions(uri: Uri?): Pair? { + val inputStream = uri?.let { context.contentResolver.openInputStream(it) } + val options = BitmapFactory.Options() + options.inJustDecodeBounds = true + BitmapFactory.decodeStream(inputStream, null, options) + inputStream?.close() + + if (options.outWidth != -1 && options.outHeight != -1) { + return Pair(options.outWidth, options.outHeight) + } + + return null + } + + private fun getImageSize(uri: Uri?): Double { + var size = 0.0 + try { + val inputStream = uri?.let { context.contentResolver.openInputStream(it) } + val bytes = inputStream?.available()?.toLong() ?: 0 + size = bytes.toDouble() / (1024 * 1024) + inputStream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + return size + } + interface SaveImageCallback { val contentResolver: ContentResolver val isFinishing: Boolean diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/model/Project.kt b/Paintroid/src/main/java/org/catrobat/paintroid/model/Project.kt index 1beed5934a..ba375c32d3 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/model/Project.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/model/Project.kt @@ -7,11 +7,11 @@ import androidx.room.PrimaryKey data class Project( var name: String, val path: String, - var lastModified: String, - val creationDate: String, + var lastModified: Long, + val creationDate: Long, val resolution: String, val format: String, - val size: Int, + val size: Double, val imagePreviewPath: String, @PrimaryKey(autoGenerate = true) val id: Int = 0 diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt b/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt index b3ff4da502..6f9d6b7ace 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt @@ -54,31 +54,12 @@ import org.catrobat.paintroid.colorpicker.ColorHistory import org.catrobat.paintroid.command.CommandFactory import org.catrobat.paintroid.command.CommandManager import org.catrobat.paintroid.command.serialization.CommandSerializer -import org.catrobat.paintroid.common.CREATE_FILE_DEFAULT -import org.catrobat.paintroid.common.LOAD_IMAGE_CATROID -import org.catrobat.paintroid.common.LOAD_IMAGE_DEFAULT -import org.catrobat.paintroid.common.LOAD_IMAGE_IMPORT_PNG +import org.catrobat.paintroid.common.* import org.catrobat.paintroid.common.MainActivityConstants.ActivityRequestCode import org.catrobat.paintroid.common.MainActivityConstants.CreateFileRequestCode import org.catrobat.paintroid.common.MainActivityConstants.LoadImageRequestCode import org.catrobat.paintroid.common.MainActivityConstants.PermissionRequestCode import org.catrobat.paintroid.common.MainActivityConstants.SaveImageRequestCode -import org.catrobat.paintroid.common.PERMISSION_EXTERNAL_STORAGE_SAVE -import org.catrobat.paintroid.common.PERMISSION_EXTERNAL_STORAGE_SAVE_CONFIRMED_FINISH -import org.catrobat.paintroid.common.PERMISSION_EXTERNAL_STORAGE_SAVE_CONFIRMED_LOAD_NEW -import org.catrobat.paintroid.common.PERMISSION_EXTERNAL_STORAGE_SAVE_CONFIRMED_NEW_EMPTY -import org.catrobat.paintroid.common.PERMISSION_EXTERNAL_STORAGE_SAVE_COPY -import org.catrobat.paintroid.common.PERMISSION_REQUEST_CODE_IMPORT_PICTURE -import org.catrobat.paintroid.common.PERMISSION_REQUEST_CODE_REPLACE_PICTURE -import org.catrobat.paintroid.common.REQUEST_CODE_IMPORT_PNG -import org.catrobat.paintroid.common.REQUEST_CODE_INTRO -import org.catrobat.paintroid.common.REQUEST_CODE_LOAD_PICTURE -import org.catrobat.paintroid.common.RESULT_INTRO_MW_NOT_SUPPORTED -import org.catrobat.paintroid.common.SAVE_IMAGE_DEFAULT -import org.catrobat.paintroid.common.SAVE_IMAGE_FINISH -import org.catrobat.paintroid.common.SAVE_IMAGE_LOAD_NEW -import org.catrobat.paintroid.common.SAVE_IMAGE_NEW_EMPTY -import org.catrobat.paintroid.common.TEMP_PICTURE_NAME import org.catrobat.paintroid.contract.MainActivityContracts import org.catrobat.paintroid.contract.MainActivityContracts.Interactor import org.catrobat.paintroid.contract.MainActivityContracts.MainView @@ -241,6 +222,14 @@ open class MainActivityPresenter( ) } + override fun saveProjectClicked() { + navigator.showSaveImageInformationDialogWhenStandalone( + PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT, + imageNumber, + false + ) + } + override fun shareImageClicked() { checkIfClippingToolNeedsAdjustment() view.refreshDrawingSurface() @@ -361,7 +350,8 @@ open class MainActivityPresenter( PERMISSION_EXTERNAL_STORAGE_SAVE_CONFIRMED_NEW_EMPTY, PERMISSION_EXTERNAL_STORAGE_SAVE_CONFIRMED_FINISH, PERMISSION_EXTERNAL_STORAGE_SAVE_COPY, - PERMISSION_EXTERNAL_STORAGE_SAVE -> checkForDefaultFilename() + PERMISSION_EXTERNAL_STORAGE_SAVE, + PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT -> checkForDefaultFilename() } } else { if (requestCode == PERMISSION_REQUEST_CODE_REPLACE_PICTURE) { @@ -522,6 +512,13 @@ open class MainActivityPresenter( ) checkForDefaultFilename() } + PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT -> { + saveProjectConfirmClicked( + SAVE_PROJECT_DEFAULT, + FileIO.storeImageUri, + FileIO.storeImagePreviewUri, + ) + } PERMISSION_EXTERNAL_STORAGE_SAVE_COPY -> { saveCopyConfirmClicked( SAVE_IMAGE_DEFAULT, @@ -580,6 +577,12 @@ open class MainActivityPresenter( interactor.saveImage(this, requestCode, workspace.layerModel, commandSerializer, uri, context) } + override fun saveProjectConfirmClicked(requestCode: Int, uri: Uri?, imagePreviewUri: Uri?) { + checkIfClippingToolNeedsAdjustment() + view.refreshDrawingSurface() + interactor.saveProject(this, requestCode, workspace.layerModel, commandSerializer, uri, imagePreviewUri, context) + } + override fun saveCopyConfirmClicked(requestCode: Int, uri: Uri?) { checkIfClippingToolNeedsAdjustment() view.refreshDrawingSurface() @@ -1014,7 +1017,7 @@ open class MainActivityPresenter( } when (requestCode) { SAVE_IMAGE_NEW_EMPTY -> onNewImage() - SAVE_IMAGE_DEFAULT -> { + SAVE_IMAGE_DEFAULT, SAVE_PROJECT_DEFAULT -> { } SAVE_IMAGE_FINISH -> { if (model.isOpenedFromCatroid) { diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/ui/MainActivityInteractor.kt b/Paintroid/src/main/java/org/catrobat/paintroid/ui/MainActivityInteractor.kt index f167239532..f6bce9c854 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/ui/MainActivityInteractor.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/ui/MainActivityInteractor.kt @@ -44,7 +44,12 @@ class MainActivityInteractor(private val idlingResource: CountingIdlingResource) uri: Uri?, context: Context ) { - SaveImage(callback, requestCode, layerModel, commandSerializer, uri, true, context, scopeIO, idlingResource).execute() + SaveImage(callback, requestCode, layerModel, commandSerializer, uri, null, true, + saveProject = false, + context = context, + scopeIO = scopeIO, + idlingResource = idlingResource + ).execute() } override fun createFile(callback: CreateFileCallback, requestCode: Int, filename: String) { @@ -59,7 +64,25 @@ class MainActivityInteractor(private val idlingResource: CountingIdlingResource) uri: Uri?, context: Context ) { - SaveImage(callback, requestCode, layerModel, commandSerializer, uri, false, context, scopeIO, idlingResource).execute() + SaveImage(callback, requestCode, layerModel, commandSerializer, uri, null, + saveAsCopy = false, + saveProject = false, + context = context, + scopeIO = scopeIO, + idlingResource = idlingResource + ).execute() + } + + override fun saveProject( + callback: SaveImageCallback, + requestCode: Int, + layerModel: LayerContracts.Model, + commandSerializer: CommandSerializer, + uri: Uri?, + imagePreviewUri: Uri?, + context: Context + ) { + SaveImage(callback, requestCode, layerModel, commandSerializer, uri, imagePreviewUri, false, true, context, scopeIO, idlingResource).execute() } override fun loadFile( diff --git a/Paintroid/src/main/res/layout/dialog_pocketpaint_save.xml b/Paintroid/src/main/res/layout/dialog_pocketpaint_save.xml index 3cb7da04bf..973f82a2c6 100644 --- a/Paintroid/src/main/res/layout/dialog_pocketpaint_save.xml +++ b/Paintroid/src/main/res/layout/dialog_pocketpaint_save.xml @@ -84,6 +84,7 @@ android:src="@drawable/ic_pocketpaint_save_info" /> + diff --git a/Paintroid/src/main/res/values/string.xml b/Paintroid/src/main/res/values/string.xml index de2ea72fb6..9d02e75721 100644 --- a/Paintroid/src/main/res/values/string.xml +++ b/Paintroid/src/main/res/values/string.xml @@ -54,6 +54,7 @@ Stickers @string/button_import_image @string/menu_save_image + @string/menu_save_project Tools Error load/save file Check image or SD-card! @@ -91,6 +92,7 @@ Add to current layer Save image Save copy + Save project Hide buttons Share image Send image via From 6b4519db46cfd6ee55745fd643f842cf8602a0fd Mon Sep 17 00:00:00 2001 From: Rd Date: Mon, 5 Jun 2023 02:25:16 +0530 Subject: [PATCH 32/52] PAINTROID-609 Floating action buttons for landing page --- .../LandingPageActivityIntegrationTest.kt | 142 ++++++++++++++++++ .../catrobat/paintroid/LandingPageActivity.kt | 16 ++ .../org/catrobat/paintroid/MainActivity.kt | 32 ++-- .../res/drawable/ic_pocketpaint_download.xml | 5 + .../activity_pocketpaint_landing_page.xml | 46 ++++++ colorpicker/src/main/res/values/colors.xml | 1 + 6 files changed, 229 insertions(+), 13 deletions(-) create mode 100644 Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt create mode 100644 Paintroid/src/main/res/drawable/ic_pocketpaint_download.xml diff --git a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt new file mode 100644 index 0000000000..1aa7331baf --- /dev/null +++ b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt @@ -0,0 +1,142 @@ +package org.catrobat.paintroid.test.espresso + +import android.app.Activity +import android.app.Instrumentation +import android.content.ContentValues +import android.content.Intent +import android.graphics.Bitmap +import android.graphics.Color +import android.net.Uri +import android.os.Build +import android.os.Environment +import android.provider.MediaStore +import androidx.appcompat.widget.Toolbar +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.Espresso.pressBack +import androidx.test.espresso.action.ViewActions.* +import androidx.test.espresso.assertion.ViewAssertions +import androidx.test.espresso.intent.Intents +import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction +import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.rule.ActivityTestRule +import org.catrobat.paintroid.LandingPageActivity +import org.catrobat.paintroid.R +import org.catrobat.paintroid.test.espresso.util.BitmapLocationProvider +import org.catrobat.paintroid.test.espresso.util.DrawingSurfaceLocationProvider +import org.catrobat.paintroid.test.espresso.util.UiInteractions +import org.catrobat.paintroid.test.espresso.util.wrappers.DrawingSurfaceInteraction +import org.catrobat.paintroid.test.utils.ScreenshotOnFailRule +import org.junit.* +import org.junit.runner.RunWith +import java.io.File +import java.io.IOException + +@RunWith(AndroidJUnit4::class) +class LandingPageActivityIntegrationTest { + @get:Rule + var launchActivityRule = ActivityTestRule(LandingPageActivity::class.java) + + @get:Rule + var screenshotOnFailRule = ScreenshotOnFailRule() + + private lateinit var activity: LandingPageActivity + + companion object { + private lateinit var deletionFileList: ArrayList + } + + @Before + fun setUp() { + deletionFileList = ArrayList() + activity = launchActivityRule.activity + } + + @After + fun tearDown() { + for (file in deletionFileList) { + if (file != null && file.exists()) { + Assert.assertTrue(file.delete()) + } + } + } + + @Test + fun testTopAppBarDisplayed(){ + onView(ViewMatchers.isAssignableFrom(Toolbar::class.java)) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) + } + + @Test + fun testAppBarTitleDisplayPocketPaint() { + onView(withText("Pocket Paint")) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) + } + + @Test + fun testTwoFABDisplayed(){ + onView(withId(R.id.pocketpaint_fab_load_image)) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) + onView(withId(R.id.pocketpaint_fab_new_image)) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) + } + + @Test + fun testMyProjectsTextDisplayed(){ + onView(withText("My Projects")) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) + } + + @Test + fun testNewImage() { + onView(withId(R.id.pocketpaint_fab_new_image)).perform(click()) + DrawingSurfaceInteraction.onDrawingSurfaceView() + .perform(UiInteractions.touchAt(DrawingSurfaceLocationProvider.MIDDLE)) + DrawingSurfaceInteraction.onDrawingSurfaceView() + .checkPixelColor(Color.BLACK, BitmapLocationProvider.MIDDLE) + pressBack() + onView(withText(R.string.discard_button_text)).perform(click()) + onView(withId(R.id.pocketpaint_fab_new_image)).perform(click()) + DrawingSurfaceInteraction.onDrawingSurfaceView() + .checkPixelColor(Color.TRANSPARENT, BitmapLocationProvider.MIDDLE) + } + + @Test + fun testLoadImageIntentStarted() { + Intents.init() + val intent = Intent() + intent.data = createTestImageFile() + val resultOK = Instrumentation.ActivityResult(Activity.RESULT_OK, intent) + Intents.intending(hasAction(Intent.ACTION_GET_CONTENT)).respondWith(resultOK) + onView(withId(R.id.pocketpaint_fab_load_image)).perform(click()) + DrawingSurfaceInteraction.onDrawingSurfaceView() + .checkPixelColor(Color.BLACK, BitmapLocationProvider.MIDDLE) + Intents.release() + } + + private fun createTestImageFile(): Uri? { + val bitmap = Bitmap.createBitmap(400, 400, Bitmap.Config.ARGB_8888) + val contentValues = ContentValues() + contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, "testfile.jpg") + contentValues.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg") + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES) + } + val resolver = InstrumentationRegistry.getInstrumentation().targetContext.contentResolver + val imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues) + try { + val fos = imageUri?.let { resolver.openOutputStream(it) } + Assert.assertTrue(bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos)) + assert(fos != null) + fos?.close() + } catch (e: IOException) { + throw AssertionError("Picture file could not be created.", e) + } + val imageFile = File(imageUri?.path, "testfile.jpg") + deletionFileList.add(imageFile) + return imageUri + } +} \ No newline at end of file diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt b/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt index a90348d275..3927c2fe2d 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt @@ -5,12 +5,14 @@ import android.os.Bundle import android.view.View import android.widget.ImageView import androidx.appcompat.app.AppCompatActivity +import com.google.android.material.floatingactionbutton.FloatingActionButton import org.catrobat.paintroid.data.local.database.ProjectDatabase import org.catrobat.paintroid.data.local.database.ProjectDatabaseProvider class LandingPageActivity: AppCompatActivity() { companion object { lateinit var projectDB: ProjectDatabase + val FAB_ACTION = "" } override fun onCreate(savedInstanceState: Bundle?) { @@ -29,5 +31,19 @@ class LandingPageActivity: AppCompatActivity() { imagePreview.setOnClickListener(imagePreviewClickListener) editCircleIcon.setOnClickListener(imagePreviewClickListener) + + val mainActivityIntent = Intent(this, MainActivity::class.java) + + val newImage = findViewById(R.id.pocketpaint_fab_new_image) + newImage.setOnClickListener { + mainActivityIntent.putExtra(FAB_ACTION, "new_image") + startActivity(mainActivityIntent) + } + + val loadImage = findViewById(R.id.pocketpaint_fab_load_image) + loadImage.setOnClickListener { + mainActivityIntent.putExtra(FAB_ACTION, "load_image") + startActivity(mainActivityIntent) + } } } \ No newline at end of file diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt index 85c98b3093..43e5e95e2c 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt @@ -54,6 +54,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import org.catrobat.paintroid.LandingPageActivity.Companion.FAB_ACTION import org.catrobat.paintroid.colorpicker.ColorHistory import org.catrobat.paintroid.command.CommandFactory import org.catrobat.paintroid.command.CommandManager @@ -309,21 +310,26 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { presenterMain.initializeFromCleanState(null, null) } savedInstanceState == null -> { - val intent = intent - val picturePath = intent.getStringExtra(PAINTROID_PICTURE_PATH) - val pictureName = intent.getStringExtra(PAINTROID_PICTURE_NAME) - presenterMain.initializeFromCleanState(picturePath, pictureName) - - if (!model.isOpenedFromCatroid && presenterMain.checkForTemporaryFile() && (!isRunningEspressoTests || isTemporaryFileSavingTest)) { - val workspaceReturnValue = presenterMain.openTemporaryFile() - commandManager.loadCommandsCatrobatImage(workspaceReturnValue?.commandManagerModel) - model.colorHistory = workspaceReturnValue?.colorHistory ?: ColorHistory() - model.colorHistory.colors.lastOrNull()?.let { - toolReference.tool?.changePaintColor(it) - presenterMain.setBottomNavigationColor(it) + when (receivedIntent.getStringExtra(FAB_ACTION)) { + "new_image" -> presenterMain.onNewImage() + "load_image" -> presenterMain.replaceImageClicked() + else -> { + val intent = intent + val picturePath = intent.getStringExtra(PAINTROID_PICTURE_PATH) + val pictureName = intent.getStringExtra(PAINTROID_PICTURE_NAME) + presenterMain.initializeFromCleanState(picturePath, pictureName) + if (!model.isOpenedFromCatroid && presenterMain.checkForTemporaryFile() && (!isRunningEspressoTests || isTemporaryFileSavingTest)) { + val workspaceReturnValue = presenterMain.openTemporaryFile() + commandManager.loadCommandsCatrobatImage(workspaceReturnValue?.commandManagerModel) + model.colorHistory = workspaceReturnValue?.colorHistory ?: ColorHistory() + model.colorHistory.colors.lastOrNull()?.let { + toolReference.tool?.changePaintColor(it) + presenterMain.setBottomNavigationColor(it) + } + } + workspace.perspective.setBitmapDimensions(layerModel.width, layerModel.height) } } - workspace.perspective.setBitmapDimensions(layerModel.width, layerModel.height) } else -> { val isFullscreen = savedInstanceState.getBoolean(IS_FULLSCREEN_KEY, false) diff --git a/Paintroid/src/main/res/drawable/ic_pocketpaint_download.xml b/Paintroid/src/main/res/drawable/ic_pocketpaint_download.xml new file mode 100644 index 0000000000..dbc4c4ed5b --- /dev/null +++ b/Paintroid/src/main/res/drawable/ic_pocketpaint_download.xml @@ -0,0 +1,5 @@ + + + diff --git a/Paintroid/src/main/res/layout/activity_pocketpaint_landing_page.xml b/Paintroid/src/main/res/layout/activity_pocketpaint_landing_page.xml index 7543274caf..69973300ef 100644 --- a/Paintroid/src/main/res/layout/activity_pocketpaint_landing_page.xml +++ b/Paintroid/src/main/res/layout/activity_pocketpaint_landing_page.xml @@ -9,6 +9,7 @@ layout="@layout/pocketpaint_layout_landing_page_top_bar"/> + + + + + + + + \ No newline at end of file diff --git a/colorpicker/src/main/res/values/colors.xml b/colorpicker/src/main/res/values/colors.xml index 389a532506..eb5f3b2a00 100644 --- a/colorpicker/src/main/res/values/colors.xml +++ b/colorpicker/src/main/res/values/colors.xml @@ -37,6 +37,7 @@ #FFC5060E #FFEB4618 #FFF9921C + #FFAB08 #FFF3D605 #FF000000 #FFA3A3A3 From 02039647ceb53c971bb5b8403c10b910be069ae1 Mon Sep 17 00:00:00 2001 From: Rd Date: Tue, 6 Jun 2023 14:04:29 +0530 Subject: [PATCH 33/52] PAINTROID-610 Show all projects of the user in the landing page --- .../catrobat/paintroid/LandingPageActivity.kt | 65 +++++++- .../org/catrobat/paintroid/MainActivity.kt | 29 +++- .../paintroid/adapter/ProjectAdapter.kt | 143 ++++++++++++++++++ .../catrobat/paintroid/common/Constants.kt | 2 + .../paintroid/data/local/dao/ProjectDao.kt | 2 +- .../paintroid/dialog/ProjectDeleteDialog.kt | 27 ++++ .../paintroid/dialog/ProjectDetailsDialog.kt | 34 +++++ .../catrobat/paintroid/iotasks/SaveImage.kt | 28 ++-- .../main/res/drawable/ic_pocketpaint_more.xml | 5 + .../res/layout/pocketpaint_item_project.xml | 76 ++++++++++ .../menu/menu_pocketpaint_project_details.xml | 9 ++ Paintroid/src/main/res/values/string.xml | 4 +- Paintroid/src/main/res/values/style.xml | 7 + 13 files changed, 409 insertions(+), 22 deletions(-) create mode 100644 Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt create mode 100644 Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDeleteDialog.kt create mode 100644 Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDetailsDialog.kt create mode 100644 Paintroid/src/main/res/drawable/ic_pocketpaint_more.xml create mode 100644 Paintroid/src/main/res/layout/pocketpaint_item_project.xml create mode 100644 Paintroid/src/main/res/menu/menu_pocketpaint_project_details.xml diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt b/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt index 3927c2fe2d..5ef93b2595 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt @@ -1,17 +1,27 @@ package org.catrobat.paintroid import android.content.Intent +import android.net.Uri import android.os.Bundle import android.view.View import android.widget.ImageView import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import com.google.android.material.floatingactionbutton.FloatingActionButton +import org.catrobat.paintroid.adapter.ProjectAdapter import org.catrobat.paintroid.data.local.database.ProjectDatabase import org.catrobat.paintroid.data.local.database.ProjectDatabaseProvider +import org.catrobat.paintroid.model.Project class LandingPageActivity: AppCompatActivity() { companion object { lateinit var projectDB: ProjectDatabase + private lateinit var projectsRecyclerView: RecyclerView + private lateinit var projectsList: ArrayList + lateinit var projectAdapter: ProjectAdapter + var latestProject: Project? = null + lateinit var imagePreview: ImageView val FAB_ACTION = "" } @@ -20,13 +30,32 @@ class LandingPageActivity: AppCompatActivity() { setContentView(R.layout.activity_pocketpaint_landing_page) projectDB = ProjectDatabaseProvider.getDatabase(applicationContext) + val allProjects = projectDB.dao.getProjects() + latestProject = allProjects.firstOrNull() - val imagePreview = findViewById(R.id.pocketpaint_image_preview) + init() + + imagePreview = findViewById(R.id.pocketpaint_image_preview) val editCircleIcon = findViewById(R.id.pocketpaint_image_edit_circle) + latestProject?.let { + imagePreview.setImageURI(Uri.parse(it.imagePreviewPath)) + imagePreview.scaleType = ImageView.ScaleType.CENTER + } + val imagePreviewClickListener = View.OnClickListener { - val intent = Intent(this, MainActivity::class.java) - startActivity(intent) + if (allProjects.isNotEmpty()){ + val loadProjectIntent = Intent(applicationContext, MainActivity::class.java).apply { + putExtra(FAB_ACTION, "load_project") + putExtra("PROJECT_URI", latestProject?.path) + putExtra("PROJECT_NAME", latestProject?.name) + putExtra("PROJECT_IMAGE_PREVIEW_URI", latestProject?.imagePreviewPath) + } + startActivity(loadProjectIntent) + }else { + val intent = Intent(this, MainActivity::class.java) + startActivity(intent) + } } imagePreview.setOnClickListener(imagePreviewClickListener) @@ -46,4 +75,34 @@ class LandingPageActivity: AppCompatActivity() { startActivity(mainActivityIntent) } } + + private fun init() { + projectsRecyclerView = findViewById(R.id.pocketpaint_projects_list) + projectsRecyclerView.layoutManager = LinearLayoutManager(this) + + projectsList = ArrayList() + projectDB.dao.getProjects().forEach { + projectsList.add(it) + } + + projectAdapter = ProjectAdapter(projectsList, supportFragmentManager) + projectsRecyclerView.adapter = projectAdapter + + projectAdapter.setOnItemClickListener(object: ProjectAdapter.OnItemClickListener{ + override fun onItemClick( + position: Int, + projectUri: String, + projectName: String, + projectImagePreviewUri: String + ) { + val loadProjectIntent = Intent(applicationContext, MainActivity::class.java).apply { + putExtra(FAB_ACTION, "load_project") + putExtra("PROJECT_URI", projectUri) + putExtra("PROJECT_NAME", projectName) + putExtra("PROJECT_IMAGE_PREVIEW_URI", projectImagePreviewUri) + } + startActivity(loadProjectIntent) + } + }) + } } \ No newline at end of file diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt index 43e5e95e2c..6353c83326 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt @@ -43,6 +43,7 @@ import androidx.annotation.VisibleForTesting import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.TooltipCompat +import androidx.core.net.toUri import androidx.core.widget.ContentLoadingProgressBar import androidx.drawerlayout.widget.DrawerLayout import androidx.recyclerview.widget.LinearLayoutManager @@ -64,9 +65,7 @@ import org.catrobat.paintroid.command.implementation.DefaultCommandFactory import org.catrobat.paintroid.command.implementation.DefaultCommandManager import org.catrobat.paintroid.command.implementation.LayerOpacityCommand import org.catrobat.paintroid.command.serialization.CommandSerializer -import org.catrobat.paintroid.common.CommonFactory -import org.catrobat.paintroid.common.PAINTROID_PICTURE_NAME -import org.catrobat.paintroid.common.PAINTROID_PICTURE_PATH +import org.catrobat.paintroid.common.* import org.catrobat.paintroid.contract.LayerContracts import org.catrobat.paintroid.contract.MainActivityContracts import org.catrobat.paintroid.contract.MainActivityContracts.MainView @@ -166,6 +165,10 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { private var userInteraction = false private var isTemporaryFileSavingTest = false + var projectName: String? = null + var projectUri: String? = null + var projectImagePreviewUri: String? = null + private val isRunningEspressoTests: Boolean by lazy { try { Class.forName("androidx.test.espresso.Espresso") @@ -313,6 +316,12 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { when (receivedIntent.getStringExtra(FAB_ACTION)) { "new_image" -> presenterMain.onNewImage() "load_image" -> presenterMain.replaceImageClicked() + "load_project" -> { + projectName = receivedIntent.getStringExtra("PROJECT_NAME") + projectUri = receivedIntent.getStringExtra("PROJECT_URI") + projectImagePreviewUri = receivedIntent.getStringExtra("PROJECT_IMAGE_PREVIEW_URI") + presenterMain.loadScaledImage(projectUri?.toUri(), REQUEST_CODE_LOAD_PICTURE) + } else -> { val intent = intent val picturePath = intent.getStringExtra(PAINTROID_PICTURE_PATH) @@ -406,7 +415,19 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { UserPreferences(getPreferences(MODE_PRIVATE)) ) R.id.pocketpaint_advanced_settings -> presenterMain.showAdvancedSettingsClicked() - android.R.id.home -> presenterMain.backToPocketCodeClicked() + android.R.id.home -> + if(projectName?.let { + FileIO.checkFileExists(FileIO.FileType.CATROBAT, + it, this.contentResolver) + } == true){ + FileIO.storeImageUri = Uri.parse(projectUri) + FileIO.storeImagePreviewUri = Uri.parse(projectImagePreviewUri) + presenterMain.switchBetweenVersions(PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT, false) + presenterMain.finishActivity() + } + else { + presenterMain.backToPocketCodeClicked() + } else -> return false } return true diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt b/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt new file mode 100644 index 0000000000..a98b00561b --- /dev/null +++ b/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt @@ -0,0 +1,143 @@ +package org.catrobat.paintroid.adapter + +import android.net.Uri +import android.os.Build +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.PopupMenu +import android.widget.TextView +import androidx.annotation.RequiresApi +import androidx.fragment.app.FragmentManager +import androidx.recyclerview.widget.RecyclerView +import org.catrobat.paintroid.LandingPageActivity.Companion.imagePreview +import org.catrobat.paintroid.LandingPageActivity.Companion.latestProject +import org.catrobat.paintroid.R +import org.catrobat.paintroid.common.PROJECT_DELETE_DIALOG_FRAGMENT_TAG +import org.catrobat.paintroid.common.PROJECT_DETAILS_DIALOG_FRAGMENT_TAG +import org.catrobat.paintroid.dialog.ProjectDeleteDialog +import org.catrobat.paintroid.dialog.ProjectDetailsDialog +import org.catrobat.paintroid.model.Project +import java.text.SimpleDateFormat +import java.util.* +import kotlin.collections.ArrayList + +class ProjectAdapter(var projectList: ArrayList, val supportFragmentManager: FragmentManager): RecyclerView.Adapter() { + private var itemClickListener: OnItemClickListener? = null + + class ItemViewHolder(itemView: View): RecyclerView.ViewHolder(itemView){ + val itemImageView: ImageView = itemView.findViewById(R.id.iv_pocket_paint_project_thumbnail_image) + val itemNameText: TextView = itemView.findViewById(R.id.tv_pocket_paint_project_name) + val itemLastModifiedText: TextView = itemView.findViewById(R.id.tv_pocket_paint_project_lastmodified) + val itemMoreOption : ImageView = itemView.findViewById(R.id.iv_pocket_paint_project_more) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder { + val view = LayoutInflater.from(parent.context).inflate(R.layout.pocketpaint_item_project, parent, false) + return ItemViewHolder(view) + } + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP_MR1) + override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { + val item = projectList[position] + val id = item.id + val name = item.name.substringBefore(".catrobat-image") + val resolution = item.resolution + val creationDate = item.creationDate + val lastModifiedDate = item.lastModified + val format = item.format + val size = item.size + + val dateTimeFormat = SimpleDateFormat("dd/MM/yyyy - HH:mm", Locale.getDefault()) + + val formattedLastModified = dateTimeFormat.format(Date(lastModifiedDate)) + val formattedCreationDate = dateTimeFormat.format(Date(creationDate)) + + val formattedSize = if (size >= 1.0) { + val formattedValue = String.format("%.2f", size) + "${formattedValue}MB" + } else { + val formattedValue = String.format("%.1f", size * 1024) + "${formattedValue}KB" + } + + holder.itemImageView.setImageURI(Uri.parse(item.imagePreviewPath)) + holder.itemNameText.text = name + holder.itemLastModifiedText.text = formattedLastModified + + val projectDetailsMenu = holder.itemMoreOption + projectDetailsMenu.setOnClickListener { view -> + val popupMenu = PopupMenu(view.context, view) + popupMenu.inflate(R.menu.menu_pocketpaint_project_details) + popupMenu.setOnMenuItemClickListener { menuItem -> + when(menuItem.itemId){ + R.id.project_details -> { + val projectDetails = ProjectDetailsDialog(name, resolution, formattedLastModified, formattedCreationDate, format, formattedSize) + projectDetails.show(supportFragmentManager, PROJECT_DETAILS_DIALOG_FRAGMENT_TAG) + true + } + R.id.project_delete -> { + val projectDelete = ProjectDeleteDialog(id, name) + projectDelete.show(supportFragmentManager, PROJECT_DELETE_DIALOG_FRAGMENT_TAG) + true + } + else -> false + } + } + popupMenu.show() + } + + holder.itemView.setOnClickListener { + val clickedItem = projectList[position] + val projectUri = clickedItem.path + val projectName = clickedItem.name + val projectImagePreviewUri = clickedItem.imagePreviewPath + itemClickListener?.onItemClick(position, projectUri, projectName, projectImagePreviewUri) + } + } + + fun insertProject(project: Project) { + projectList.add(0, project) + imagePreview.setImageURI(Uri.parse(project?.imagePreviewPath)) + notifyItemInserted(0) + } + + fun updateProject(filename: String, imagePreviewPath: String, projectUri: String, lastModified: Long) { + val index = projectList.indexOfFirst { it.name == filename } + if (index != -1) { + val updatedProject = projectList[index].copy( + path = projectUri, + imagePreviewPath = imagePreviewPath, + lastModified = lastModified, + ) + projectList[index] = updatedProject + notifyItemChanged(index) + } + } + + fun removeProject(projectId: Int) { + val iterator = projectList.iterator() + while (iterator.hasNext()) { + val project = iterator.next() + if (project.id == projectId) { + iterator.remove() + break + } + } + latestProject = projectList[0] + imagePreview.setImageURI(Uri.parse(latestProject?.imagePreviewPath)) + } + + override fun getItemCount(): Int { + return projectList.size + } + + interface OnItemClickListener{ + fun onItemClick(position: Int, projectUri: String, projectName: String, projectImagePreviewUri: String) + } + + fun setOnItemClickListener(listener: OnItemClickListener){ + itemClickListener = listener + } +} \ No newline at end of file diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/common/Constants.kt b/Paintroid/src/main/java/org/catrobat/paintroid/common/Constants.kt index 19b121b8b4..fc393db096 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/common/Constants.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/common/Constants.kt @@ -25,6 +25,8 @@ const val PAINTROID_PICTURE_PATH = "org.catrobat.extra.PAINTROID_PICTURE_PATH" const val PAINTROID_PICTURE_NAME = "org.catrobat.extra.PAINTROID_PICTURE_NAME" const val TEMP_PICTURE_NAME = "catroidTemp" const val MEDIA_GALLEY_URL = "https://share.catrob.at/pocketcode/media-library/looks" +const val PROJECT_DETAILS_DIALOG_FRAGMENT_TAG = "projectdetailsfragment" +const val PROJECT_DELETE_DIALOG_FRAGMENT_TAG = "projectdeletefragment" const val ABOUT_DIALOG_FRAGMENT_TAG = "aboutdialogfragment" const val LIKE_US_DIALOG_FRAGMENT_TAG = "likeusdialogfragment" const val RATE_US_DIALOG_FRAGMENT_TAG = "rateusdialogfragment" diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt b/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt index e773d8114a..3c8222668a 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt @@ -12,7 +12,7 @@ interface ProjectDao { fun insertProject(project: Project) @Query("UPDATE Project SET path= :projectUri, imagePreviewPath= :imagePreviewPath, lastModified= :lastModified WHERE name= :name") - fun updateProjectUri(name: String, imagePreviewPath: String, projectUri: String, lastModified: Long) + fun updateProject(name: String, imagePreviewPath: String, projectUri: String, lastModified: Long) @Query("SELECT * FROM Project ORDER BY lastModified DESC") fun getProjects(): List diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDeleteDialog.kt b/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDeleteDialog.kt new file mode 100644 index 0000000000..0c997cc512 --- /dev/null +++ b/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDeleteDialog.kt @@ -0,0 +1,27 @@ +package org.catrobat.paintroid.dialog + +import android.app.AlertDialog +import android.app.Dialog +import android.os.Bundle +import androidx.appcompat.app.AppCompatDialogFragment +import androidx.core.text.HtmlCompat +import org.catrobat.paintroid.LandingPageActivity.Companion.projectAdapter +import org.catrobat.paintroid.LandingPageActivity.Companion.projectDB +import org.catrobat.paintroid.R + +class ProjectDeleteDialog(private val projectId: Int, private val name: String) : AppCompatDialogFragment() { + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val message = getString(R.string.pocketpaint_project_delete_title_dialog, name) + + return AlertDialog.Builder(context, R.style.PocketPaintAlertDialog) + .setTitle(HtmlCompat.fromHtml(message, HtmlCompat.FROM_HTML_MODE_LEGACY)) + .setMessage(R.string.pocketpaint_project_delete_message_dialog) + .setPositiveButton(R.string.pocketpaint_ok) { _, _ -> + projectDB.dao.deleteProject(projectId) + projectAdapter.removeProject(projectId) + projectAdapter.notifyDataSetChanged() + } + .setNegativeButton(R.string.pocketpaint_cancel) { _, _ -> dismiss() } + .create() + } +} \ No newline at end of file diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDetailsDialog.kt b/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDetailsDialog.kt new file mode 100644 index 0000000000..c4012f8c74 --- /dev/null +++ b/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDetailsDialog.kt @@ -0,0 +1,34 @@ +package org.catrobat.paintroid.dialog + +import android.app.AlertDialog +import android.app.Dialog +import android.os.Bundle +import androidx.appcompat.app.AppCompatDialogFragment +import androidx.core.text.HtmlCompat +import org.catrobat.paintroid.R + +class ProjectDetailsDialog( + private val name: String, + private val resolution: String, + private val formattedLastModified: String, + private val formattedCreationDate: String, + private val format: String, + private val size: String +) : AppCompatDialogFragment() { + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val message = getString( + R.string.pocketpaint_project_details_dialog, + resolution, + formattedLastModified, + formattedCreationDate, + format, + size.toString() + ) + + return AlertDialog.Builder(context, R.style.PocketPaintAlertDialog) + .setTitle(name) + .setMessage(HtmlCompat.fromHtml(message, HtmlCompat.FROM_HTML_MODE_LEGACY)) + .setPositiveButton(R.string.pocketpaint_ok) { _, _ -> dismiss() } + .create() + } +} \ No newline at end of file diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/SaveImage.kt b/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/SaveImage.kt index cd63be8750..cbb9ea6306 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/SaveImage.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/SaveImage.kt @@ -30,6 +30,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.catrobat.paintroid.FileIO +import org.catrobat.paintroid.LandingPageActivity.Companion.projectAdapter import org.catrobat.paintroid.LandingPageActivity.Companion.projectDB import org.catrobat.paintroid.command.serialization.CommandSerializer import org.catrobat.paintroid.contract.LayerContracts @@ -80,7 +81,7 @@ class SaveImage( val imageUri = FileIO.saveBitmapToFile(imagePreviewFilename, bitmap, callback.contentResolver, context) imageUri } else { - uri?.let { FileIO.saveBitmapToUri(it, bitmap, context) } + imagePreviewUri?.let { FileIO.saveBitmapToUri(it, bitmap, context) } } } @@ -149,23 +150,24 @@ class SaveImage( val date = Calendar.getInstance().timeInMillis if(uri != null){ uri?.let { - projectDB.dao.updateProjectUri(filename, imagePreviewPath.toString(), currentUri.toString(), date) + projectDB.dao.updateProject(filename, imagePreviewPath.toString(), currentUri.toString(), date) + projectAdapter.updateProject(filename, imagePreviewPath.toString(), currentUri.toString(), date) } } else { val dimensions = getImageDimensions(imagePreviewPath) val size = getImageSize(imagePreviewPath) - projectDB.dao.insertProject( - Project( - filename, - currentUri.toString(), - date, - date, - "${dimensions?.first} x ${dimensions?.second}", - FileIO.fileType.toString(), - size, - imagePreviewPath.toString() - ) + val project = Project( + filename, + currentUri.toString(), + date, + date, + "${dimensions?.first} x ${dimensions?.second}", + FileIO.fileType.toString(), + size, + imagePreviewPath.toString() ) + projectDB.dao.insertProject(project) + projectAdapter.insertProject(project) } } else { currentUri = if (FileIO.fileType == FileIO.FileType.ORA) { diff --git a/Paintroid/src/main/res/drawable/ic_pocketpaint_more.xml b/Paintroid/src/main/res/drawable/ic_pocketpaint_more.xml new file mode 100644 index 0000000000..073ebd8795 --- /dev/null +++ b/Paintroid/src/main/res/drawable/ic_pocketpaint_more.xml @@ -0,0 +1,5 @@ + + + diff --git a/Paintroid/src/main/res/layout/pocketpaint_item_project.xml b/Paintroid/src/main/res/layout/pocketpaint_item_project.xml new file mode 100644 index 0000000000..aa8f35f4cd --- /dev/null +++ b/Paintroid/src/main/res/layout/pocketpaint_item_project.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Paintroid/src/main/res/menu/menu_pocketpaint_project_details.xml b/Paintroid/src/main/res/menu/menu_pocketpaint_project_details.xml new file mode 100644 index 0000000000..f648a2dc7d --- /dev/null +++ b/Paintroid/src/main/res/menu/menu_pocketpaint_project_details.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/Paintroid/src/main/res/values/string.xml b/Paintroid/src/main/res/values/string.xml index 9d02e75721..afed22db41 100644 --- a/Paintroid/src/main/res/values/string.xml +++ b/Paintroid/src/main/res/values/string.xml @@ -185,7 +185,9 @@ This format remembers layers. It can be opened by apps that support the Openraster format. catrobat-image Pocket Paint\'s native image format. This format remembers commands and layers. - + Resolution: %s<br />Last modified: %s<br />Creation date: %s<br />Format: %s<br />Size: %s + Delete %s + Do you really want to delete your Project? This app needs the requested permission to function properly. In order to save images to the local memory, the app needs read and write access to it. This app needs the requested permission to function properly. In order to save images to the local memory, the app needs read and write access to it. diff --git a/Paintroid/src/main/res/values/style.xml b/Paintroid/src/main/res/values/style.xml index 84a7bdaf90..404941502b 100644 --- a/Paintroid/src/main/res/values/style.xml +++ b/Paintroid/src/main/res/values/style.xml @@ -50,6 +50,8 @@ @android:transition/move @android:transition/move @style/AppBottomNavigation + @style/CustomPopupMenuStyle + @style/CustomPopupMenuStyle @style/appBarLayout @android:color/black @android:color/tab_indicator_text @@ -133,6 +135,11 @@ @android:color/white + + - + + From cfd5e26cddb5c28ff07278e69213b7293def4fa1 Mon Sep 17 00:00:00 2001 From: Rd Date: Sat, 10 Jun 2023 22:20:16 +0530 Subject: [PATCH 35/52] Fixed Static Analysis Warnings --- .../paintroid/test/database/ProjectDaoTest.kt | 10 +- .../LandingPageActivityIntegrationTest.kt | 170 ++++++++++-------- .../catrobat/paintroid/LandingPageActivity.kt | 10 +- .../org/catrobat/paintroid/MainActivity.kt | 39 ++-- .../paintroid/adapter/ProjectAdapter.kt | 45 ++--- .../paintroid/data/local/dao/ProjectDao.kt | 2 +- .../data/local/database/ProjectDatabase.kt | 4 +- .../local/database/ProjectDatabaseProvider.kt | 8 +- .../paintroid/dialog/ProjectDeleteDialog.kt | 6 +- .../paintroid/dialog/ProjectDetailsDialog.kt | 2 +- .../paintroid/dialog/SaveInformationDialog.kt | 10 +- .../catrobat/paintroid/iotasks/SaveImage.kt | 99 +++++----- .../org/catrobat/paintroid/model/Project.kt | 2 +- .../paintroid/model/SaveImageOptions.kt | 5 + .../presenter/MainActivityPresenter.kt | 26 ++- .../paintroid/ui/MainActivityInteractor.kt | 47 +++-- 16 files changed, 278 insertions(+), 207 deletions(-) create mode 100644 Paintroid/src/main/java/org/catrobat/paintroid/model/SaveImageOptions.kt diff --git a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/database/ProjectDaoTest.kt b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/database/ProjectDaoTest.kt index 15ca0966e2..3d6b766c53 100644 --- a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/database/ProjectDaoTest.kt +++ b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/database/ProjectDaoTest.kt @@ -3,7 +3,9 @@ package org.catrobat.paintroid.test.database import androidx.room.Room import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 -import junit.framework.Assert.* +import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertTrue +import junit.framework.Assert.assertEquals import org.catrobat.paintroid.data.local.dao.ProjectDao import org.catrobat.paintroid.data.local.database.ProjectDatabase import org.catrobat.paintroid.model.Project @@ -27,7 +29,7 @@ class ProjectDaoTest { } @After - fun teardown(){ + fun teardown() { database.close() } @@ -114,9 +116,9 @@ class ProjectDaoTest { "catrobat/upath", 1) val allProjects = dao.getProjects() - val updatedProject = allProjects.find { it.name == "name"} + val updatedProject = allProjects.find { it.name == "name" } assertEquals("paintroid/upath", updatedProject?.imagePreviewPath) assertEquals("catrobat/upath", updatedProject?.path) assertEquals(1L, updatedProject?.lastModified) } -} \ No newline at end of file +} diff --git a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt index c7c54e3b06..2b97439ab9 100644 --- a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt +++ b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt @@ -19,17 +19,26 @@ import android.widget.TextView import androidx.appcompat.widget.Toolbar import androidx.core.graphics.drawable.toBitmap import androidx.recyclerview.widget.RecyclerView +import androidx.room.Room import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.pressBack import androidx.test.espresso.UiController import androidx.test.espresso.ViewAction -import androidx.test.espresso.action.ViewActions.* +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.action.ViewActions.replaceText +import androidx.test.espresso.action.ViewActions.closeSoftKeyboard +import androidx.test.espresso.action.ViewActions.scrollTo import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.contrib.RecyclerViewActions import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition import androidx.test.espresso.intent.Intents import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction -import androidx.test.espresso.matcher.ViewMatchers.* +import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.isRoot +import androidx.test.espresso.matcher.ViewMatchers.hasDescendant import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import androidx.test.rule.ActivityTestRule @@ -38,10 +47,12 @@ import org.catrobat.paintroid.R import org.catrobat.paintroid.adapter.ProjectAdapter import org.catrobat.paintroid.common.CATROBAT_IMAGE_ENDING import org.catrobat.paintroid.common.PNG_IMAGE_ENDING +import org.catrobat.paintroid.data.local.dao.ProjectDao +import org.catrobat.paintroid.data.local.database.ProjectDatabase import org.catrobat.paintroid.test.espresso.util.BitmapLocationProvider import org.catrobat.paintroid.test.espresso.util.DrawingSurfaceLocationProvider -import org.catrobat.paintroid.test.espresso.util.UiInteractions import org.catrobat.paintroid.test.espresso.util.UiInteractions.touchAt +import org.catrobat.paintroid.test.espresso.util.UiInteractions.waitFor import org.catrobat.paintroid.test.espresso.util.UiMatcher.atPosition import org.catrobat.paintroid.test.espresso.util.wrappers.DrawingSurfaceInteraction.onDrawingSurfaceView import org.catrobat.paintroid.test.espresso.util.wrappers.ToolBarViewInteraction @@ -53,7 +64,11 @@ import org.hamcrest.CoreMatchers.not import org.hamcrest.Description import org.hamcrest.Matcher import org.hamcrest.TypeSafeMatcher -import org.junit.* +import org.junit.Test +import org.junit.Rule +import org.junit.After +import org.junit.Before +import org.junit.Assert import org.junit.runner.RunWith import java.io.File import java.io.IOException @@ -62,6 +77,9 @@ import kotlin.collections.ArrayList @RunWith(AndroidJUnit4::class) class LandingPageActivityIntegrationTest { + private lateinit var database: ProjectDatabase + private lateinit var dao: ProjectDao + @get:Rule var launchActivityRule = ActivityTestRule(LandingPageActivity::class.java) @@ -78,12 +96,20 @@ class LandingPageActivityIntegrationTest { @Before fun setUp() { + database = Room.databaseBuilder(InstrumentationRegistry.getInstrumentation().targetContext, ProjectDatabase::class.java, "projects.db") + .allowMainThreadQueries() + .build() + dao = database.dao deletionFileList = ArrayList() activity = launchActivityRule.activity } @After fun tearDown() { + database.dao.deleteAllProjects() + database.clearAllTables() + database.close() + for (file in deletionFileList) { if (file != null && file.exists()) { Assert.assertTrue(file.delete()) @@ -108,7 +134,7 @@ class LandingPageActivityIntegrationTest { } @Test - fun testTopAppBarDisplayed(){ + fun testTopAppBarDisplayed() { onView(isAssignableFrom(Toolbar::class.java)) .check(matches(isDisplayed())) } @@ -120,7 +146,7 @@ class LandingPageActivityIntegrationTest { } @Test - fun testTwoFABDisplayed(){ + fun testTwoFABDisplayed() { onView(withId(R.id.pocketpaint_fab_load_image)) .check(matches(isDisplayed())) onView(withId(R.id.pocketpaint_fab_new_image)) @@ -128,7 +154,7 @@ class LandingPageActivityIntegrationTest { } @Test - fun testMyProjectsTextDisplayed(){ + fun testMyProjectsTextDisplayed() { onView(withText("My Projects")) .check(matches(isDisplayed())) } @@ -183,9 +209,8 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(UiInteractions.waitFor(300)) - onView(withContentDescription(R.string.abc_action_bar_up_description)) - .perform(click()); + onView(isRoot()).perform(waitFor(300)) + pressBack() onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) onView(withId(R.id.pocketpaint_projects_list)).check( @@ -207,9 +232,8 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(UiInteractions.waitFor(300)) - onView(withContentDescription(R.string.abc_action_bar_up_description)) - .perform(click()); + onView(isRoot()).perform(waitFor(300)) + pressBack() onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) onView(withId(R.id.pocketpaint_projects_list)) @@ -233,9 +257,8 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(UiInteractions.waitFor(300)) - onView(withContentDescription(R.string.abc_action_bar_up_description)) - .perform(click()); + onView(isRoot()).perform(waitFor(300)) + pressBack() onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) val imagesDirectory = @@ -265,42 +288,6 @@ class LandingPageActivityIntegrationTest { ) } - @Test - fun testSavedProjectOpen() { - onView(withId(R.id.pocketpaint_fab_new_image)) - .perform(click()) - onDrawingSurfaceView() - .perform(touchAt(DrawingSurfaceLocationProvider.MIDDLE)) - TopBarViewInteraction.onTopBarView() - .performOpenMoreOptions() - onView(withText(R.string.menu_save_project)) - .perform(click()) - onView(withId(R.id.pocketpaint_image_name_save_text)) - .perform(replaceText(PROJECT_NAME)) - onView(withText(R.string.save_button_text)) - .perform(click()) - onView(isRoot()).perform(UiInteractions.waitFor(300)) - onView(withContentDescription(R.string.abc_action_bar_up_description)) - .perform(click()); - onView(withId(R.id.pocketpaint_projects_list)) - .perform(RecyclerViewActions.scrollToPosition(position)) - onView(withId(R.id.pocketpaint_projects_list)) - .perform(actionOnItemAtPosition(position, click())) - onView( - allOf( - withId(R.id.pocketpaint_toolbar), - hasDescendant( - allOf( - isAssignableFrom(TextView::class.java), - withText(PROJECT_NAME) - ) - ) - ) - ).check( - matches(isDisplayed()) - ) - } - @Test fun testProjectOverFlowMenuDetailsDisplayed() { onView(withId(R.id.pocketpaint_fab_new_image)) @@ -315,9 +302,8 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(UiInteractions.waitFor(300)) - onView(withContentDescription(R.string.abc_action_bar_up_description)) - .perform(click()); + onView(isRoot()).perform(waitFor(300)) + pressBack() onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) onView(withId(R.id.pocketpaint_projects_list)).perform( @@ -358,9 +344,8 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(UiInteractions.waitFor(300)) - onView(withContentDescription(R.string.abc_action_bar_up_description)) - .perform(click()); + onView(isRoot()).perform(waitFor(300)) + pressBack() onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) onView(withId(R.id.pocketpaint_projects_list)).perform( @@ -385,7 +370,7 @@ class LandingPageActivityIntegrationTest { ) onView(withText(R.string.menu_project_detail_title)) .perform(click()) - onView(isRoot()).perform(UiInteractions.waitFor(300)) + onView(isRoot()).perform(waitFor(300)) onView(withText(android.R.string.ok)) .perform(click()) } @@ -404,9 +389,8 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(UiInteractions.waitFor(300)) - onView(withContentDescription(R.string.abc_action_bar_up_description)) - .perform(click()); + onView(isRoot()).perform(waitFor(300)) + pressBack() onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) onView(withId(R.id.pocketpaint_projects_list)).perform( @@ -447,9 +431,8 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(UiInteractions.waitFor(300)) - onView(withContentDescription(R.string.abc_action_bar_up_description)) - .perform(click()); + onView(isRoot()).perform(waitFor(300)) + pressBack() onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) onView(withId(R.id.pocketpaint_projects_list)).perform( @@ -500,13 +483,10 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(UiInteractions.waitFor(300)) - onView(withContentDescription(R.string.abc_action_bar_up_description)) - .perform(click()); - onView(withId(R.id.pocketpaint_projects_list)) - .perform(RecyclerViewActions.scrollToPosition(position)) + onView(isRoot()).perform(waitFor(300)) + pressBack() onView(withId(R.id.pocketpaint_projects_list)).perform( - actionOnItemAtPosition( + actionOnItemAtPosition( position, object : ViewAction { override fun getConstraints(): Matcher { @@ -527,17 +507,19 @@ class LandingPageActivityIntegrationTest { ) onView(withText(R.string.menu_project_delete_title)) .perform(click()) - onView(isRoot()).perform(UiInteractions.waitFor(100)) + onView(isRoot()).perform(waitFor(100)) onView(withId(android.R.id.button1)) .perform(closeSoftKeyboard()) .perform(scrollTo()) .perform(click()) + onView(isRoot()).perform(waitFor(300)) onView(withId(R.id.pocketpaint_projects_list)) - .perform(RecyclerViewActions.scrollToPosition(position)) + .perform(RecyclerViewActions.scrollToPosition(position)) + onView(isRoot()).perform(waitFor(300)) if (isRecyclerViewEmpty(recyclerViewMatcher)) { onView(withId(R.id.pocketpaint_projects_list)) .check(matches(not(hasDescendant(withId(R.id.iv_pocket_paint_project_thumbnail_image))))) - }else { + } else { onView(withId(R.id.pocketpaint_projects_list)) .check( matches( @@ -554,6 +536,38 @@ class LandingPageActivityIntegrationTest { } } + @Test + fun testSavedProjectOpen() { + onView(withId(R.id.pocketpaint_fab_new_image)) + .perform(click()) + onDrawingSurfaceView() + .perform(touchAt(DrawingSurfaceLocationProvider.MIDDLE)) + TopBarViewInteraction.onTopBarView() + .performOpenMoreOptions() + onView(withText(R.string.menu_save_project)) + .perform(click()) + onView(withId(R.id.pocketpaint_image_name_save_text)) + .perform(replaceText(PROJECT_NAME)) + onView(withText(R.string.save_button_text)) + .perform(click()) + onView(isRoot()).perform(waitFor(300)) + pressBack() + onView(withId(R.id.pocketpaint_projects_list)) + .perform(actionOnItemAtPosition(position, click())) + onView( + allOf( + withId(R.id.pocketpaint_toolbar), + hasDescendant( + allOf( + isAssignableFrom(TextView::class.java), + withText(PROJECT_NAME) + ) + ) + ) + ).check( + matches(isDisplayed()) + ) + } @Test fun testImagePreviewAfterProjectInsert() { onView(withId(R.id.pocketpaint_fab_new_image)) @@ -570,9 +584,8 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(UiInteractions.waitFor(300)) - onView(withContentDescription(R.string.abc_action_bar_up_description)) - .perform(click()); + onView(isRoot()).perform(waitFor(300)) + pressBack() onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) val imagesDirectory = @@ -634,5 +647,4 @@ class LandingPageActivityIntegrationTest { } return itemCount } - -} \ No newline at end of file +} diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt b/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt index 204553357b..18ad58fa99 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt @@ -14,7 +14,7 @@ import org.catrobat.paintroid.data.local.database.ProjectDatabase import org.catrobat.paintroid.data.local.database.ProjectDatabaseProvider import org.catrobat.paintroid.model.Project -class LandingPageActivity: AppCompatActivity() { +class LandingPageActivity : AppCompatActivity() { companion object { lateinit var projectDB: ProjectDatabase private lateinit var projectsRecyclerView: RecyclerView @@ -46,7 +46,7 @@ class LandingPageActivity: AppCompatActivity() { val imagePreviewClickListener = View.OnClickListener { allProjects = projectDB.dao.getProjects() latestProject = allProjects.firstOrNull() - if (allProjects.isNotEmpty()){ + if (allProjects.isNotEmpty()) { val loadProjectIntent = Intent(applicationContext, MainActivity::class.java).apply { putExtra(PROJECT_ACTION, "load_project") putExtra("PROJECT_URI", latestProject?.path) @@ -54,7 +54,7 @@ class LandingPageActivity: AppCompatActivity() { putExtra("PROJECT_IMAGE_PREVIEW_URI", latestProject?.imagePreviewPath) } startActivity(loadProjectIntent) - }else { + } else { val newProjectIntent = Intent(this, MainActivity::class.java).apply { putExtra(PROJECT_ACTION, "new_project") } @@ -92,7 +92,7 @@ class LandingPageActivity: AppCompatActivity() { projectAdapter = ProjectAdapter(this, projectsList, supportFragmentManager) projectsRecyclerView.adapter = projectAdapter - projectAdapter.setOnItemClickListener(object: ProjectAdapter.OnItemClickListener{ + projectAdapter.setOnItemClickListener(object : ProjectAdapter.OnItemClickListener { override fun onItemClick( position: Int, projectUri: String, @@ -109,4 +109,4 @@ class LandingPageActivity: AppCompatActivity() { } }) } -} \ No newline at end of file +} diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt index dc77a89a0c..40eb6d4169 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt @@ -65,7 +65,11 @@ import org.catrobat.paintroid.command.implementation.DefaultCommandFactory import org.catrobat.paintroid.command.implementation.DefaultCommandManager import org.catrobat.paintroid.command.implementation.LayerOpacityCommand import org.catrobat.paintroid.command.serialization.CommandSerializer -import org.catrobat.paintroid.common.* +import org.catrobat.paintroid.common.PAINTROID_PICTURE_NAME +import org.catrobat.paintroid.common.PAINTROID_PICTURE_PATH +import org.catrobat.paintroid.common.REQUEST_CODE_LOAD_PICTURE +import org.catrobat.paintroid.common.PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT +import org.catrobat.paintroid.common.CommonFactory import org.catrobat.paintroid.contract.LayerContracts import org.catrobat.paintroid.contract.MainActivityContracts import org.catrobat.paintroid.contract.MainActivityContracts.MainView @@ -187,7 +191,6 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { private const val APP_FRAGMENT_KEY = "customActivityState" private const val SHARED_PREFS_NAME = "preferences" private const val FIRST_LAUNCH_AFTER_INSTALL = "firstLaunchAfterInstall" - private var toolbar: Toolbar? = null var projectName: String? = null var projectUri: String? = null var projectImagePreviewUri: String? = null @@ -312,19 +315,12 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { workspace.resetPerspective() presenterMain.initializeFromCleanState(null, null) } - savedInstanceState == null -> { + savedInstanceState == null -> when (receivedIntent.getStringExtra(PROJECT_ACTION)) { "new_project" -> presenterMain.onNewImage() "new_image" -> presenterMain.onNewImage() "load_image" -> presenterMain.replaceImageClicked() - "load_project" -> { - projectName = receivedIntent.getStringExtra("PROJECT_NAME") - projectUri = receivedIntent.getStringExtra("PROJECT_URI") - projectImagePreviewUri = receivedIntent.getStringExtra("PROJECT_IMAGE_PREVIEW_URI") - val projectNameText = findViewById(R.id.pocketpaint_toolbar) - projectNameText.subtitle = projectName?.substringBefore(".catrobat-image") - presenterMain.loadScaledImage(projectUri?.toUri(), REQUEST_CODE_LOAD_PICTURE) - } + "load_project" -> loadProject(receivedIntent) else -> { val intent = intent val picturePath = intent.getStringExtra(PAINTROID_PICTURE_PATH) @@ -342,7 +338,6 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { workspace.perspective.setBitmapDimensions(layerModel.width, layerModel.height) } } - } else -> { val isFullscreen = savedInstanceState.getBoolean(IS_FULLSCREEN_KEY, false) val isSaved = savedInstanceState.getBoolean(IS_SAVED_KEY, false) @@ -376,6 +371,15 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { } } + private fun loadProject(receivedIntent: Intent) { + projectName = receivedIntent.getStringExtra("PROJECT_NAME") + projectUri = receivedIntent.getStringExtra("PROJECT_URI") + projectImagePreviewUri = receivedIntent.getStringExtra("PROJECT_IMAGE_PREVIEW_URI") + val projectNameText = findViewById(R.id.pocketpaint_toolbar) + projectNameText.subtitle = projectName?.substringBefore(".catrobat-image") + presenterMain.loadScaledImage(projectUri?.toUri(), REQUEST_CODE_LOAD_PICTURE) + } + override fun onWindowFocusChanged(hasFocus: Boolean) { super.onWindowFocusChanged(hasFocus) if (!hasFocus) { @@ -419,16 +423,15 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { ) R.id.pocketpaint_advanced_settings -> presenterMain.showAdvancedSettingsClicked() android.R.id.home -> - if(projectName?.let { + if (projectName?.let { FileIO.checkFileExists(FileIO.FileType.CATROBAT, it, this.contentResolver) - } == true){ + } == true) { FileIO.storeImageUri = Uri.parse(projectUri) FileIO.storeImagePreviewUri = Uri.parse(projectImagePreviewUri) presenterMain.switchBetweenVersions(PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT, false) presenterMain.finishActivity() - } - else { + } else { presenterMain.backToPocketCodeClicked() } else -> return false @@ -714,10 +717,10 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { override fun onBackPressed() { if (supportFragmentManager.isStateSaved) { super.onBackPressed() - }else if(projectName?.let { + } else if (projectName?.let { FileIO.checkFileExists(FileIO.FileType.CATROBAT, it, this.contentResolver) - } == true){ + } == true) { FileIO.storeImageUri = Uri.parse(projectUri) FileIO.storeImagePreviewUri = Uri.parse(projectImagePreviewUri) presenterMain.switchBetweenVersions(PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT, false) diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt b/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt index c42b806614..80c25676b8 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt @@ -3,7 +3,6 @@ package org.catrobat.paintroid.adapter import android.content.Context import android.net.Uri import android.os.Build -import android.os.Environment import android.provider.MediaStore import android.util.Log import android.view.LayoutInflater @@ -18,26 +17,31 @@ import androidx.recyclerview.widget.RecyclerView import org.catrobat.paintroid.LandingPageActivity.Companion.imagePreview import org.catrobat.paintroid.LandingPageActivity.Companion.latestProject import org.catrobat.paintroid.R -import org.catrobat.paintroid.common.CATROBAT_IMAGE_ENDING -import org.catrobat.paintroid.common.PNG_IMAGE_ENDING import org.catrobat.paintroid.common.PROJECT_DELETE_DIALOG_FRAGMENT_TAG import org.catrobat.paintroid.common.PROJECT_DETAILS_DIALOG_FRAGMENT_TAG import org.catrobat.paintroid.dialog.ProjectDeleteDialog import org.catrobat.paintroid.dialog.ProjectDetailsDialog import org.catrobat.paintroid.model.Project import java.io.File +import java.io.IOException import java.text.SimpleDateFormat import java.util.* import kotlin.collections.ArrayList -class ProjectAdapter(val context: Context, var projectList: ArrayList, val supportFragmentManager: FragmentManager): RecyclerView.Adapter() { +class ProjectAdapter(val context: Context, var projectList: ArrayList, private val supportFragmentManager: FragmentManager) : RecyclerView.Adapter() { private var itemClickListener: OnItemClickListener? = null - class ItemViewHolder(itemView: View): RecyclerView.ViewHolder(itemView){ + companion object { + private val TAG = ProjectAdapter::class.java.simpleName + private const val MEGABYTE = 1.0 + private const val KILOBYTE = 1024 + } + + class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val itemImageView: ImageView = itemView.findViewById(R.id.iv_pocket_paint_project_thumbnail_image) val itemNameText: TextView = itemView.findViewById(R.id.tv_pocket_paint_project_name) val itemLastModifiedText: TextView = itemView.findViewById(R.id.tv_pocket_paint_project_lastmodified) - val itemMoreOption : ImageView = itemView.findViewById(R.id.iv_pocket_paint_project_more) + val itemMoreOption: ImageView = itemView.findViewById(R.id.iv_pocket_paint_project_more) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder { @@ -61,22 +65,22 @@ class ProjectAdapter(val context: Context, var projectList: ArrayList, val formattedLastModified = dateTimeFormat.format(Date(lastModifiedDate)) val formattedCreationDate = dateTimeFormat.format(Date(creationDate)) - val formattedSize = if (size >= 1.0) { + val formattedSize = if (size >= MEGABYTE) { val formattedValue = String.format("%.2f", size) "${formattedValue}MB" } else { - val formattedValue = String.format("%.1f", size * 1024) + val formattedValue = String.format("%.1f", size * KILOBYTE) "${formattedValue}KB" } val imageFile = getFileFromUri(Uri.parse(item.imagePreviewPath), context) - val imageUri: Uri? = if(imageFile?.exists() == true) { + val imageUri: Uri? = if (imageFile?.exists() == true) { Uri.fromFile(imageFile) } else { null } - if(imageUri != null) { + if (imageUri != null) { holder.itemImageView.setImageURI(Uri.parse(item.imagePreviewPath)) } else { holder.itemImageView.setImageResource(R.drawable.pocketpaint_logo_small) @@ -89,14 +93,14 @@ class ProjectAdapter(val context: Context, var projectList: ArrayList, val popupMenu = PopupMenu(view.context, view) popupMenu.inflate(R.menu.menu_pocketpaint_project_details) popupMenu.setOnMenuItemClickListener { menuItem -> - when(menuItem.itemId){ + when (menuItem.itemId) { R.id.project_details -> { val projectDetails = ProjectDetailsDialog(name, resolution, formattedLastModified, formattedCreationDate, format, formattedSize) projectDetails.show(supportFragmentManager, PROJECT_DETAILS_DIALOG_FRAGMENT_TAG) true } R.id.project_delete -> { - val projectDelete = ProjectDeleteDialog(id, name) + val projectDelete = ProjectDeleteDialog(id, name, position) projectDelete.show(supportFragmentManager, PROJECT_DELETE_DIALOG_FRAGMENT_TAG) true } @@ -133,8 +137,8 @@ class ProjectAdapter(val context: Context, var projectList: ArrayList, try { file = File(uri.path) return file - } catch (e: Exception) { - e.printStackTrace() + } catch (e: IOException) { + Log.e(TAG, "Error occurred while accessing the file: ${e.message}") } return null } @@ -158,7 +162,8 @@ class ProjectAdapter(val context: Context, var projectList: ArrayList, } } - fun removeProject(projectId: Int) { + fun removeProject(projectId: Int, position: Int) { + projectList.removeAt(position) val iterator = projectList.iterator() while (iterator.hasNext()) { val project = iterator.next() @@ -175,15 +180,13 @@ class ProjectAdapter(val context: Context, var projectList: ArrayList, } } - override fun getItemCount(): Int { - return projectList.size - } + override fun getItemCount(): Int = projectList.size - interface OnItemClickListener{ + interface OnItemClickListener { fun onItemClick(position: Int, projectUri: String, projectName: String, projectImagePreviewUri: String) } - fun setOnItemClickListener(listener: OnItemClickListener){ + fun setOnItemClickListener(listener: OnItemClickListener) { itemClickListener = listener } -} \ No newline at end of file +} diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt b/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt index 3c8222668a..daca90a853 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt @@ -22,4 +22,4 @@ interface ProjectDao { @Query("DELETE FROM Project") fun deleteAllProjects() -} \ No newline at end of file +} diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/data/local/database/ProjectDatabase.kt b/Paintroid/src/main/java/org/catrobat/paintroid/data/local/database/ProjectDatabase.kt index 929fa5d019..c7a03158be 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/data/local/database/ProjectDatabase.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/data/local/database/ProjectDatabase.kt @@ -9,7 +9,7 @@ import org.catrobat.paintroid.model.Project entities = [Project::class], version = 1 ) -abstract class ProjectDatabase: RoomDatabase() { +abstract class ProjectDatabase : RoomDatabase() { abstract val dao: ProjectDao -} \ No newline at end of file +} diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/data/local/database/ProjectDatabaseProvider.kt b/Paintroid/src/main/java/org/catrobat/paintroid/data/local/database/ProjectDatabaseProvider.kt index 8698cb60ac..2a7b031076 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/data/local/database/ProjectDatabaseProvider.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/data/local/database/ProjectDatabaseProvider.kt @@ -6,15 +6,15 @@ import androidx.room.Room object ProjectDatabaseProvider { var projectDatabase: ProjectDatabase? = null - fun getDatabase(context: Context): ProjectDatabase{ - return projectDatabase?: synchronized(this){ + fun getDatabase(context: Context): ProjectDatabase { + return projectDatabase ?: synchronized(this) { projectDatabase ?: buildDatabase(context).also { projectDatabase = it } } } - fun buildDatabase(context: Context): ProjectDatabase{ + fun buildDatabase(context: Context): ProjectDatabase { return Room.databaseBuilder(context, ProjectDatabase::class.java, "projects.db") .allowMainThreadQueries() .build() } -} \ No newline at end of file +} diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDeleteDialog.kt b/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDeleteDialog.kt index 0c997cc512..3773854668 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDeleteDialog.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDeleteDialog.kt @@ -9,7 +9,7 @@ import org.catrobat.paintroid.LandingPageActivity.Companion.projectAdapter import org.catrobat.paintroid.LandingPageActivity.Companion.projectDB import org.catrobat.paintroid.R -class ProjectDeleteDialog(private val projectId: Int, private val name: String) : AppCompatDialogFragment() { +class ProjectDeleteDialog(private val projectId: Int, private val name: String, private val position: Int) : AppCompatDialogFragment() { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val message = getString(R.string.pocketpaint_project_delete_title_dialog, name) @@ -18,10 +18,10 @@ class ProjectDeleteDialog(private val projectId: Int, private val name: String) .setMessage(R.string.pocketpaint_project_delete_message_dialog) .setPositiveButton(R.string.pocketpaint_ok) { _, _ -> projectDB.dao.deleteProject(projectId) - projectAdapter.removeProject(projectId) + projectAdapter.removeProject(projectId, position) projectAdapter.notifyDataSetChanged() } .setNegativeButton(R.string.pocketpaint_cancel) { _, _ -> dismiss() } .create() } -} \ No newline at end of file +} diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDetailsDialog.kt b/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDetailsDialog.kt index c4012f8c74..7dce39c095 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDetailsDialog.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDetailsDialog.kt @@ -31,4 +31,4 @@ class ProjectDetailsDialog( .setPositiveButton(R.string.pocketpaint_ok) { _, _ -> dismiss() } .create() } -} \ No newline at end of file +} diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/dialog/SaveInformationDialog.kt b/Paintroid/src/main/java/org/catrobat/paintroid/dialog/SaveInformationDialog.kt index 80af3580c3..bb6a54ac4d 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/dialog/SaveInformationDialog.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/dialog/SaveInformationDialog.kt @@ -25,9 +25,14 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.* +import android.widget.ArrayAdapter +import android.widget.AdapterView import android.widget.AdapterView.OnItemSelectedListener +import android.widget.ImageButton +import android.widget.SeekBar import android.widget.SeekBar.OnSeekBarChangeListener +import android.widget.Spinner +import android.widget.TextView import androidx.appcompat.app.AlertDialog import androidx.appcompat.widget.AppCompatEditText import androidx.appcompat.widget.AppCompatImageButton @@ -131,12 +136,11 @@ class SaveInformationDialog : spinner.visibility = View.VISIBLE } - @SuppressLint("InflateParams") override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { inflater = requireActivity().layoutInflater val customLayout = inflater.inflate(R.layout.dialog_pocketpaint_save, null) - val dialogTitle = if(permission == PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT){ + val dialogTitle = if (permission == PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT) { R.string.dialog_save_project_title } else { R.string.dialog_save_image_title diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/SaveImage.kt b/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/SaveImage.kt index 97bb1ce2f0..771e108a2a 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/SaveImage.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/SaveImage.kt @@ -40,6 +40,7 @@ import org.catrobat.paintroid.command.serialization.CommandSerializer import org.catrobat.paintroid.common.PNG_IMAGE_ENDING import org.catrobat.paintroid.contract.LayerContracts import org.catrobat.paintroid.model.Project +import org.catrobat.paintroid.model.SaveImageOptions import java.io.File import java.io.IOException import java.lang.ref.WeakReference @@ -51,9 +52,7 @@ class SaveImage( private val layerModel: LayerContracts.Model, private val commandSerializer: CommandSerializer, private var uri: Uri?, - private var imagePreviewUri: Uri?, - private val saveAsCopy: Boolean, - private val saveProject: Boolean, + private val saveImageOptions: SaveImageOptions, private val context: Context, private val scopeIO: CoroutineScope, private val idlingResource: CountingIdlingResource @@ -62,6 +61,8 @@ class SaveImage( companion object { private val TAG = SaveImage::class.java.simpleName + private const val KILOBYTE = 1024 + var currentUri: Uri? = null } private fun getImageUri( @@ -86,12 +87,12 @@ class SaveImage( Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString() val pathToFile = imagesDirectory + File.separator + filename + "." + PNG_IMAGE_ENDING val imageFile = File(pathToFile) - return if (imagePreviewUri == null || !imageFile.exists()) { + return if (saveImageOptions.imagePreviewUri == null || !imageFile.exists()) { val imagePreviewFilename = filename.replace(".catrobat-image", ".png") val imageUri = FileIO.saveBitmapToFile(imagePreviewFilename, bitmap, callback.contentResolver, context) imageUri } else { - imagePreviewUri?.let { FileIO.saveBitmapToUri(it, bitmap, context) } + saveImageOptions.imagePreviewUri?.let { FileIO.saveBitmapToUri(it, bitmap, context) } } } @@ -140,48 +141,13 @@ class SaveImage( callback.onSaveImagePreExecute(requestCode) } - var currentUri: Uri? = null - var imagePreviewPath: Uri? = null scopeIO.launch { try { idlingResource.increment() val bitmap = layerModel.getBitmapOfAllLayers() val filename = FileIO.defaultFileName - if (saveProject) { - FileIO.fileType = FileIO.FileType.CATROBAT - currentUri = if (uri != null) { - uri?.let { - commandSerializer.overWriteFile(filename, it, callback.contentResolver) - } - } else { - commandSerializer.writeToFile(filename) - } - imagePreviewPath = getImagePreviewUri(callback, bitmap) - val date = Calendar.getInstance().timeInMillis - if(uri != null){ - uri?.let { - projectDB.dao.updateProject(filename, imagePreviewPath.toString(), currentUri.toString(), date) - projectAdapter.updateProject(filename, imagePreviewPath.toString(), currentUri.toString(), date) - } - } else { - val dimensions = getImageDimensions(imagePreviewPath) - val size = getImageSize(imagePreviewPath) - val project = Project( - filename, - currentUri.toString(), - date, - date, - "${dimensions?.first} x ${dimensions?.second}", - FileIO.fileType.toString(), - size, - imagePreviewPath.toString() - ) - projectDB.dao.insertProject(project) - projectAdapter.insertProject(project) - projectName = filename - projectUri = currentUri.toString() - projectImagePreviewUri = imagePreviewPath.toString() - } + if (saveImageOptions.saveProject) { + executeSaveProject(filename, bitmap, callback) } else { currentUri = if (FileIO.fileType == FileIO.FileType.ORA) { val layers = layerModel.layers @@ -197,11 +163,7 @@ class SaveImage( } else if (FileIO.fileType == FileIO.FileType.CATROBAT) { if (uri != null) { uri?.let { - commandSerializer.overWriteFile( - filename, - it, - callback.contentResolver - ) + commandSerializer.overWriteFile(filename, it, callback.contentResolver) } } else { commandSerializer.writeToFile(filename) @@ -221,12 +183,49 @@ class SaveImage( withContext(Dispatchers.Main) { if (!callback.isFinishing) { - callback.onSaveImagePostExecute(requestCode, currentUri, saveAsCopy) + callback.onSaveImagePostExecute(requestCode, currentUri, saveImageOptions.saveAsCopy) } } } } + private fun executeSaveProject(filename: String, bitmap: Bitmap?, callback: SaveImageCallback) { + FileIO.fileType = FileIO.FileType.CATROBAT + currentUri = if (uri != null) { + uri?.let { + commandSerializer.overWriteFile(filename, it, callback.contentResolver) + } + } else { + commandSerializer.writeToFile(filename) + } + val imagePreviewPath = getImagePreviewUri(callback, bitmap) + val date = Calendar.getInstance().timeInMillis + if (uri != null) { + uri?.let { + projectDB.dao.updateProject(filename, imagePreviewPath.toString(), currentUri.toString(), date) + projectAdapter.updateProject(filename, imagePreviewPath.toString(), currentUri.toString(), date) + } + } else { + val dimensions = getImageDimensions(imagePreviewPath) + val size = getImageSize(imagePreviewPath) + val project = Project( + filename, + currentUri.toString(), + date, + date, + "${dimensions?.first} x ${dimensions?.second}", + FileIO.fileType.toString(), + size, + imagePreviewPath.toString() + ) + projectDB.dao.insertProject(project) + projectAdapter.insertProject(project) + projectName = filename + projectUri = currentUri.toString() + projectImagePreviewUri = imagePreviewPath.toString() + } + } + private fun getImageDimensions(uri: Uri?): Pair? { val inputStream = uri?.let { context.contentResolver.openInputStream(it) } val options = BitmapFactory.Options() @@ -246,10 +245,10 @@ class SaveImage( try { val inputStream = uri?.let { context.contentResolver.openInputStream(it) } val bytes = inputStream?.available()?.toLong() ?: 0 - size = bytes.toDouble() / (1024 * 1024) + size = bytes.toDouble() / (KILOBYTE * KILOBYTE) inputStream?.close() } catch (e: IOException) { - e.printStackTrace() + Log.e(TAG, "Error occurred while getting the image size: ${e.message}") } return size } diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/model/Project.kt b/Paintroid/src/main/java/org/catrobat/paintroid/model/Project.kt index ba375c32d3..6a8e2e6389 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/model/Project.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/model/Project.kt @@ -15,4 +15,4 @@ data class Project( val imagePreviewPath: String, @PrimaryKey(autoGenerate = true) val id: Int = 0 -) \ No newline at end of file +) diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/model/SaveImageOptions.kt b/Paintroid/src/main/java/org/catrobat/paintroid/model/SaveImageOptions.kt new file mode 100644 index 0000000000..f6a2201770 --- /dev/null +++ b/Paintroid/src/main/java/org/catrobat/paintroid/model/SaveImageOptions.kt @@ -0,0 +1,5 @@ +package org.catrobat.paintroid.model + +import android.net.Uri + +data class SaveImageOptions(val imagePreviewUri: Uri?, val saveAsCopy: Boolean, val saveProject: Boolean) diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt b/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt index 6f9d6b7ace..6061aeb9ab 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt @@ -54,12 +54,33 @@ import org.catrobat.paintroid.colorpicker.ColorHistory import org.catrobat.paintroid.command.CommandFactory import org.catrobat.paintroid.command.CommandManager import org.catrobat.paintroid.command.serialization.CommandSerializer -import org.catrobat.paintroid.common.* import org.catrobat.paintroid.common.MainActivityConstants.ActivityRequestCode import org.catrobat.paintroid.common.MainActivityConstants.CreateFileRequestCode import org.catrobat.paintroid.common.MainActivityConstants.LoadImageRequestCode import org.catrobat.paintroid.common.MainActivityConstants.PermissionRequestCode import org.catrobat.paintroid.common.MainActivityConstants.SaveImageRequestCode +import org.catrobat.paintroid.common.PERMISSION_REQUEST_CODE_REPLACE_PICTURE +import org.catrobat.paintroid.common.PERMISSION_REQUEST_CODE_IMPORT_PICTURE +import org.catrobat.paintroid.common.PERMISSION_EXTERNAL_STORAGE_SAVE_CONFIRMED_LOAD_NEW +import org.catrobat.paintroid.common.PERMISSION_EXTERNAL_STORAGE_SAVE_CONFIRMED_NEW_EMPTY +import org.catrobat.paintroid.common.PERMISSION_EXTERNAL_STORAGE_SAVE_CONFIRMED_FINISH +import org.catrobat.paintroid.common.PERMISSION_EXTERNAL_STORAGE_SAVE_COPY +import org.catrobat.paintroid.common.PERMISSION_EXTERNAL_STORAGE_SAVE +import org.catrobat.paintroid.common.PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT +import org.catrobat.paintroid.common.REQUEST_CODE_LOAD_PICTURE +import org.catrobat.paintroid.common.REQUEST_CODE_INTRO +import org.catrobat.paintroid.common.REQUEST_CODE_IMPORT_PNG +import org.catrobat.paintroid.common.LOAD_IMAGE_IMPORT_PNG +import org.catrobat.paintroid.common.LOAD_IMAGE_DEFAULT +import org.catrobat.paintroid.common.RESULT_INTRO_MW_NOT_SUPPORTED +import org.catrobat.paintroid.common.SAVE_IMAGE_DEFAULT +import org.catrobat.paintroid.common.SAVE_IMAGE_FINISH +import org.catrobat.paintroid.common.SAVE_IMAGE_LOAD_NEW +import org.catrobat.paintroid.common.SAVE_IMAGE_NEW_EMPTY +import org.catrobat.paintroid.common.SAVE_PROJECT_DEFAULT +import org.catrobat.paintroid.common.LOAD_IMAGE_CATROID +import org.catrobat.paintroid.common.CREATE_FILE_DEFAULT +import org.catrobat.paintroid.common.TEMP_PICTURE_NAME import org.catrobat.paintroid.contract.MainActivityContracts import org.catrobat.paintroid.contract.MainActivityContracts.Interactor import org.catrobat.paintroid.contract.MainActivityContracts.MainView @@ -512,13 +533,12 @@ open class MainActivityPresenter( ) checkForDefaultFilename() } - PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT -> { + PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT -> saveProjectConfirmClicked( SAVE_PROJECT_DEFAULT, FileIO.storeImageUri, FileIO.storeImagePreviewUri, ) - } PERMISSION_EXTERNAL_STORAGE_SAVE_COPY -> { saveCopyConfirmClicked( SAVE_IMAGE_DEFAULT, diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/ui/MainActivityInteractor.kt b/Paintroid/src/main/java/org/catrobat/paintroid/ui/MainActivityInteractor.kt index f6bce9c854..aeb9bd22ef 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/ui/MainActivityInteractor.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/ui/MainActivityInteractor.kt @@ -32,6 +32,7 @@ import org.catrobat.paintroid.iotasks.LoadImage import org.catrobat.paintroid.iotasks.LoadImage.LoadImageCallback import org.catrobat.paintroid.iotasks.SaveImage import org.catrobat.paintroid.iotasks.SaveImage.SaveImageCallback +import org.catrobat.paintroid.model.SaveImageOptions class MainActivityInteractor(private val idlingResource: CountingIdlingResource) : Interactor { private val scopeIO = CoroutineScope(Dispatchers.IO) @@ -44,11 +45,17 @@ class MainActivityInteractor(private val idlingResource: CountingIdlingResource) uri: Uri?, context: Context ) { - SaveImage(callback, requestCode, layerModel, commandSerializer, uri, null, true, - saveProject = false, - context = context, - scopeIO = scopeIO, - idlingResource = idlingResource + val saveImageOptions = SaveImageOptions(null, saveAsCopy = true, saveProject = false) + SaveImage( + callback, + requestCode, + layerModel, + commandSerializer, + uri, + saveImageOptions, + context, + scopeIO, + idlingResource ).execute() } @@ -64,12 +71,17 @@ class MainActivityInteractor(private val idlingResource: CountingIdlingResource) uri: Uri?, context: Context ) { - SaveImage(callback, requestCode, layerModel, commandSerializer, uri, null, - saveAsCopy = false, - saveProject = false, - context = context, - scopeIO = scopeIO, - idlingResource = idlingResource + val saveImageOptions = SaveImageOptions(null, saveAsCopy = false, saveProject = false) + SaveImage( + callback, + requestCode, + layerModel, + commandSerializer, + uri, + saveImageOptions, + context, + scopeIO, + idlingResource ).execute() } @@ -82,7 +94,18 @@ class MainActivityInteractor(private val idlingResource: CountingIdlingResource) imagePreviewUri: Uri?, context: Context ) { - SaveImage(callback, requestCode, layerModel, commandSerializer, uri, imagePreviewUri, false, true, context, scopeIO, idlingResource).execute() + val saveImageOptions = SaveImageOptions(imagePreviewUri, saveAsCopy = false, saveProject = true) + SaveImage( + callback, + requestCode, + layerModel, + commandSerializer, + uri, + saveImageOptions, + context, + scopeIO, + idlingResource + ).execute() } override fun loadFile( From b17c2cf308e3738f53a94fe779763424d8579f9c Mon Sep 17 00:00:00 2001 From: Rd Date: Sun, 11 Jun 2023 09:51:53 +0530 Subject: [PATCH 36/52] Fixed Lint warnings --- .../catrobat/paintroid/dialog/ProjectDeleteDialog.kt | 2 +- .../res/layout/activity_pocketpaint_landing_page.xml | 12 ++++++++---- .../src/main/res/layout/pocketpaint_item_project.xml | 12 +++++++----- Paintroid/src/main/res/values/string.xml | 10 ++++++++++ 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDeleteDialog.kt b/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDeleteDialog.kt index 3773854668..afdd02bcbf 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDeleteDialog.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDeleteDialog.kt @@ -19,7 +19,7 @@ class ProjectDeleteDialog(private val projectId: Int, private val name: String, .setPositiveButton(R.string.pocketpaint_ok) { _, _ -> projectDB.dao.deleteProject(projectId) projectAdapter.removeProject(projectId, position) - projectAdapter.notifyDataSetChanged() + projectAdapter.notifyItemRemoved(position) } .setNegativeButton(R.string.pocketpaint_cancel) { _, _ -> dismiss() } .create() diff --git a/Paintroid/src/main/res/layout/activity_pocketpaint_landing_page.xml b/Paintroid/src/main/res/layout/activity_pocketpaint_landing_page.xml index 69973300ef..da82f64232 100644 --- a/Paintroid/src/main/res/layout/activity_pocketpaint_landing_page.xml +++ b/Paintroid/src/main/res/layout/activity_pocketpaint_landing_page.xml @@ -23,6 +23,7 @@ android:layout_height="match_parent" android:scaleType="center" android:background="@drawable/pocketpaint_checkeredbg_repeat" + android:contentDescription="@string/pocketpaint_landing_page_image_preview" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -30,9 +31,10 @@ @@ -61,6 +63,7 @@ android:backgroundTint="@color/pocketpaint_color_picker_orange3" android:elevation="5dp" android:src="@drawable/ic_pocketpaint_download" + android:contentDescription="@string/pocketpaint_landing_page_fab_load_image" app:layout_constraintBottom_toTopOf="@+id/pocketpaint_fab_new_image" app:layout_constraintEnd_toEndOf="parent" /> @@ -73,6 +76,7 @@ android:elevation="5dp" android:layout_margin="15dp" android:src="@drawable/ic_pocketpaint_plus_enabled" + android:contentDescription="@string/pocketpaint_landing_page_fab_new_image" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" /> diff --git a/Paintroid/src/main/res/layout/pocketpaint_item_project.xml b/Paintroid/src/main/res/layout/pocketpaint_item_project.xml index aa8f35f4cd..d7c6be92d8 100644 --- a/Paintroid/src/main/res/layout/pocketpaint_item_project.xml +++ b/Paintroid/src/main/res/layout/pocketpaint_item_project.xml @@ -26,9 +26,10 @@ android:id="@+id/iv_pocket_paint_project_thumbnail_image" android:layout_width="50dp" android:layout_height="80dp" - android:layout_marginLeft="10dp" + android:layout_marginStart="10dp" android:layout_marginTop="5dp" android:src="@drawable/pocketpaint_logo_small" + android:contentDescription="@string/pocketpaint_landing_project_image_thumbnail" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -36,10 +37,10 @@ android:id="@+id/tv_pocket_paint_project_name" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="Project Name" + android:text="@string/pocketpaint_project_name" android:textSize="20sp" android:textColor="@color/pocketpaint_color_picker_white" - android:layout_marginLeft="20dp" + android:layout_marginStart="20dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toEndOf="@+id/iv_pocket_paint_project_thumbnail_image" app:layout_constraintTop_toTopOf="parent" @@ -49,8 +50,8 @@ android:id="@+id/tv_pocket_paint_project_lastmodified" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginLeft="20dp" - android:text="Project Name" + android:layout_marginStart="20dp" + android:text="@string/pocketpaint_project_last_modified" android:textColor="@color/pocketpaint_lighter_gray" android:textSize="13sp" app:layout_constraintBottom_toBottomOf="parent" @@ -64,6 +65,7 @@ android:layout_height="32dp" android:src="@drawable/ic_pocketpaint_more" android:layout_marginTop="24dp" + android:contentDescription="@string/pocketpaint_landing_project_more_options" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.917" app:layout_constraintStart_toEndOf="@+id/tv_pocket_paint_project_name" diff --git a/Paintroid/src/main/res/values/string.xml b/Paintroid/src/main/res/values/string.xml index 055ffb48fc..b32072af02 100644 --- a/Paintroid/src/main/res/values/string.xml +++ b/Paintroid/src/main/res/values/string.xml @@ -51,6 +51,14 @@ Copy Cut + Image Preview + Edit Image + Load Image + New Image + Project Thumbnail + Project Options + My Projects + Stickers @string/button_import_image @string/menu_save_image @@ -190,6 +198,8 @@ Resolution: %s<br />Last modified: %s<br />Creation date: %s<br />Format: %s<br />Size: %s Delete %s Do you really want to delete your Project? + Project Name + DD/MM/YYYY HH:MM This app needs the requested permission to function properly. In order to save images to the local memory, the app needs read and write access to it. This app needs the requested permission to function properly. In order to save images to the local memory, the app needs read and write access to it. From cc3d54d446ce749cc21ce63790d6d4c6fbcf0769 Mon Sep 17 00:00:00 2001 From: Rd Date: Sun, 11 Jun 2023 12:34:39 +0530 Subject: [PATCH 37/52] Fixed SprayTool context issue --- .../main/java/org/catrobat/paintroid/tools/Tool.kt | 2 +- .../paintroid/tools/implementation/BaseTool.kt | 13 +++++++++---- .../implementation/BaseToolWithRectangleShape.kt | 2 +- .../tools/implementation/BaseToolWithShape.kt | 6 +++--- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/tools/Tool.kt b/Paintroid/src/main/java/org/catrobat/paintroid/tools/Tool.kt index 5c320e929b..2f637d1639 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/tools/Tool.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/tools/Tool.kt @@ -55,7 +55,7 @@ interface Tool { pointY: Float, screenWidth: Int, screenHeight: Int - ): Point + ): Point? fun handleUpAnimations(coordinate: PointF?) fun handleDownAnimations(coordinate: PointF?) diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/BaseTool.kt b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/BaseTool.kt index 156d3c2aa3..61ff5a5914 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/BaseTool.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/BaseTool.kt @@ -54,7 +54,10 @@ abstract class BaseTool( protected val movedDistance: PointF @JvmField - protected var scrollBehavior: ScrollBehavior + protected var scrollTolerance: Int? = null + + @JvmField + protected var scrollBehavior: ScrollBehavior? = null @JvmField var previousEventCoordinate: PointF? @@ -63,8 +66,10 @@ abstract class BaseTool( protected var commandFactory: CommandFactory = DefaultCommandFactory() init { - val scrollTolerance = contextCallback.scrollTolerance - scrollBehavior = PointScrollBehavior(scrollTolerance) + contextCallback?.let { callback -> + scrollTolerance = callback.scrollTolerance + } + scrollBehavior = scrollTolerance?.let { PointScrollBehavior(it) } movedDistance = PointF(0f, 0f) previousEventCoordinate = PointF(0f, 0f) if (toolPaint != null && toolPaint.paint != null && toolPaint.paint.pathEffect != null) { @@ -117,7 +122,7 @@ abstract class BaseTool( pointY: Float, screenWidth: Int, screenHeight: Int - ): Point = scrollBehavior.getScrollDirection(pointX, pointY, screenWidth, screenHeight) + ): Point? = scrollBehavior?.getScrollDirection(pointX, pointY, screenWidth, screenHeight) override fun handToolMode(): Boolean = false } diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/BaseToolWithRectangleShape.kt b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/BaseToolWithRectangleShape.kt index 18bb4ec5e2..006262996c 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/BaseToolWithRectangleShape.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/BaseToolWithRectangleShape.kt @@ -740,7 +740,7 @@ abstract class BaseToolWithRectangleShape( pointY: Float, screenWidth: Int, screenHeight: Int - ): Point { + ): Point? { return if (currentAction == FloatingBoxAction.MOVE || currentAction == FloatingBoxAction.RESIZE) { super.getAutoScrollDirection(pointX, pointY, screenWidth, screenHeight) } else Point(0, 0) diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/BaseToolWithShape.kt b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/BaseToolWithShape.kt index 5d5bea966c..8b5762acc3 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/BaseToolWithShape.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/BaseToolWithShape.kt @@ -124,14 +124,14 @@ abstract class BaseToolWithShape @SuppressLint("VisibleForTests") constructor( pointY: Float, screenWidth: Int, screenHeight: Int - ): Point { + ): Point? { val surfaceToolPosition = workspace.getSurfacePointFromCanvasPoint(toolPosition) - return scrollBehavior.getScrollDirection( + return scrollBehavior?.getScrollDirection( surfaceToolPosition.x, surfaceToolPosition.y, screenWidth, screenHeight - ) + ) ?: null } abstract fun onClickOnButton() From 71aea37b78b14b081c3d7934dc1cb44bd89721fe Mon Sep 17 00:00:00 2001 From: Rd Date: Sun, 11 Jun 2023 16:45:52 +0530 Subject: [PATCH 38/52] Landing Page Tests Trigger --- .../LandingPageActivityIntegrationTest.kt | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt index 2b97439ab9..bae782209b 100644 --- a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt +++ b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt @@ -26,18 +26,16 @@ import androidx.test.espresso.UiController import androidx.test.espresso.ViewAction import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.action.ViewActions.replaceText -import androidx.test.espresso.action.ViewActions.closeSoftKeyboard -import androidx.test.espresso.action.ViewActions.scrollTo import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.contrib.RecyclerViewActions import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition import androidx.test.espresso.intent.Intents import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction +import androidx.test.espresso.matcher.RootMatchers import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.espresso.matcher.ViewMatchers.withId -import androidx.test.espresso.matcher.ViewMatchers.isRoot import androidx.test.espresso.matcher.ViewMatchers.hasDescendant import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry @@ -52,7 +50,6 @@ import org.catrobat.paintroid.data.local.database.ProjectDatabase import org.catrobat.paintroid.test.espresso.util.BitmapLocationProvider import org.catrobat.paintroid.test.espresso.util.DrawingSurfaceLocationProvider import org.catrobat.paintroid.test.espresso.util.UiInteractions.touchAt -import org.catrobat.paintroid.test.espresso.util.UiInteractions.waitFor import org.catrobat.paintroid.test.espresso.util.UiMatcher.atPosition import org.catrobat.paintroid.test.espresso.util.wrappers.DrawingSurfaceInteraction.onDrawingSurfaceView import org.catrobat.paintroid.test.espresso.util.wrappers.ToolBarViewInteraction @@ -74,6 +71,8 @@ import java.io.File import java.io.IOException import kotlin.collections.ArrayList +private const val THREAD_WAITING_TIME: Long = 300 + @RunWith(AndroidJUnit4::class) class LandingPageActivityIntegrationTest { @@ -209,7 +208,7 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(waitFor(300)) + Thread.sleep(THREAD_WAITING_TIME) pressBack() onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) @@ -232,7 +231,7 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(waitFor(300)) + Thread.sleep(THREAD_WAITING_TIME) pressBack() onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) @@ -257,7 +256,7 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(waitFor(300)) + Thread.sleep(THREAD_WAITING_TIME) pressBack() onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) @@ -302,7 +301,7 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(waitFor(300)) + Thread.sleep(THREAD_WAITING_TIME) pressBack() onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) @@ -344,7 +343,7 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(waitFor(300)) + Thread.sleep(THREAD_WAITING_TIME) pressBack() onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) @@ -370,8 +369,9 @@ class LandingPageActivityIntegrationTest { ) onView(withText(R.string.menu_project_detail_title)) .perform(click()) - onView(isRoot()).perform(waitFor(300)) + Thread.sleep(THREAD_WAITING_TIME) onView(withText(android.R.string.ok)) + .inRoot(RootMatchers.isDialog()) .perform(click()) } @@ -389,7 +389,7 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(waitFor(300)) + Thread.sleep(THREAD_WAITING_TIME) pressBack() onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) @@ -431,7 +431,7 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(waitFor(300)) + Thread.sleep(THREAD_WAITING_TIME) pressBack() onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) @@ -457,7 +457,8 @@ class LandingPageActivityIntegrationTest { ) onView(withText(R.string.menu_project_delete_title)) .perform(click()) - onView(withText(R.string.cancel_button_text)) + onView(withId(android.R.id.button2)) + .inRoot(RootMatchers.isDialog()) .perform(click()) onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) @@ -483,7 +484,7 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(waitFor(300)) + Thread.sleep(THREAD_WAITING_TIME) pressBack() onView(withId(R.id.pocketpaint_projects_list)).perform( actionOnItemAtPosition( @@ -507,15 +508,12 @@ class LandingPageActivityIntegrationTest { ) onView(withText(R.string.menu_project_delete_title)) .perform(click()) - onView(isRoot()).perform(waitFor(100)) + Thread.sleep(THREAD_WAITING_TIME) onView(withId(android.R.id.button1)) - .perform(closeSoftKeyboard()) - .perform(scrollTo()) + .inRoot(RootMatchers.isDialog()) .perform(click()) - onView(isRoot()).perform(waitFor(300)) onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) - onView(isRoot()).perform(waitFor(300)) if (isRecyclerViewEmpty(recyclerViewMatcher)) { onView(withId(R.id.pocketpaint_projects_list)) .check(matches(not(hasDescendant(withId(R.id.iv_pocket_paint_project_thumbnail_image))))) @@ -550,7 +548,7 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(waitFor(300)) + Thread.sleep(THREAD_WAITING_TIME) pressBack() onView(withId(R.id.pocketpaint_projects_list)) .perform(actionOnItemAtPosition(position, click())) @@ -568,6 +566,7 @@ class LandingPageActivityIntegrationTest { matches(isDisplayed()) ) } + @Test fun testImagePreviewAfterProjectInsert() { onView(withId(R.id.pocketpaint_fab_new_image)) @@ -584,7 +583,7 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - onView(isRoot()).perform(waitFor(300)) + Thread.sleep(THREAD_WAITING_TIME) pressBack() onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) From e9c53a34831059d2e2649e8f53ae280f6e370cde Mon Sep 17 00:00:00 2001 From: Rd Date: Tue, 13 Jun 2023 05:53:53 +0530 Subject: [PATCH 39/52] Fix Device Tests - Landing Page --- .../LandingPageActivityIntegrationTest.kt | 210 ++++++------------ .../catrobat/paintroid/LandingPageActivity.kt | 1 + .../org/catrobat/paintroid/MainActivity.kt | 6 + .../paintroid/adapter/ProjectAdapter.kt | 10 +- 4 files changed, 88 insertions(+), 139 deletions(-) diff --git a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt index bae782209b..1270409e05 100644 --- a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt +++ b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt @@ -18,6 +18,7 @@ import android.widget.ImageView import android.widget.TextView import androidx.appcompat.widget.Toolbar import androidx.core.graphics.drawable.toBitmap +import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.room.Room import androidx.test.espresso.Espresso.onView @@ -26,20 +27,25 @@ import androidx.test.espresso.UiController import androidx.test.espresso.ViewAction import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.action.ViewActions.replaceText +import androidx.test.espresso.action.ViewActions.closeSoftKeyboard +import androidx.test.espresso.action.ViewActions.scrollTo import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.contrib.RecyclerViewActions import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition import androidx.test.espresso.intent.Intents import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction -import androidx.test.espresso.matcher.RootMatchers import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.isRoot import androidx.test.espresso.matcher.ViewMatchers.hasDescendant import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import androidx.test.rule.ActivityTestRule +import kotlinx.coroutines.Dispatchers.Main +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext import org.catrobat.paintroid.LandingPageActivity import org.catrobat.paintroid.R import org.catrobat.paintroid.adapter.ProjectAdapter @@ -47,9 +53,11 @@ import org.catrobat.paintroid.common.CATROBAT_IMAGE_ENDING import org.catrobat.paintroid.common.PNG_IMAGE_ENDING import org.catrobat.paintroid.data.local.dao.ProjectDao import org.catrobat.paintroid.data.local.database.ProjectDatabase +import org.catrobat.paintroid.model.Project import org.catrobat.paintroid.test.espresso.util.BitmapLocationProvider import org.catrobat.paintroid.test.espresso.util.DrawingSurfaceLocationProvider import org.catrobat.paintroid.test.espresso.util.UiInteractions.touchAt +import org.catrobat.paintroid.test.espresso.util.UiInteractions.waitFor import org.catrobat.paintroid.test.espresso.util.UiMatcher.atPosition import org.catrobat.paintroid.test.espresso.util.wrappers.DrawingSurfaceInteraction.onDrawingSurfaceView import org.catrobat.paintroid.test.espresso.util.wrappers.ToolBarViewInteraction @@ -69,10 +77,9 @@ import org.junit.Assert import org.junit.runner.RunWith import java.io.File import java.io.IOException +import java.util.* import kotlin.collections.ArrayList -private const val THREAD_WAITING_TIME: Long = 300 - @RunWith(AndroidJUnit4::class) class LandingPageActivityIntegrationTest { @@ -86,11 +93,25 @@ class LandingPageActivityIntegrationTest { var screenshotOnFailRule = ScreenshotOnFailRule() private lateinit var activity: LandingPageActivity + private lateinit var intent: Intent + private lateinit var recyclerView: RecyclerView + private lateinit var adapter: ProjectAdapter companion object { private lateinit var deletionFileList: ArrayList private const val PROJECT_NAME = "projectName" private const val position = 0 + private val project = Project( + "name", + "catrobat/path", + 0, + 0, + "0x0", + "CATROBAT", + 0.0, + "paintroid/path", + 1) + private val projectList = ArrayList().apply { add(project) } } @Before @@ -100,6 +121,7 @@ class LandingPageActivityIntegrationTest { .build() dao = database.dao deletionFileList = ArrayList() + intent = Intent() activity = launchActivityRule.activity } @@ -158,23 +180,6 @@ class LandingPageActivityIntegrationTest { .check(matches(isDisplayed())) } - @Test - fun testNewImage() { - onView(withId(R.id.pocketpaint_fab_new_image)) - .perform(click()) - onDrawingSurfaceView() - .perform(touchAt(DrawingSurfaceLocationProvider.MIDDLE)) - onDrawingSurfaceView() - .checkPixelColor(Color.BLACK, BitmapLocationProvider.MIDDLE) - pressBack() - onView(withText(R.string.discard_button_text)) - .perform(click()) - onView(withId(R.id.pocketpaint_fab_new_image)) - .perform(click()) - onDrawingSurfaceView() - .checkPixelColor(Color.TRANSPARENT, BitmapLocationProvider.MIDDLE) - } - @Test fun testLoadImageIntentStarted() { Intents.init() @@ -194,29 +199,6 @@ class LandingPageActivityIntegrationTest { .check(matches(isDisplayed())) } - @Test - fun testProjectInsertedAndDisplayed() { - onView(withId(R.id.pocketpaint_fab_new_image)) - .perform(click()) - onDrawingSurfaceView() - .perform(touchAt(DrawingSurfaceLocationProvider.MIDDLE)) - TopBarViewInteraction.onTopBarView() - .performOpenMoreOptions() - onView(withText(R.string.menu_save_project)) - .perform(click()) - onView(withId(R.id.pocketpaint_image_name_save_text)) - .perform(replaceText(PROJECT_NAME)) - onView(withText(R.string.save_button_text)) - .perform(click()) - Thread.sleep(THREAD_WAITING_TIME) - pressBack() - onView(withId(R.id.pocketpaint_projects_list)) - .perform(RecyclerViewActions.scrollToPosition(position)) - onView(withId(R.id.pocketpaint_projects_list)).check( - matches(atPosition(position, isDisplayed())) - ) - } - @Test fun testProjectInsertedWithProjectName() { onView(withId(R.id.pocketpaint_fab_new_image)) @@ -231,8 +213,10 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - Thread.sleep(THREAD_WAITING_TIME) + onView(isRoot()).perform(waitFor(1000)) pressBack() + launchActivityRule.launchActivity(intent) + onView(isRoot()).perform(waitFor(300)) onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) onView(withId(R.id.pocketpaint_projects_list)) @@ -244,6 +228,7 @@ class LandingPageActivityIntegrationTest { @Test fun testProjectInsertedWithImagePreview() { + val testName = UUID.randomUUID().toString() onView(withId(R.id.pocketpaint_fab_new_image)) .perform(click()) onDrawingSurfaceView() @@ -253,16 +238,18 @@ class LandingPageActivityIntegrationTest { onView(withText(R.string.menu_save_project)) .perform(click()) onView(withId(R.id.pocketpaint_image_name_save_text)) - .perform(replaceText(PROJECT_NAME)) + .perform(replaceText(testName)) onView(withText(R.string.save_button_text)) .perform(click()) - Thread.sleep(THREAD_WAITING_TIME) + onView(isRoot()).perform(waitFor(1000)) pressBack() + launchActivityRule.launchActivity(intent) + onView(isRoot()).perform(waitFor(300)) onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) val imagesDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString() - val imagePathToFile = imagesDirectory + File.separator + PROJECT_NAME + "." + PNG_IMAGE_ENDING + val imagePathToFile = imagesDirectory + File.separator + testName + "." + PNG_IMAGE_ENDING val imageFile = File(imagePathToFile) onView(withId(R.id.pocketpaint_projects_list)).perform( actionOnItemAtPosition( @@ -289,22 +276,7 @@ class LandingPageActivityIntegrationTest { @Test fun testProjectOverFlowMenuDetailsDisplayed() { - onView(withId(R.id.pocketpaint_fab_new_image)) - .perform(click()) - onDrawingSurfaceView() - .perform(touchAt(DrawingSurfaceLocationProvider.MIDDLE)) - TopBarViewInteraction.onTopBarView() - .performOpenMoreOptions() - onView(withText(R.string.menu_save_project)) - .perform(click()) - onView(withId(R.id.pocketpaint_image_name_save_text)) - .perform(replaceText(PROJECT_NAME)) - onView(withText(R.string.save_button_text)) - .perform(click()) - Thread.sleep(THREAD_WAITING_TIME) - pressBack() - onView(withId(R.id.pocketpaint_projects_list)) - .perform(RecyclerViewActions.scrollToPosition(position)) + insertProjectIntoRecyclerView() onView(withId(R.id.pocketpaint_projects_list)).perform( actionOnItemAtPosition( position, @@ -331,22 +303,7 @@ class LandingPageActivityIntegrationTest { @Test fun testProjectOverFlowMenuProjectDetail() { - onView(withId(R.id.pocketpaint_fab_new_image)) - .perform(click()) - onDrawingSurfaceView() - .perform(touchAt(DrawingSurfaceLocationProvider.MIDDLE)) - TopBarViewInteraction.onTopBarView() - .performOpenMoreOptions() - onView(withText(R.string.menu_save_project)) - .perform(click()) - onView(withId(R.id.pocketpaint_image_name_save_text)) - .perform(replaceText(PROJECT_NAME)) - onView(withText(R.string.save_button_text)) - .perform(click()) - Thread.sleep(THREAD_WAITING_TIME) - pressBack() - onView(withId(R.id.pocketpaint_projects_list)) - .perform(RecyclerViewActions.scrollToPosition(position)) + insertProjectIntoRecyclerView() onView(withId(R.id.pocketpaint_projects_list)).perform( actionOnItemAtPosition( position, @@ -369,30 +326,14 @@ class LandingPageActivityIntegrationTest { ) onView(withText(R.string.menu_project_detail_title)) .perform(click()) - Thread.sleep(THREAD_WAITING_TIME) + onView(isRoot()).perform(waitFor(300)) onView(withText(android.R.string.ok)) - .inRoot(RootMatchers.isDialog()) .perform(click()) } @Test fun testProjectOverFlowMenuDeleteDisplayed() { - onView(withId(R.id.pocketpaint_fab_new_image)) - .perform(click()) - onDrawingSurfaceView() - .perform(touchAt(DrawingSurfaceLocationProvider.MIDDLE)) - TopBarViewInteraction.onTopBarView() - .performOpenMoreOptions() - onView(withText(R.string.menu_save_project)) - .perform(click()) - onView(withId(R.id.pocketpaint_image_name_save_text)) - .perform(replaceText(PROJECT_NAME)) - onView(withText(R.string.save_button_text)) - .perform(click()) - Thread.sleep(THREAD_WAITING_TIME) - pressBack() - onView(withId(R.id.pocketpaint_projects_list)) - .perform(RecyclerViewActions.scrollToPosition(position)) + insertProjectIntoRecyclerView() onView(withId(R.id.pocketpaint_projects_list)).perform( actionOnItemAtPosition( position, @@ -419,22 +360,7 @@ class LandingPageActivityIntegrationTest { @Test fun testProjectOverFlowMenuProjectDeleteCancel() { - onView(withId(R.id.pocketpaint_fab_new_image)) - .perform(click()) - onDrawingSurfaceView() - .perform(touchAt(DrawingSurfaceLocationProvider.MIDDLE)) - TopBarViewInteraction.onTopBarView() - .performOpenMoreOptions() - onView(withText(R.string.menu_save_project)) - .perform(click()) - onView(withId(R.id.pocketpaint_image_name_save_text)) - .perform(replaceText(PROJECT_NAME)) - onView(withText(R.string.save_button_text)) - .perform(click()) - Thread.sleep(THREAD_WAITING_TIME) - pressBack() - onView(withId(R.id.pocketpaint_projects_list)) - .perform(RecyclerViewActions.scrollToPosition(position)) + insertProjectIntoRecyclerView() onView(withId(R.id.pocketpaint_projects_list)).perform( actionOnItemAtPosition( position, @@ -457,35 +383,22 @@ class LandingPageActivityIntegrationTest { ) onView(withText(R.string.menu_project_delete_title)) .perform(click()) - onView(withId(android.R.id.button2)) - .inRoot(RootMatchers.isDialog()) + onView(withText(R.string.cancel_button_text)) .perform(click()) onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) onView(withId(R.id.pocketpaint_projects_list)) .check(matches(atPosition(position, hasDescendant(allOf( isAssignableFrom(TextView::class.java), - withText(PROJECT_NAME) + withText(project.name) ))))) } @Test fun testProjectOverFlowMenuProjectDelete() { val recyclerViewMatcher = withId(R.id.pocketpaint_projects_list) - onView(withId(R.id.pocketpaint_fab_new_image)) - .perform(click()) - onDrawingSurfaceView() - .perform(touchAt(DrawingSurfaceLocationProvider.MIDDLE)) - TopBarViewInteraction.onTopBarView() - .performOpenMoreOptions() - onView(withText(R.string.menu_save_project)) - .perform(click()) - onView(withId(R.id.pocketpaint_image_name_save_text)) - .perform(replaceText(PROJECT_NAME)) - onView(withText(R.string.save_button_text)) - .perform(click()) - Thread.sleep(THREAD_WAITING_TIME) - pressBack() + insertProjectIntoRecyclerView() + dao.insertProject(project) onView(withId(R.id.pocketpaint_projects_list)).perform( actionOnItemAtPosition( position, @@ -508,12 +421,15 @@ class LandingPageActivityIntegrationTest { ) onView(withText(R.string.menu_project_delete_title)) .perform(click()) - Thread.sleep(THREAD_WAITING_TIME) + onView(isRoot()).perform(waitFor(100)) onView(withId(android.R.id.button1)) - .inRoot(RootMatchers.isDialog()) + .perform(closeSoftKeyboard()) + .perform(scrollTo()) .perform(click()) + onView(isRoot()).perform(waitFor(300)) onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) + onView(isRoot()).perform(waitFor(300)) if (isRecyclerViewEmpty(recyclerViewMatcher)) { onView(withId(R.id.pocketpaint_projects_list)) .check(matches(not(hasDescendant(withId(R.id.iv_pocket_paint_project_thumbnail_image))))) @@ -525,7 +441,7 @@ class LandingPageActivityIntegrationTest { position, hasDescendant( allOf( isAssignableFrom(TextView::class.java), - not(withText(PROJECT_NAME)) + not(withText(project.name)) ) ) ) @@ -548,8 +464,10 @@ class LandingPageActivityIntegrationTest { .perform(replaceText(PROJECT_NAME)) onView(withText(R.string.save_button_text)) .perform(click()) - Thread.sleep(THREAD_WAITING_TIME) + onView(isRoot()).perform(waitFor(1000)) pressBack() + launchActivityRule.launchActivity(intent) + onView(isRoot()).perform(waitFor(1000)) onView(withId(R.id.pocketpaint_projects_list)) .perform(actionOnItemAtPosition(position, click())) onView( @@ -569,6 +487,7 @@ class LandingPageActivityIntegrationTest { @Test fun testImagePreviewAfterProjectInsert() { + val testName = UUID.randomUUID().toString() onView(withId(R.id.pocketpaint_fab_new_image)) .perform(click()) ToolBarViewInteraction.onToolBarView() @@ -580,21 +499,38 @@ class LandingPageActivityIntegrationTest { onView(withText(R.string.menu_save_project)) .perform(click()) onView(withId(R.id.pocketpaint_image_name_save_text)) - .perform(replaceText(PROJECT_NAME)) + .perform(replaceText(testName)) onView(withText(R.string.save_button_text)) .perform(click()) - Thread.sleep(THREAD_WAITING_TIME) + onView(isRoot()).perform(waitFor(1000)) pressBack() + launchActivityRule.launchActivity(intent) + onView(isRoot()).perform(waitFor(300)) onView(withId(R.id.pocketpaint_projects_list)) .perform(RecyclerViewActions.scrollToPosition(position)) val imagesDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString() - val imagePathToFile = imagesDirectory + File.separator + PROJECT_NAME + "." + PNG_IMAGE_ENDING + val imagePathToFile = imagesDirectory + File.separator + testName + "." + PNG_IMAGE_ENDING val imageFile = File(imagePathToFile) val expectedBitmap = BitmapFactory.decodeFile(imageFile.absolutePath) onView(withId(R.id.pocketpaint_image_preview)).check(matches(withBitmap(expectedBitmap))) } + private fun insertProjectIntoRecyclerView() { + runBlocking { + recyclerView = activity.findViewById(R.id.pocketpaint_projects_list) + adapter = ProjectAdapter( + InstrumentationRegistry.getInstrumentation().targetContext, + projectList, + activity.supportFragmentManager + ) + withContext(Main) { + recyclerView.layoutManager = LinearLayoutManager(activity) + recyclerView.adapter = adapter + } + } + } + private fun createTestImageFile(): Uri? { val bitmap = Bitmap.createBitmap(400, 400, Bitmap.Config.ARGB_8888) val contentValues = ContentValues() diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt b/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt index 18ad58fa99..77f37a86d3 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt @@ -15,6 +15,7 @@ import org.catrobat.paintroid.data.local.database.ProjectDatabaseProvider import org.catrobat.paintroid.model.Project class LandingPageActivity : AppCompatActivity() { + companion object { lateinit var projectDB: ProjectDatabase private lateinit var projectsRecyclerView: RecyclerView diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt index 40eb6d4169..9442c6b400 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt @@ -725,11 +725,17 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { FileIO.storeImagePreviewUri = Uri.parse(projectImagePreviewUri) presenterMain.switchBetweenVersions(PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT, false) presenterMain.finishActivity() + launchLandingPageActivity() } else if (!supportFragmentManager.popBackStackImmediate()) { presenterMain.onBackPressed() } } + private fun launchLandingPageActivity() { + val intent = Intent(this, LandingPageActivity::class.java) + startActivity(intent) + } + public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) presenterMain.handleActivityResult(requestCode, resultCode, data) diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt b/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt index 80c25676b8..db9f70b113 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt @@ -28,7 +28,11 @@ import java.text.SimpleDateFormat import java.util.* import kotlin.collections.ArrayList -class ProjectAdapter(val context: Context, var projectList: ArrayList, private val supportFragmentManager: FragmentManager) : RecyclerView.Adapter() { +class ProjectAdapter( + val context: Context, + var projectList: ArrayList, + private val supportFragmentManager: FragmentManager +) : RecyclerView.Adapter() { private var itemClickListener: OnItemClickListener? = null companion object { @@ -163,7 +167,9 @@ class ProjectAdapter(val context: Context, var projectList: ArrayList, } fun removeProject(projectId: Int, position: Int) { - projectList.removeAt(position) + if (projectList.isNotEmpty()) { + projectList.removeAt(position) + } val iterator = projectList.iterator() while (iterator.hasNext()) { val project = iterator.next() From 08332afbc6da254d444f44f7858df99ddf9f4c89 Mon Sep 17 00:00:00 2001 From: Rd Date: Tue, 13 Jun 2023 09:25:11 +0530 Subject: [PATCH 40/52] Fix FAILED:compileDebugAndroidTestKotlin --- .../espresso/LandingPageActivityIntegrationTest.kt | 12 ++++++------ .../paintroid/test/espresso/util/UiInteractions.kt | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt index 1270409e05..dffdff325a 100644 --- a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt +++ b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt @@ -59,7 +59,7 @@ import org.catrobat.paintroid.test.espresso.util.DrawingSurfaceLocationProvider import org.catrobat.paintroid.test.espresso.util.UiInteractions.touchAt import org.catrobat.paintroid.test.espresso.util.UiInteractions.waitFor import org.catrobat.paintroid.test.espresso.util.UiMatcher.atPosition -import org.catrobat.paintroid.test.espresso.util.wrappers.DrawingSurfaceInteraction.onDrawingSurfaceView +import org.catrobat.paintroid.test.espresso.util.wrappers.DrawingSurfaceInteraction import org.catrobat.paintroid.test.espresso.util.wrappers.ToolBarViewInteraction import org.catrobat.paintroid.test.espresso.util.wrappers.TopBarViewInteraction import org.catrobat.paintroid.test.utils.ScreenshotOnFailRule @@ -188,7 +188,7 @@ class LandingPageActivityIntegrationTest { val resultOK = Instrumentation.ActivityResult(Activity.RESULT_OK, intent) Intents.intending(hasAction(Intent.ACTION_GET_CONTENT)).respondWith(resultOK) onView(withId(R.id.pocketpaint_fab_load_image)).perform(click()) - onDrawingSurfaceView() + DrawingSurfaceInteraction.onDrawingSurfaceView() .checkPixelColor(Color.BLACK, BitmapLocationProvider.MIDDLE) Intents.release() } @@ -203,7 +203,7 @@ class LandingPageActivityIntegrationTest { fun testProjectInsertedWithProjectName() { onView(withId(R.id.pocketpaint_fab_new_image)) .perform(click()) - onDrawingSurfaceView() + DrawingSurfaceInteraction.onDrawingSurfaceView() .perform(touchAt(DrawingSurfaceLocationProvider.MIDDLE)) TopBarViewInteraction.onTopBarView() .performOpenMoreOptions() @@ -231,7 +231,7 @@ class LandingPageActivityIntegrationTest { val testName = UUID.randomUUID().toString() onView(withId(R.id.pocketpaint_fab_new_image)) .perform(click()) - onDrawingSurfaceView() + DrawingSurfaceInteraction.onDrawingSurfaceView() .perform(touchAt(DrawingSurfaceLocationProvider.MIDDLE)) TopBarViewInteraction.onTopBarView() .performOpenMoreOptions() @@ -454,7 +454,7 @@ class LandingPageActivityIntegrationTest { fun testSavedProjectOpen() { onView(withId(R.id.pocketpaint_fab_new_image)) .perform(click()) - onDrawingSurfaceView() + DrawingSurfaceInteraction.onDrawingSurfaceView() .perform(touchAt(DrawingSurfaceLocationProvider.MIDDLE)) TopBarViewInteraction.onTopBarView() .performOpenMoreOptions() @@ -492,7 +492,7 @@ class LandingPageActivityIntegrationTest { .perform(click()) ToolBarViewInteraction.onToolBarView() .performSelectTool(ToolType.FILL) - onDrawingSurfaceView() + DrawingSurfaceInteraction.onDrawingSurfaceView() .perform(touchAt(DrawingSurfaceLocationProvider.MIDDLE)) TopBarViewInteraction.onTopBarView() .performOpenMoreOptions() diff --git a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/util/UiInteractions.kt b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/util/UiInteractions.kt index 4a19745683..c696a8b6be 100644 --- a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/util/UiInteractions.kt +++ b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/util/UiInteractions.kt @@ -141,6 +141,7 @@ object UiInteractions { (r.right + 50).toFloat(), r.centerY().toFloat() ) + else -> return@CoordinatesProvider null } null }, Press.FINGER, 0, 1) From 476157ff8a8481d5000d5afd9fef1d4126769281 Mon Sep 17 00:00:00 2001 From: Rd Date: Mon, 3 Jul 2023 20:20:52 +0530 Subject: [PATCH 41/52] Refinements on Deletion, Updation, Image Creation --- .idea/codeStyles/Project.xml | 124 ------------------ .../paintroid/test/database/ProjectDaoTest.kt | 17 +++ .../paintroid/adapter/ProjectAdapter.kt | 39 ++++-- .../paintroid/data/local/dao/ProjectDao.kt | 3 + .../paintroid/dialog/ProjectDeleteDialog.kt | 40 +++++- .../catrobat/paintroid/iotasks/SaveImage.kt | 18 ++- 6 files changed, 100 insertions(+), 141 deletions(-) delete mode 100644 .idea/codeStyles/Project.xml diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml deleted file mode 100644 index ab75be245f..0000000000 --- a/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/database/ProjectDaoTest.kt b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/database/ProjectDaoTest.kt index 3d6b766c53..700cc8a14c 100644 --- a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/database/ProjectDaoTest.kt +++ b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/database/ProjectDaoTest.kt @@ -50,6 +50,23 @@ class ProjectDaoTest { assertTrue(allProjects.contains(project)) } + @Test + fun getProject() { + val project = Project( + "name", + "catrobat/path", + 0, + 0, + "0x0", + "CATROBAT", + 0.0, + "paintroid/path", + 1) + dao.insertProject(project) + val insertedProject = dao.getProject(project.name) + assertTrue(insertedProject == project) + } + @Test fun deleteProject() { val project = Project( diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt b/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt index db9f70b113..41d24402a2 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt @@ -1,5 +1,6 @@ package org.catrobat.paintroid.adapter +import android.annotation.SuppressLint import android.content.Context import android.net.Uri import android.os.Build @@ -14,9 +15,13 @@ import android.widget.TextView import androidx.annotation.RequiresApi import androidx.fragment.app.FragmentManager import androidx.recyclerview.widget.RecyclerView +import kotlinx.coroutines.Dispatchers.Main +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext import org.catrobat.paintroid.LandingPageActivity.Companion.imagePreview import org.catrobat.paintroid.LandingPageActivity.Companion.latestProject import org.catrobat.paintroid.R +import org.catrobat.paintroid.common.CATROBAT_IMAGE_ENDING import org.catrobat.paintroid.common.PROJECT_DELETE_DIALOG_FRAGMENT_TAG import org.catrobat.paintroid.common.PROJECT_DETAILS_DIALOG_FRAGMENT_TAG import org.catrobat.paintroid.dialog.ProjectDeleteDialog @@ -57,7 +62,7 @@ class ProjectAdapter( override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { val item = projectList[position] val id = item.id - val name = item.name.substringBefore(".catrobat-image") + val name = item.name.substringBefore(".$CATROBAT_IMAGE_ENDING") val resolution = item.resolution val creationDate = item.creationDate val lastModifiedDate = item.lastModified @@ -104,7 +109,7 @@ class ProjectAdapter( true } R.id.project_delete -> { - val projectDelete = ProjectDeleteDialog(id, name, position) + val projectDelete = ProjectDeleteDialog(id, name, imageUri, position) projectDelete.show(supportFragmentManager, PROJECT_DELETE_DIALOG_FRAGMENT_TAG) true } @@ -133,13 +138,13 @@ class ProjectAdapter( if (it.moveToFirst()) { val columnIndex = it.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA) filePath = it.getString(columnIndex) - file = File(filePath) + file = File(filePath.toString()) return file } } } try { - file = File(uri.path) + file = File(uri.path.toString()) return file } catch (e: IOException) { Log.e(TAG, "Error occurred while accessing the file: ${e.message}") @@ -147,12 +152,19 @@ class ProjectAdapter( return null } + @SuppressLint("NotifyDataSetChanged") fun insertProject(project: Project) { projectList.add(0, project) - imagePreview.setImageURI(Uri.parse(project?.imagePreviewPath)) - notifyItemInserted(0) + runBlocking { + withContext(Main) { + notifyItemInserted(0) + notifyDataSetChanged() + imagePreview.setImageURI(Uri.parse(project.imagePreviewPath)) + } + } } + @SuppressLint("NotifyDataSetChanged") fun updateProject(filename: String, imagePreviewPath: String, projectUri: String, lastModified: Long) { val index = projectList.indexOfFirst { it.name == filename } if (index != -1) { @@ -161,11 +173,20 @@ class ProjectAdapter( imagePreviewPath = imagePreviewPath, lastModified = lastModified, ) - projectList[index] = updatedProject - notifyItemChanged(index) + projectList.removeAt(index) + projectList.add(0, updatedProject) + runBlocking { + withContext(Main) { + notifyItemChanged(index) + notifyDataSetChanged() + imagePreview.setImageDrawable(null) + imagePreview.setImageURI(Uri.parse(imagePreviewPath)) + } + } } } + @SuppressLint("NotifyDataSetChanged") fun removeProject(projectId: Int, position: Int) { if (projectList.isNotEmpty()) { projectList.removeAt(position) @@ -178,6 +199,8 @@ class ProjectAdapter( break } } + notifyItemRemoved(position) + notifyDataSetChanged() if (projectList.isNotEmpty()) { latestProject = projectList[0] imagePreview.setImageURI(Uri.parse(latestProject?.imagePreviewPath)) diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt b/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt index daca90a853..4a4e1570dd 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/data/local/dao/ProjectDao.kt @@ -17,6 +17,9 @@ interface ProjectDao { @Query("SELECT * FROM Project ORDER BY lastModified DESC") fun getProjects(): List + @Query("SELECT * FROM Project WHERE name= :name") + fun getProject(name: String): Project + @Query("DELETE FROM Project WHERE id= :id") fun deleteProject(id: Int) diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDeleteDialog.kt b/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDeleteDialog.kt index afdd02bcbf..530c0d617c 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDeleteDialog.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ProjectDeleteDialog.kt @@ -2,14 +2,20 @@ package org.catrobat.paintroid.dialog import android.app.AlertDialog import android.app.Dialog +import android.net.Uri +import android.os.Build import android.os.Bundle +import android.os.Environment import androidx.appcompat.app.AppCompatDialogFragment import androidx.core.text.HtmlCompat import org.catrobat.paintroid.LandingPageActivity.Companion.projectAdapter import org.catrobat.paintroid.LandingPageActivity.Companion.projectDB import org.catrobat.paintroid.R +import org.catrobat.paintroid.common.CATROBAT_IMAGE_ENDING +import org.catrobat.paintroid.common.PNG_IMAGE_ENDING +import java.io.File -class ProjectDeleteDialog(private val projectId: Int, private val name: String, private val position: Int) : AppCompatDialogFragment() { +class ProjectDeleteDialog(private val projectId: Int, private val name: String, private val imageUri: Uri?, private val position: Int) : AppCompatDialogFragment() { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val message = getString(R.string.pocketpaint_project_delete_title_dialog, name) @@ -18,10 +24,40 @@ class ProjectDeleteDialog(private val projectId: Int, private val name: String, .setMessage(R.string.pocketpaint_project_delete_message_dialog) .setPositiveButton(R.string.pocketpaint_ok) { _, _ -> projectDB.dao.deleteProject(projectId) + if (imageUri != null) { + deleteProjectFile(name, imageUri) + deleteProjectImage(name, imageUri) + } projectAdapter.removeProject(projectId, position) - projectAdapter.notifyItemRemoved(position) } .setNegativeButton(R.string.pocketpaint_cancel) { _, _ -> dismiss() } .create() } + + private fun deleteProjectFile(name: String, uri: Uri) { + val file = getFile(name, uri, Environment.DIRECTORY_DOWNLOADS, CATROBAT_IMAGE_ENDING) + deleteFile(file) + } + + private fun deleteProjectImage(name: String, uri: Uri) { + val file = getFile(name, uri, Environment.DIRECTORY_PICTURES, PNG_IMAGE_ENDING) + deleteFile(file) + } + + private fun getFile(name: String, uri: Uri, directoryType: String, fileExtension: String): File? { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + val dir = Environment.getExternalStoragePublicDirectory(directoryType) + File(dir, "$name.$fileExtension") + } else { + File(uri.path.toString()) + } + } + + private fun deleteFile(file: File?) { + file?.let { + if (it.exists()) { + it.delete() + } + } + } } diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/SaveImage.kt b/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/SaveImage.kt index 771e108a2a..9fe9a6d23b 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/SaveImage.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/SaveImage.kt @@ -37,6 +37,7 @@ import org.catrobat.paintroid.MainActivity.Companion.projectImagePreviewUri import org.catrobat.paintroid.MainActivity.Companion.projectName import org.catrobat.paintroid.MainActivity.Companion.projectUri import org.catrobat.paintroid.command.serialization.CommandSerializer +import org.catrobat.paintroid.common.CATROBAT_IMAGE_ENDING import org.catrobat.paintroid.common.PNG_IMAGE_ENDING import org.catrobat.paintroid.contract.LayerContracts import org.catrobat.paintroid.model.Project @@ -83,16 +84,18 @@ class SaveImage( bitmap: Bitmap? ): Uri? { val filename = FileIO.defaultFileName - val imagesDirectory = - Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString() - val pathToFile = imagesDirectory + File.separator + filename + "." + PNG_IMAGE_ENDING + val name = filename.removeSuffix(".$CATROBAT_IMAGE_ENDING") + val pngFileName = "$name.$PNG_IMAGE_ENDING" + val imagesDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString() + val pathToFile = imagesDirectory + File.separator + pngFileName val imageFile = File(pathToFile) return if (saveImageOptions.imagePreviewUri == null || !imageFile.exists()) { - val imagePreviewFilename = filename.replace(".catrobat-image", ".png") - val imageUri = FileIO.saveBitmapToFile(imagePreviewFilename, bitmap, callback.contentResolver, context) + val imageUri = FileIO.saveBitmapToFile(pngFileName, bitmap, callback.contentResolver, context) imageUri } else { - saveImageOptions.imagePreviewUri?.let { FileIO.saveBitmapToUri(it, bitmap, context) } + saveImageOptions.imagePreviewUri?.let { + FileIO.saveBitmapToUri(it, bitmap, context) + } } } @@ -219,7 +222,8 @@ class SaveImage( imagePreviewPath.toString() ) projectDB.dao.insertProject(project) - projectAdapter.insertProject(project) + val insertedProject = projectDB.dao.getProject(filename) + projectAdapter.insertProject(insertedProject) projectName = filename projectUri = currentUri.toString() projectImagePreviewUri = imagePreviewPath.toString() From 5292c4f0f205a97c57702a152a4de874cb6ab052 Mon Sep 17 00:00:00 2001 From: Rd Date: Thu, 13 Jul 2023 20:28:53 +0530 Subject: [PATCH 42/52] Fixed Crash on returning to Landing Page --- Paintroid/build.gradle | 2 +- .../org/catrobat/paintroid/presenter/MainActivityPresenter.kt | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Paintroid/build.gradle b/Paintroid/build.gradle index 2e1745bbf8..1c2b512b56 100644 --- a/Paintroid/build.gradle +++ b/Paintroid/build.gradle @@ -153,7 +153,7 @@ dependencies { testImplementation "androidx.test:core-ktx:1.4.0" implementation 'com.android.support.test.espresso:espresso-idling-resource:3.1.0' - def room_version = "2.3.0" + def room_version = "2.4.0-alpha03" implementation "androidx.room:room-ktx:$room_version" kapt "androidx.room:room-compiler:$room_version" } diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt b/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt index 6061aeb9ab..9086c08f89 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt @@ -48,6 +48,7 @@ import androidx.core.view.GravityCompat import androidx.test.espresso.idling.CountingIdlingResource import org.catrobat.paintroid.FileIO import org.catrobat.paintroid.MainActivity +import org.catrobat.paintroid.MainActivity.Companion.projectName import org.catrobat.paintroid.R import org.catrobat.paintroid.UserPreferences import org.catrobat.paintroid.colorpicker.ColorHistory @@ -335,6 +336,7 @@ open class MainActivityPresenter( } override fun onNewImage() { + projectName = null val metrics = view.displayMetrics resetPerspectiveAfterNextCommand = true model.savedPictureUri = null From 68ff71f2ac7b781244047722af4d687ed1e5b80e Mon Sep 17 00:00:00 2001 From: Rd Date: Thu, 13 Jul 2023 22:10:41 +0530 Subject: [PATCH 43/52] testImagePreviewAfterProjectInsert bitmap check --- .../LandingPageActivityIntegrationTest.kt | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt index dffdff325a..a694723135 100644 --- a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt +++ b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/LandingPageActivityIntegrationTest.kt @@ -17,7 +17,6 @@ import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView import androidx.appcompat.widget.Toolbar -import androidx.core.graphics.drawable.toBitmap import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.room.Room @@ -66,9 +65,7 @@ import org.catrobat.paintroid.test.utils.ScreenshotOnFailRule import org.catrobat.paintroid.tools.ToolType import org.hamcrest.CoreMatchers.allOf import org.hamcrest.CoreMatchers.not -import org.hamcrest.Description import org.hamcrest.Matcher -import org.hamcrest.TypeSafeMatcher import org.junit.Test import org.junit.Rule import org.junit.After @@ -512,8 +509,24 @@ class LandingPageActivityIntegrationTest { Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString() val imagePathToFile = imagesDirectory + File.separator + testName + "." + PNG_IMAGE_ENDING val imageFile = File(imagePathToFile) - val expectedBitmap = BitmapFactory.decodeFile(imageFile.absolutePath) - onView(withId(R.id.pocketpaint_image_preview)).check(matches(withBitmap(expectedBitmap))) + onView(withId(R.id.pocketpaint_image_preview)).perform( + object : ViewAction { + override fun getConstraints(): Matcher { + return isAssignableFrom(ImageView::class.java) + } + + override fun getDescription(): String { + return "Check if image is correctly displayed" + } + + override fun perform(uiController: UiController?, view: View?) { + val imageView = view as? ImageView + val expectedBitmap = BitmapFactory.decodeFile(imageFile.absolutePath) + val actualBitmap = (imageView?.drawable as? BitmapDrawable)?.bitmap + actualBitmap?.sameAs(expectedBitmap) + } + } + ) } private fun insertProjectIntoRecyclerView() { @@ -554,20 +567,6 @@ class LandingPageActivityIntegrationTest { return imageUri } - private fun withBitmap(expectedBitmap: Bitmap): Matcher { - return object : TypeSafeMatcher() { - override fun describeTo(description: Description) { - description.appendText("with bitmap: ") - } - - override fun matchesSafely(view: View): Boolean { - if (view !is ImageView) return false - val actualBitmap = view.drawable.toBitmap() - return expectedBitmap.sameAs(actualBitmap) - } - } - } - private fun isRecyclerViewEmpty(recyclerViewMatcher: Matcher): Boolean { val itemCount = getRecyclerViewItemCount(recyclerViewMatcher) return itemCount == 0 From c6733f90e80d58d0d0b7c25d38ae24758b5f73cd Mon Sep 17 00:00:00 2001 From: Rd Date: Sat, 15 Jul 2023 12:58:47 +0530 Subject: [PATCH 44/52] more options integration test - swipe up --- .../paintroid/test/espresso/MoreOptionsIntegrationTest.kt | 3 +++ .../espresso/util/wrappers/OptionsMenuViewInteraction.kt | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/MoreOptionsIntegrationTest.kt b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/MoreOptionsIntegrationTest.kt index 7beb1c5c94..b8ebdfa762 100644 --- a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/MoreOptionsIntegrationTest.kt +++ b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/MoreOptionsIntegrationTest.kt @@ -256,12 +256,14 @@ class MoreOptionsIntegrationTest { @Test fun testOnOffSmoothOptions() { + OptionsMenuViewInteraction.onOptionsMenu().swipeUp() Espresso.onView(withText(R.string.menu_advanced)).perform(ViewActions.click()) Espresso.onView(withId(R.id.pocketpaint_smoothing)).perform(ViewActions.click()) Espresso.onView(withText(R.string.pocketpaint_ok)).perform(ViewActions.click()) Assert.assertFalse("Smoothing is still on!", AdvancedSettingsAlgorithms.smoothing) TopBarViewInteraction.onTopBarView() .performOpenMoreOptions() + OptionsMenuViewInteraction.onOptionsMenu().swipeUp() Espresso.onView(withText(R.string.menu_advanced)).perform(ViewActions.click()) Espresso.onView(withId(R.id.pocketpaint_smoothing)).perform(ViewActions.click()) Espresso.onView(withText(R.string.pocketpaint_ok)).perform(ViewActions.click()) @@ -270,6 +272,7 @@ class MoreOptionsIntegrationTest { @Test fun testNoChangeOnSmoothingWhenCancel() { + OptionsMenuViewInteraction.onOptionsMenu().swipeUp() Espresso.onView(withText(R.string.menu_advanced)).perform(ViewActions.click()) Espresso.onView(withId(R.id.pocketpaint_smoothing)).perform(ViewActions.click()) Espresso.onView(withText(R.string.cancel_button_text)).perform(ViewActions.click()) diff --git a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/util/wrappers/OptionsMenuViewInteraction.kt b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/util/wrappers/OptionsMenuViewInteraction.kt index f683fedf96..07fcf8f7d8 100644 --- a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/util/wrappers/OptionsMenuViewInteraction.kt +++ b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/util/wrappers/OptionsMenuViewInteraction.kt @@ -4,6 +4,7 @@ import androidx.annotation.StringRes import androidx.appcompat.widget.MenuPopupWindow.MenuDropDownListView import androidx.test.espresso.Espresso import androidx.test.espresso.ViewInteraction +import androidx.test.espresso.action.ViewActions import androidx.test.espresso.assertion.ViewAssertions import org.catrobat.paintroid.test.espresso.util.UiMatcher import org.hamcrest.CoreMatchers @@ -28,6 +29,11 @@ class OptionsMenuViewInteraction private constructor() { return this } + fun swipeUp(): OptionsMenuViewInteraction { + optionsMenu.perform(ViewActions.swipeUp()) + return this + } + companion object { lateinit var optionsMenu: ViewInteraction fun onOptionsMenu(): OptionsMenuViewInteraction = OptionsMenuViewInteraction() From 13184d32d2ab8f6ff6c28518d02bc7117538f4f7 Mon Sep 17 00:00:00 2001 From: Rd Date: Sat, 15 Jul 2023 16:11:02 +0530 Subject: [PATCH 45/52] Room version roll back --- Paintroid/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Paintroid/build.gradle b/Paintroid/build.gradle index 1c2b512b56..2e1745bbf8 100644 --- a/Paintroid/build.gradle +++ b/Paintroid/build.gradle @@ -153,7 +153,7 @@ dependencies { testImplementation "androidx.test:core-ktx:1.4.0" implementation 'com.android.support.test.espresso:espresso-idling-resource:3.1.0' - def room_version = "2.4.0-alpha03" + def room_version = "2.3.0" implementation "androidx.room:room-ktx:$room_version" kapt "androidx.room:room-compiler:$room_version" } From bddf7ecb83640ff9e4a7b834c0d39f0882dcb834 Mon Sep 17 00:00:00 2001 From: Rd Date: Sat, 22 Jul 2023 19:10:12 +0530 Subject: [PATCH 46/52] Fixed Saving Projects with API 28 error --- .../catrobat/paintroid/LandingPageActivity.kt | 29 ++++++++++++------- .../org/catrobat/paintroid/MainActivity.kt | 27 ++++++++++++----- .../paintroid/adapter/ProjectAdapter.kt | 1 + .../catrobat/paintroid/common/Constants.kt | 7 +++++ .../catrobat/paintroid/iotasks/LoadImage.kt | 3 +- .../presenter/MainActivityPresenter.kt | 1 + 6 files changed, 48 insertions(+), 20 deletions(-) diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt b/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt index 77f37a86d3..9b8d79a6e0 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/LandingPageActivity.kt @@ -10,6 +10,13 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.google.android.material.floatingactionbutton.FloatingActionButton import org.catrobat.paintroid.adapter.ProjectAdapter +import org.catrobat.paintroid.common.LOAD_IMAGE +import org.catrobat.paintroid.common.LOAD_NEW_IMAGE +import org.catrobat.paintroid.common.LOAD_PROJECT +import org.catrobat.paintroid.common.NEW_PROJECT +import org.catrobat.paintroid.common.PROJECT_NAME +import org.catrobat.paintroid.common.PROJECT_URI +import org.catrobat.paintroid.common.PROJECT_IMAGE_PREVIEW_URI import org.catrobat.paintroid.data.local.database.ProjectDatabase import org.catrobat.paintroid.data.local.database.ProjectDatabaseProvider import org.catrobat.paintroid.model.Project @@ -49,15 +56,15 @@ class LandingPageActivity : AppCompatActivity() { latestProject = allProjects.firstOrNull() if (allProjects.isNotEmpty()) { val loadProjectIntent = Intent(applicationContext, MainActivity::class.java).apply { - putExtra(PROJECT_ACTION, "load_project") - putExtra("PROJECT_URI", latestProject?.path) - putExtra("PROJECT_NAME", latestProject?.name) - putExtra("PROJECT_IMAGE_PREVIEW_URI", latestProject?.imagePreviewPath) + putExtra(PROJECT_ACTION, LOAD_PROJECT) + putExtra(PROJECT_URI, latestProject?.path) + putExtra(PROJECT_NAME, latestProject?.name) + putExtra(PROJECT_IMAGE_PREVIEW_URI, latestProject?.imagePreviewPath) } startActivity(loadProjectIntent) } else { val newProjectIntent = Intent(this, MainActivity::class.java).apply { - putExtra(PROJECT_ACTION, "new_project") + putExtra(PROJECT_ACTION, NEW_PROJECT) } startActivity(newProjectIntent) } @@ -70,13 +77,13 @@ class LandingPageActivity : AppCompatActivity() { val newImage = findViewById(R.id.pocketpaint_fab_new_image) newImage.setOnClickListener { - mainActivityIntent.putExtra(PROJECT_ACTION, "new_image") + mainActivityIntent.putExtra(PROJECT_ACTION, LOAD_NEW_IMAGE) startActivity(mainActivityIntent) } val loadImage = findViewById(R.id.pocketpaint_fab_load_image) loadImage.setOnClickListener { - mainActivityIntent.putExtra(PROJECT_ACTION, "load_image") + mainActivityIntent.putExtra(PROJECT_ACTION, LOAD_IMAGE) startActivity(mainActivityIntent) } } @@ -101,10 +108,10 @@ class LandingPageActivity : AppCompatActivity() { projectImagePreviewUri: String ) { val loadProjectIntent = Intent(applicationContext, MainActivity::class.java).apply { - putExtra(PROJECT_ACTION, "load_project") - putExtra("PROJECT_URI", projectUri) - putExtra("PROJECT_NAME", projectName) - putExtra("PROJECT_IMAGE_PREVIEW_URI", projectImagePreviewUri) + putExtra(PROJECT_ACTION, LOAD_PROJECT) + putExtra(PROJECT_URI, projectUri) + putExtra(PROJECT_NAME, projectName) + putExtra(PROJECT_IMAGE_PREVIEW_URI, projectImagePreviewUri) } startActivity(loadProjectIntent) } diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt index 9442c6b400..c49b4ceb3e 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt @@ -70,6 +70,14 @@ import org.catrobat.paintroid.common.PAINTROID_PICTURE_PATH import org.catrobat.paintroid.common.REQUEST_CODE_LOAD_PICTURE import org.catrobat.paintroid.common.PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT import org.catrobat.paintroid.common.CommonFactory +import org.catrobat.paintroid.common.CATROBAT_IMAGE_ENDING +import org.catrobat.paintroid.common.NEW_PROJECT +import org.catrobat.paintroid.common.LOAD_NEW_IMAGE +import org.catrobat.paintroid.common.LOAD_IMAGE +import org.catrobat.paintroid.common.LOAD_PROJECT +import org.catrobat.paintroid.common.PROJECT_NAME +import org.catrobat.paintroid.common.PROJECT_URI +import org.catrobat.paintroid.common.PROJECT_IMAGE_PREVIEW_URI import org.catrobat.paintroid.contract.LayerContracts import org.catrobat.paintroid.contract.MainActivityContracts import org.catrobat.paintroid.contract.MainActivityContracts.MainView @@ -317,10 +325,10 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { } savedInstanceState == null -> when (receivedIntent.getStringExtra(PROJECT_ACTION)) { - "new_project" -> presenterMain.onNewImage() - "new_image" -> presenterMain.onNewImage() - "load_image" -> presenterMain.replaceImageClicked() - "load_project" -> loadProject(receivedIntent) + NEW_PROJECT -> presenterMain.onNewImage() + LOAD_NEW_IMAGE -> presenterMain.onNewImage() + LOAD_IMAGE -> presenterMain.replaceImageClicked() + LOAD_PROJECT -> loadProject(receivedIntent) else -> { val intent = intent val picturePath = intent.getStringExtra(PAINTROID_PICTURE_PATH) @@ -372,11 +380,11 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { } private fun loadProject(receivedIntent: Intent) { - projectName = receivedIntent.getStringExtra("PROJECT_NAME") - projectUri = receivedIntent.getStringExtra("PROJECT_URI") - projectImagePreviewUri = receivedIntent.getStringExtra("PROJECT_IMAGE_PREVIEW_URI") + projectName = receivedIntent.getStringExtra(PROJECT_NAME) + projectUri = receivedIntent.getStringExtra(PROJECT_URI) + projectImagePreviewUri = receivedIntent.getStringExtra(PROJECT_IMAGE_PREVIEW_URI) val projectNameText = findViewById(R.id.pocketpaint_toolbar) - projectNameText.subtitle = projectName?.substringBefore(".catrobat-image") + projectNameText.subtitle = projectName?.substringBefore(".$CATROBAT_IMAGE_ENDING") presenterMain.loadScaledImage(projectUri?.toUri(), REQUEST_CODE_LOAD_PICTURE) } @@ -427,10 +435,12 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { FileIO.checkFileExists(FileIO.FileType.CATROBAT, it, this.contentResolver) } == true) { + FileIO.filename = projectName!!.removeSuffix(".$CATROBAT_IMAGE_ENDING") FileIO.storeImageUri = Uri.parse(projectUri) FileIO.storeImagePreviewUri = Uri.parse(projectImagePreviewUri) presenterMain.switchBetweenVersions(PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT, false) presenterMain.finishActivity() + projectName = null } else { presenterMain.backToPocketCodeClicked() } @@ -726,6 +736,7 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { presenterMain.switchBetweenVersions(PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT, false) presenterMain.finishActivity() launchLandingPageActivity() + projectName = null } else if (!supportFragmentManager.popBackStackImmediate()) { presenterMain.onBackPressed() } diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt b/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt index 41d24402a2..40abf58989 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/adapter/ProjectAdapter.kt @@ -90,6 +90,7 @@ class ProjectAdapter( } if (imageUri != null) { + holder.itemImageView.setImageDrawable(null) holder.itemImageView.setImageURI(Uri.parse(item.imagePreviewPath)) } else { holder.itemImageView.setImageResource(R.drawable.pocketpaint_logo_small) diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/common/Constants.kt b/Paintroid/src/main/java/org/catrobat/paintroid/common/Constants.kt index b1a2eddaf0..2e041abb75 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/common/Constants.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/common/Constants.kt @@ -27,6 +27,13 @@ const val TEMP_PICTURE_NAME = "catroidTemp" const val MEDIA_GALLEY_URL = "https://share.catrob.at/pocketcode/media-library/looks" const val PROJECT_DETAILS_DIALOG_FRAGMENT_TAG = "projectdetailsfragment" const val PROJECT_DELETE_DIALOG_FRAGMENT_TAG = "projectdeletefragment" +const val PROJECT_URI = "projecturi" +const val PROJECT_NAME = "projectname" +const val PROJECT_IMAGE_PREVIEW_URI = "projectimagepreviewuri" +const val NEW_PROJECT = "newproject" +const val LOAD_NEW_IMAGE = "loadnewimage" +const val LOAD_IMAGE = "loadimage" +const val LOAD_PROJECT = "loadproject" const val ABOUT_DIALOG_FRAGMENT_TAG = "aboutdialogfragment" const val LIKE_US_DIALOG_FRAGMENT_TAG = "likeusdialogfragment" const val RATE_US_DIALOG_FRAGMENT_TAG = "rateusdialogfragment" diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/LoadImage.kt b/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/LoadImage.kt index 1d5562001e..457c0560ba 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/LoadImage.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/iotasks/LoadImage.kt @@ -33,6 +33,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.catrobat.paintroid.FileIO import org.catrobat.paintroid.command.serialization.CommandSerializer +import org.catrobat.paintroid.common.CATROBAT_IMAGE_ENDING class LoadImage( callback: LoadImageCallback, @@ -52,7 +53,7 @@ class LoadImage( return resolver.getType(uri) } val fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri.toString()) - if (fileExtension.equals("catrobat-image")) { + if (fileExtension.equals(CATROBAT_IMAGE_ENDING)) { return "application/octet-stream" } return MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension.toLowerCase(Locale.US)) diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt b/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt index 9086c08f89..ac8103e3f6 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt @@ -156,6 +156,7 @@ open class MainActivityPresenter( var toolOptionsViewWasShown = false override fun replaceImageClicked() { + projectName = null checkIfClippingToolNeedsAdjustment() switchBetweenVersions(PERMISSION_REQUEST_CODE_REPLACE_PICTURE, false) setFirstCheckBoxInLayerMenu() From e148990600f910444a1ddc41693d1381f0576bb8 Mon Sep 17 00:00:00 2001 From: Rd Date: Sat, 22 Jul 2023 19:18:27 +0530 Subject: [PATCH 47/52] restored Project.xml file --- .idea/codeStyles/Project.xml | 124 +++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 .idea/codeStyles/Project.xml diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000000..32539daf87 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,124 @@ + + + + + + + + + + \ No newline at end of file From 74048f8bbbb9885f17ee50b187ea7fdada37016c Mon Sep 17 00:00:00 2001 From: Rd Date: Sun, 30 Jul 2023 18:57:01 +0530 Subject: [PATCH 48/52] Fixed Saving Project with API 21, 22, 23, 24 error --- .../main/java/org/catrobat/paintroid/MainActivity.kt | 10 ++++++++++ .../command/serialization/CommandSerializer.kt | 11 ++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt index c49b4ceb3e..fa4b8b6d04 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt @@ -54,6 +54,7 @@ import com.google.android.material.navigation.NavigationView import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.launch import org.catrobat.paintroid.LandingPageActivity.Companion.PROJECT_ACTION import org.catrobat.paintroid.colorpicker.ColorHistory @@ -202,6 +203,7 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { var projectName: String? = null var projectUri: String? = null var projectImagePreviewUri: String? = null + var downloadManagerIdRemoved: Boolean = true } override val presenter: MainActivityContracts.Presenter @@ -435,10 +437,18 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { FileIO.checkFileExists(FileIO.FileType.CATROBAT, it, this.contentResolver) } == true) { + downloadManagerIdRemoved = true FileIO.filename = projectName!!.removeSuffix(".$CATROBAT_IMAGE_ENDING") FileIO.storeImageUri = Uri.parse(projectUri) FileIO.storeImagePreviewUri = Uri.parse(projectImagePreviewUri) presenterMain.switchBetweenVersions(PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT, false) + if (VERSION.SDK_INT <= Build.VERSION_CODES.M) { + while (downloadManagerIdRemoved) { + runBlocking { + delay(100) + } + } + } presenterMain.finishActivity() projectName = null } else { diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/command/serialization/CommandSerializer.kt b/Paintroid/src/main/java/org/catrobat/paintroid/command/serialization/CommandSerializer.kt index 4db959c653..2584fa3007 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/command/serialization/CommandSerializer.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/command/serialization/CommandSerializer.kt @@ -35,6 +35,9 @@ import android.provider.MediaStore import com.esotericsoftware.kryo.Kryo import com.esotericsoftware.kryo.io.Input import com.esotericsoftware.kryo.io.Output +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import org.catrobat.paintroid.MainActivity.Companion.downloadManagerIdRemoved import org.catrobat.paintroid.colorpicker.ColorHistory import org.catrobat.paintroid.command.Command import org.catrobat.paintroid.command.CommandManager @@ -211,7 +214,13 @@ open class CommandSerializer(private val activityContext: Context, private val c val id = sharedPreferences.getLong(uri.path, -1) if (id > -1) { val downloadManager = OpenRasterFileFormatConversion.mainActivity.baseContext.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager - downloadManager.remove(id) + runBlocking { + downloadManager.remove(id) + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) { + delay(1000) + downloadManagerIdRemoved = false + } + } } if (!isDeleted) { throw AssertionError("No file to delete was found!") From 979c36fd252d3637291e2b1f7551f29d61be1f10 Mon Sep 17 00:00:00 2001 From: Rd Date: Sun, 30 Jul 2023 19:24:08 +0530 Subject: [PATCH 49/52] Fixed Static warnings --- Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt | 3 ++- .../paintroid/command/serialization/CommandSerializer.kt | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt index fa4b8b6d04..15b93d0a2b 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt @@ -204,6 +204,7 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { var projectUri: String? = null var projectImagePreviewUri: String? = null var downloadManagerIdRemoved: Boolean = true + const val DOWNLOAD_MANAGER_ID_DELAY = 100L } override val presenter: MainActivityContracts.Presenter @@ -445,7 +446,7 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { if (VERSION.SDK_INT <= Build.VERSION_CODES.M) { while (downloadManagerIdRemoved) { runBlocking { - delay(100) + delay(DOWNLOAD_MANAGER_ID_DELAY) } } } diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/command/serialization/CommandSerializer.kt b/Paintroid/src/main/java/org/catrobat/paintroid/command/serialization/CommandSerializer.kt index 2584fa3007..d1f2cd2b83 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/command/serialization/CommandSerializer.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/command/serialization/CommandSerializer.kt @@ -87,6 +87,7 @@ open class CommandSerializer(private val activityContext: Context, private val c companion object { const val CURRENT_IMAGE_VERSION = 2 const val MAGIC_VALUE = "CATROBAT" + const val DOWNLOAD_MANAGER_DELAY_TIME = 1000L } val kryo = Kryo() @@ -217,7 +218,7 @@ open class CommandSerializer(private val activityContext: Context, private val c runBlocking { downloadManager.remove(id) if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) { - delay(1000) + delay(DOWNLOAD_MANAGER_DELAY_TIME) downloadManagerIdRemoved = false } } From 0b1ef7b08c39488166d38530b57f18086734b017 Mon Sep 17 00:00:00 2001 From: Rd Date: Wed, 6 Sep 2023 18:29:30 +0530 Subject: [PATCH 50/52] Project List Updation in Background --- .../src/main/java/org/catrobat/paintroid/MainActivity.kt | 5 +++-- .../catrobat/paintroid/presenter/MainActivityPresenter.kt | 6 ++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt index 15b93d0a2b..35a87cf71d 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt @@ -204,6 +204,8 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { var projectUri: String? = null var projectImagePreviewUri: String? = null var downloadManagerIdRemoved: Boolean = true + var isHomePressed: Boolean = false + const val DOWNLOAD_MANAGER_ID_DELAY = 100L } @@ -438,6 +440,7 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { FileIO.checkFileExists(FileIO.FileType.CATROBAT, it, this.contentResolver) } == true) { + isHomePressed = true downloadManagerIdRemoved = true FileIO.filename = projectName!!.removeSuffix(".$CATROBAT_IMAGE_ENDING") FileIO.storeImageUri = Uri.parse(projectUri) @@ -450,8 +453,6 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { } } } - presenterMain.finishActivity() - projectName = null } else { presenterMain.backToPocketCodeClicked() } diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt b/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt index ac8103e3f6..0ad8c950db 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt @@ -48,6 +48,7 @@ import androidx.core.view.GravityCompat import androidx.test.espresso.idling.CountingIdlingResource import org.catrobat.paintroid.FileIO import org.catrobat.paintroid.MainActivity +import org.catrobat.paintroid.MainActivity.Companion.isHomePressed import org.catrobat.paintroid.MainActivity.Companion.projectName import org.catrobat.paintroid.R import org.catrobat.paintroid.UserPreferences @@ -1055,6 +1056,11 @@ open class MainActivityPresenter( ) else -> throw IllegalArgumentException() } + if (requestCode == SAVE_PROJECT_DEFAULT && isHomePressed) { + navigator.finishActivity() + isHomePressed = false + projectName = null + } } override fun actionToolsClicked() { From c43199fb6bf60b65d6a3345aa1017eb351b86157 Mon Sep 17 00:00:00 2001 From: Rd Date: Fri, 8 Sep 2023 21:25:10 +0530 Subject: [PATCH 51/52] Replacing !!-operator --- Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt index 35a87cf71d..4c6a12c266 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt @@ -442,7 +442,7 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { } == true) { isHomePressed = true downloadManagerIdRemoved = true - FileIO.filename = projectName!!.removeSuffix(".$CATROBAT_IMAGE_ENDING") + FileIO.filename = projectName?.removeSuffix(".$CATROBAT_IMAGE_ENDING").toString() FileIO.storeImageUri = Uri.parse(projectUri) FileIO.storeImagePreviewUri = Uri.parse(projectImagePreviewUri) presenterMain.switchBetweenVersions(PERMISSION_EXTERNAL_STORAGE_SAVE_PROJECT, false) From f8b2267d61aaa56958c98199b9d2d0c67e5e1f5c Mon Sep 17 00:00:00 2001 From: Rd Date: Wed, 4 Oct 2023 01:34:09 +0530 Subject: [PATCH 52/52] Fixed ImagePreview with Suggested Name --- .../java/org/catrobat/paintroid/dialog/SaveInformationDialog.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/dialog/SaveInformationDialog.kt b/Paintroid/src/main/java/org/catrobat/paintroid/dialog/SaveInformationDialog.kt index bb6a54ac4d..7dfd32a8d8 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/dialog/SaveInformationDialog.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/dialog/SaveInformationDialog.kt @@ -152,6 +152,7 @@ class SaveInformationDialog : .setPositiveButton(R.string.save_button_text) { _, _ -> FileIO.filename = imageName.text.toString() FileIO.storeImageUri = null + FileIO.storeImagePreviewUri = null if (FileIO.checkFileExists(FileIO.fileType, FileIO.defaultFileName, requireContext().contentResolver)) { presenter.showOverwriteDialog(permission, isExport) } else {