From ecca7f0512d64098162e2cfaceed0e3b084a3ad3 Mon Sep 17 00:00:00 2001 From: David Andrawes Date: Thu, 12 May 2022 14:46:26 +0200 Subject: [PATCH] CATROID-1420 Looks in scenes becomes corrupted after renaming the scene --- .../uiespresso/ui/fragment/RenameSceneTest.kt | 192 ++++++++++++++++-- .../sensing/CollisionPolygonCreationTask.java | 6 +- .../fragment/RecyclerViewFragment.java | 9 +- .../fragment/SceneListFragment.kt | 10 +- 4 files changed, 194 insertions(+), 23 deletions(-) diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/fragment/RenameSceneTest.kt b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/fragment/RenameSceneTest.kt index 6f8ec0402fd..051aa486dd4 100644 --- a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/fragment/RenameSceneTest.kt +++ b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/fragment/RenameSceneTest.kt @@ -23,8 +23,11 @@ package org.catrobat.catroid.uiespresso.ui.fragment import android.content.Context +import android.media.MediaMetadataRetriever +import android.preference.PreferenceManager.getDefaultSharedPreferences +import android.text.format.DateUtils import android.widget.EditText -import androidx.test.core.app.ApplicationProvider +import androidx.test.core.app.ApplicationProvider.getApplicationContext import androidx.test.espresso.Espresso.closeSoftKeyboard import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions.click @@ -35,20 +38,36 @@ import androidx.test.espresso.matcher.ViewMatchers.isDisplayed 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.getInstrumentation import org.catrobat.catroid.ProjectManager import org.catrobat.catroid.R +import org.catrobat.catroid.common.Constants.IMAGE_DIRECTORY_NAME +import org.catrobat.catroid.common.Constants.SOUND_DIRECTORY_NAME +import org.catrobat.catroid.common.LookData +import org.catrobat.catroid.common.SharedPreferenceKeys.SHOW_DETAILS_LOOKS_PREFERENCE_KEY +import org.catrobat.catroid.common.SharedPreferenceKeys.SHOW_DETAILS_SOUNDS_PREFERENCE_KEY +import org.catrobat.catroid.common.SoundInfo import org.catrobat.catroid.content.Project import org.catrobat.catroid.content.Scene +import org.catrobat.catroid.content.Sprite +import org.catrobat.catroid.io.ResourceImporter.createImageFileFromResourcesInDirectory +import org.catrobat.catroid.io.ResourceImporter.createSoundFileFromResourcesInDirectory import org.catrobat.catroid.io.asynctask.saveProjectSerial import org.catrobat.catroid.test.utils.TestUtils import org.catrobat.catroid.testsuites.annotations.Cat.AppUi import org.catrobat.catroid.testsuites.annotations.Level.Smoke import org.catrobat.catroid.ui.ProjectActivity +import org.catrobat.catroid.ui.SpriteActivity +import org.catrobat.catroid.ui.SpriteActivity.FRAGMENT_LOOKS import org.catrobat.catroid.uiespresso.ui.fragment.rvutils.RecyclerViewInteractionWrapper.onRecyclerView +import org.catrobat.catroid.uiespresso.util.UiTestUtils.Companion.getResourcesString import org.catrobat.catroid.uiespresso.util.UiTestUtils.Companion.openActionBarMenu +import org.catrobat.catroid.uiespresso.util.actions.selectTabAtPosition import org.catrobat.catroid.uiespresso.util.rules.BaseActivityTestRule +import org.catrobat.catroid.utils.FileMetaDataExtractor.getSizeAsString import org.hamcrest.Matchers.allOf import org.hamcrest.Matchers.instanceOf +import org.hamcrest.Matchers.not import org.junit.After import org.junit.Assert.assertEquals import org.junit.Before @@ -57,7 +76,10 @@ import org.junit.Test import org.junit.experimental.categories.Category import org.junit.runner.RunWith import org.koin.java.KoinJavaComponent.inject +import java.io.File +import java.util.Locale.getDefault +@Category(AppUi::class, Smoke::class) @RunWith(AndroidJUnit4::class) class RenameSceneTest { private lateinit var applicationContext: Context @@ -68,6 +90,9 @@ class RenameSceneTest { private val projectManager by inject(ProjectManager::class.java) + private var bufferedShowDetailsLooksPreference = false + private var bufferedShowDetailsSoundsPreference = false + @get:Rule var baseActivityTestRule = BaseActivityTestRule( ProjectActivity::class.java, false, false @@ -75,50 +100,183 @@ class RenameSceneTest { @Before fun setUp() { - applicationContext = ApplicationProvider.getApplicationContext() + applicationContext = getApplicationContext() createProject() + + val sharedPreferences = getDefaultSharedPreferences(applicationContext) + bufferedShowDetailsLooksPreference = sharedPreferences.getBoolean(SHOW_DETAILS_LOOKS_PREFERENCE_KEY, false) + bufferedShowDetailsSoundsPreference = sharedPreferences.getBoolean(SHOW_DETAILS_SOUNDS_PREFERENCE_KEY, false) + + sharedPreferences + .edit() + .putBoolean(SHOW_DETAILS_LOOKS_PREFERENCE_KEY, true) + .putBoolean(SHOW_DETAILS_SOUNDS_PREFERENCE_KEY, true) + .commit() + baseActivityTestRule.launchActivity(null) } - @Category(AppUi::class, Smoke::class) + @After + fun tearDown() { + getDefaultSharedPreferences(getApplicationContext()) + .edit() + .putBoolean(SHOW_DETAILS_LOOKS_PREFERENCE_KEY, bufferedShowDetailsLooksPreference) + .putBoolean(SHOW_DETAILS_SOUNDS_PREFERENCE_KEY, bufferedShowDetailsSoundsPreference) + .commit() + + TestUtils.deleteProjects(projectName) + } + @Test fun testRenameScene() { - val renameSceneString = applicationContext.getString(R.string.rename) - val cancelString = applicationContext.getString(R.string.cancel) - val okString = applicationContext.getString(R.string.ok) - val renameSceneDialogString = applicationContext.getString(R.string.rename_scene_dialog) - val oldSceneName = applicationContext.getString(R.string.default_scene_name) - openActionBarMenu() - onView(withText(renameSceneString)) + + onView(withText(getResourcesString(R.string.rename))) .perform(click()) + onRecyclerView().atPosition(0) .perform(click()) - onView(withText(renameSceneDialogString)) + + onView(withText(getResourcesString(R.string.rename_scene_dialog))) .inRoot(isDialog()) .check(matches(isDisplayed())) - onView(allOf(withText(oldSceneName), isDisplayed(), instanceOf(EditText::class.java))) + + onView(allOf(withText(getResourcesString(R.string.default_scene_name)), isDisplayed(), instanceOf(EditText::class.java))) .perform(replaceText(newSceneName)) + closeSoftKeyboard() - onView(allOf(withId(android.R.id.button2), withText(cancelString))) + + onView(allOf(withId(android.R.id.button2), withText(getResourcesString(R.string.cancel)))) .check(matches(isDisplayed())) - onView(allOf(withId(android.R.id.button1), withText(okString))) + + onView(allOf(withId(android.R.id.button1), withText(getResourcesString(R.string.ok)))) .perform(click()) + onView(withText(newSceneName)) .check(matches(isDisplayed())) + assertEquals(newSceneName, project.defaultScene.name) } - @After - fun tearDown() { - TestUtils.deleteProjects(projectName) + @Test + fun testLooksUnchangedAfterRenameScene() { + openActionBarMenu() + + onView(withText(getResourcesString((R.string.rename)))) + .perform(click()) + + onRecyclerView().atPosition(1) + .perform(click()) + + onView(allOf(withText(otherSceneName), isDisplayed(), instanceOf(EditText::class.java))) + .perform(replaceText(newSceneName)) + + closeSoftKeyboard() + + onView(allOf(withId(android.R.id.button1), withText(getResourcesString(R.string.ok)))) + .perform(click()) + + onView(withText(newSceneName)) + .perform(click()) + + onRecyclerView().atPosition(0) + .perform(click()) + + onView(withId(R.id.tab_layout)) + .perform(selectTabAtPosition(FRAGMENT_LOOKS)) + + val falseDetailsString = String.format(getDefault(), getResourcesString(R.string.look_details),"0 x 0", "0 B") + + onRecyclerView().atPosition(0).onChildView(R.id.details_view) + .check(matches(not(withText(falseDetailsString)))) + + val item = projectManager.currentSprite.lookList.first() + val measureString = item.measure?.get(0).toString() + " x " + item.measure?.get(1) + val correctDetailsString = String.format(getDefault(), getResourcesString(R.string.look_details), + measureString, getSizeAsString(item.file, applicationContext)) + + onRecyclerView().atPosition(0).onChildView(R.id.details_view) + .check(matches(withText(correctDetailsString))) + } + + @Test + fun testSoundsUnchangedAfterRenameScene() { + openActionBarMenu() + + onView(withText(getResourcesString(R.string.rename))) + .perform(click()) + + onRecyclerView().atPosition(1) + .perform(click()) + + onView(allOf(withText(otherSceneName), isDisplayed(), instanceOf(EditText::class.java))) + .perform(replaceText(newSceneName)) + + closeSoftKeyboard() + + onView(allOf(withId(android.R.id.button1), withText(getResourcesString(R.string.ok)))) + .perform(click()) + + onView(withText(newSceneName)) + .perform(click()) + + onRecyclerView().atPosition(0) + .perform(click()) + + onView(withId(R.id.tab_layout)) + .perform(selectTabAtPosition(SpriteActivity.FRAGMENT_SOUNDS)) + + val falseDetailsString = String.format(getDefault(), getResourcesString(R.string.sound_details),"00:00", "0 B") + + onRecyclerView().atPosition(0).onChildView(R.id.details_view) + .check(matches(not(withText(falseDetailsString)))) + + val item = projectManager.currentSprite.soundList.first() + val correctDetailsString = String.format(getDefault(), getResourcesString(R.string.sound_details), + getSoundDuration(item), getSizeAsString(item.file, applicationContext)) + + onRecyclerView().atPosition(0).onChildView(R.id.details_view) + .check(matches(withText(correctDetailsString))) } private fun createProject() { project = Project(applicationContext, projectName) + val sprite = Sprite("Test") val otherScene = Scene(otherSceneName, project) + otherScene.addSprite(sprite) project.addScene(otherScene) projectManager.currentProject = project saveProjectSerial(project, applicationContext) + addLookDataToSprite(sprite, otherScene, "Image.png") + addSoundInfoToSprite(sprite, otherScene, "Sound.mp3") + saveProjectSerial(project, applicationContext) + } + + private fun addLookDataToSprite(sprite: Sprite, currentScene: Scene, name: String) { + val lookFile = createImageFileFromResourcesInDirectory( + getInstrumentation().context.resources, org.catrobat.catroid.test.R.raw.icon, + File(currentScene.directory, IMAGE_DIRECTORY_NAME), name, 1.0) + + val lookData = LookData(lookFile.name, lookFile) + sprite.lookList.add(lookData) + } + + private fun addSoundInfoToSprite(sprite: Sprite, currentScene: Scene, name: String) { + val soundFile = createSoundFileFromResourcesInDirectory( + getInstrumentation().context.resources, org.catrobat.catroid.test.R.raw.longsound, + File(currentScene.directory, SOUND_DIRECTORY_NAME), name) + + val soundInfo = SoundInfo(soundFile.name, soundFile) + sprite.soundList.add(soundInfo) + } + + private fun getSoundDuration(sound: SoundInfo): String { + val metadataRetriever = MediaMetadataRetriever() + metadataRetriever.setDataSource(sound.file?.absolutePath) + + var duration = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLong() ?: 0 + + duration = if (duration / 1000 == 0L) 1 else duration / 1000 + return DateUtils.formatElapsedTime(duration) } } diff --git a/catroid/src/main/java/org/catrobat/catroid/sensing/CollisionPolygonCreationTask.java b/catroid/src/main/java/org/catrobat/catroid/sensing/CollisionPolygonCreationTask.java index 164d0920def..2dc4fc1acb1 100644 --- a/catroid/src/main/java/org/catrobat/catroid/sensing/CollisionPolygonCreationTask.java +++ b/catroid/src/main/java/org/catrobat/catroid/sensing/CollisionPolygonCreationTask.java @@ -1,6 +1,6 @@ /* * Catroid: An on-device visual programming system for Android devices - * Copyright (C) 2010-2018 The Catrobat Team + * Copyright (C) 2010-2022 The Catrobat Team * () * * This program is free software: you can redistribute it and/or modify @@ -28,6 +28,8 @@ import org.catrobat.catroid.common.LookData; +import ar.com.hjg.pngj.PngjInputException; + public class CollisionPolygonCreationTask implements Runnable { private LookData lookdata; private static final String TAG = CollisionPolygonCreationTask.class.getSimpleName(); @@ -42,6 +44,8 @@ public void run() { lookdata.getCollisionInformation().loadCollisionPolygon(); } catch (NullPointerException exception) { Log.e(TAG, "Image format not supported "); + } catch (PngjInputException exception) { + Log.e(TAG, "File not found"); } } } diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/RecyclerViewFragment.java b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/RecyclerViewFragment.java index 05219e31a97..54789c575b6 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/RecyclerViewFragment.java +++ b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/RecyclerViewFragment.java @@ -26,6 +26,7 @@ import android.content.Context; import android.os.Bundle; import android.preference.PreferenceManager; +import android.util.Log; import android.view.ActionMode; import android.view.LayoutInflater; import android.view.Menu; @@ -82,6 +83,8 @@ public abstract class RecyclerViewFragment extends Fragment protected static final int MERGE = 5; protected static final int IMPORT_LOCAL = 6; + private static final String TAG = RecyclerViewFragment.class.getSimpleName(); + protected View parentView; protected RecyclerView recyclerView; protected TextView emptyView; @@ -282,7 +285,11 @@ public void onResume() { @Override public void onPause() { super.onPause(); - adapter.unregisterAdapterDataObserver(observer); + try { + adapter.unregisterAdapterDataObserver(observer); + } catch (IllegalStateException exception) { + Log.d(TAG, "Observer was not registered"); + } } @Override diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/SceneListFragment.kt b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/SceneListFragment.kt index 5fd82068cab..41bcca32323 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/SceneListFragment.kt +++ b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/SceneListFragment.kt @@ -1,6 +1,6 @@ /* * Catroid: An on-device visual programming system for Android devices - * Copyright (C) 2010-2021 The Catrobat Team + * Copyright (C) 2010-2022 The Catrobat Team * () * * This program is free software: you can redistribute it and/or modify @@ -30,14 +30,14 @@ import android.widget.PopupMenu import androidx.annotation.PluralsRes import androidx.appcompat.app.AppCompatActivity import org.catrobat.catroid.ProjectManager - import org.catrobat.catroid.R import org.catrobat.catroid.common.Constants import org.catrobat.catroid.common.SharedPreferenceKeys import org.catrobat.catroid.content.Scene import org.catrobat.catroid.content.Sprite +import org.catrobat.catroid.io.XstreamSerializer import org.catrobat.catroid.io.asynctask.ProjectLoader.ProjectLoadListener -import org.catrobat.catroid.io.asynctask.ProjectSaver +import org.catrobat.catroid.io.asynctask.loadProject import org.catrobat.catroid.ui.controller.BackpackListManager import org.catrobat.catroid.ui.recyclerview.adapter.SceneAdapter import org.catrobat.catroid.ui.recyclerview.backpack.BackpackActivity @@ -191,7 +191,9 @@ class SceneListFragment : RecyclerViewFragment(), if (item?.name != name) { if (sceneController.rename(item, name)) { val currentProject = projectManager.currentProject - ProjectSaver(currentProject, requireContext()).saveProjectAsync() + XstreamSerializer.getInstance().saveProject(currentProject) + loadProject(currentProject.directory, requireContext().applicationContext) + initializeAdapter() } else { ToastUtil.showError(activity, R.string.error_rename_scene) }