diff --git a/.gitignore b/.gitignore index 60d2c045..06ca2609 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ /captures *debug* *release* +*.swp +*.swo +*~ diff --git a/app/build.gradle b/app/build.gradle index 9b72296b..f733a860 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -25,11 +25,26 @@ android { } buildTypes { release { + versionNameSuffix ".release" minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + buildConfigField "String", "ROCC_URL_PREFIX", ROCC_URL_PREFIX + buildConfigField "String", "ROCC_WEBSOCKETS_PREFIX", ROCC_WEBSOCKETS_PREFIX + buildConfigField "Boolean", "ENABLE_IN_APP_ROCC_URL_SETTING", "false" + resValue "bool", "enable_in_app_rocc_url_setting", "false" } debug { versionNameSuffix ".debug" + buildConfigField "String", "ROCC_URL_PREFIX", ROCC_URL_PREFIX + buildConfigField "String", "ROCC_WEBSOCKETS_PREFIX", ROCC_WEBSOCKETS_PREFIX + buildConfigField "Boolean", "ENABLE_IN_APP_ROCC_URL_SETTING", "true" + resValue "bool", "enable_in_app_rocc_url_setting", "true" + } + clientDebug { + initWith debug + buildConfigField "Boolean", "ENABLE_IN_APP_ROCC_URL_SETTING", "false" + resValue "bool", "enable_in_app_rocc_url_setting", "false" + } } defaultConfig { @@ -80,12 +95,12 @@ dependencies { implementation 'com.github.codekidX:storage-chooser:2.0.3' implementation 'com.squareup.moshi:moshi:1.8.0' implementation 'com.opencsv:opencsv:4.3.2' - implementation 'org.jsoup:jsoup:1.11.3' implementation 'com.android.support:support-v4:28.0.0' implementation 'android.arch.lifecycle:extensions:1.1.1' implementation 'com.android.support.constraint:constraint-layout:1.1.3' implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1' implementation 'com.google.firebase:firebase-core:16.0.9' + implementation 'org.java-websocket:Java-WebSocket:1.4.1' kapt 'com.squareup.moshi:moshi-kotlin-codegen:1.6.0' testImplementation 'junit:junit:4.12' testImplementation 'androidx.test:core:1.2.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b030777d..53ccd32c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -19,6 +19,7 @@ android:theme="@style/AppTheme" android:largeHeap="true" android:fullBackupContent="false" + android:usesCleartextTraffic="true" tools:replace="android:icon,android:roundIcon" > @@ -32,12 +33,6 @@ android:resource="@xml/file_paths"/> - - - - - - - - - - \ No newline at end of file + diff --git a/app/src/main/java/org/sil/storyproducer/controller/MainActivity.kt b/app/src/main/java/org/sil/storyproducer/controller/MainActivity.kt index 7815dad1..e7fea7cd 100644 --- a/app/src/main/java/org/sil/storyproducer/controller/MainActivity.kt +++ b/app/src/main/java/org/sil/storyproducer/controller/MainActivity.kt @@ -1,5 +1,6 @@ package org.sil.storyproducer.controller +import android.app.Activity import android.app.AlertDialog import android.content.BroadcastReceiver import android.content.Context @@ -7,7 +8,7 @@ import android.content.Intent import android.content.IntentFilter import android.net.ConnectivityManager import android.os.Bundle -import android.os.SystemClock +import android.preference.PreferenceManager import android.support.design.widget.NavigationView import android.support.v4.view.GravityCompat import android.support.v4.widget.DrawerLayout @@ -19,21 +20,91 @@ import android.view.Menu import android.view.MenuItem import android.view.View import android.webkit.WebView +import android.widget.EditText +import android.widget.LinearLayout import android.widget.ProgressBar import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.Job import kotlinx.coroutines.async import org.sil.storyproducer.R -import org.sil.storyproducer.model.Phase -import org.sil.storyproducer.model.PhaseType -import org.sil.storyproducer.model.Story import org.sil.storyproducer.model.Workspace import org.sil.storyproducer.tools.Network.ConnectivityStatus import org.sil.storyproducer.tools.Network.VolleySingleton import java.io.Serializable +fun handleDrawerItemSelection(activity: Activity, menuItem: MenuItem, drawerLayout: DrawerLayout, currentItemId: Int?): Boolean { + menuItem.isChecked = true + drawerLayout.closeDrawers() + + if (currentItemId == menuItem.itemId) { + return true + } + + when (menuItem.itemId) { + R.id.nav_workspace -> { + val intent = Intent(activity, WorkspaceUpdateActivity::class.java) + activity.startActivity(intent) + activity.finish() + } + R.id.nav_stories -> { + val intent = Intent(activity, MainActivity::class.java) + activity.startActivity(intent) + activity.finish() + } + R.id.nav_registration -> { + val intent = Intent(activity, RegistrationActivity::class.java) + activity.startActivity(intent) + activity.finish() + } + R.id.nav_license -> { + val dialog = AlertDialog.Builder(activity) + .setTitle(activity.getString(R.string.license_title)) + .setMessage(activity.getString(R.string.license_body)) + .setPositiveButton(activity.getString(R.string.ok)) { _, _ -> }.create() + dialog.show() + } + R.id.nav_set_rocc_url_prefix -> { + val container = LinearLayout(activity) + container.orientation = LinearLayout.VERTICAL + container.layoutParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.MATCH_PARENT) + val roccPrefixInput = EditText(activity) + roccPrefixInput.setText(PreferenceManager.getDefaultSharedPreferences(activity).getString("ROCC_URL_PREFIX", "") + ?: "") + container.addView(roccPrefixInput, + LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT)) + val webSocketsInput = EditText(activity) + container.addView(webSocketsInput, + LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT)) + webSocketsInput.setText(PreferenceManager.getDefaultSharedPreferences(activity).getString("WEBSOCKETS_URL", "") + ?: "") + val dialog = AlertDialog.Builder(activity) + .setTitle("ROCC URL Prefix") + .setMessage("Enter a string to prefix all requests to the remote consultant site") + .setView(container) + .setPositiveButton(activity.getString(R.string.ok)) { _, _ -> + PreferenceManager.getDefaultSharedPreferences(activity).edit() + .putString("ROCC_URL_PREFIX", roccPrefixInput.text.toString()) + .putString("WEBSOCKETS_URL", webSocketsInput.text.toString()).apply() + }.create() + dialog.show() + } + R.id.nav_clear_all_messages -> { + } + R.id.nav_forget_remote_story_id -> { + Workspace.activeStory.remoteId = null + } + } + + return true +} + class MainActivity : AppCompatActivity(), Serializable { - private var mDrawerLayout: DrawerLayout? = null + private lateinit var mDrawerLayout: DrawerLayout private val receiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { @@ -60,7 +131,7 @@ class MainActivity : AppCompatActivity(), Serializable { pb.visibility = View.VISIBLE GlobalScope.async { - if(!Workspace.isInitialized) Workspace.initializeWorskpace(this@MainActivity.applicationContext) + if (!Workspace.isInitialized) Workspace.initializeWorkspace(this@MainActivity.applicationContext) runOnUiThread { pb.visibility = View.GONE supportFragmentManager.beginTransaction().add(R.id.fragment_container, StoryListFrag()).commit() @@ -74,19 +145,10 @@ class MainActivity : AppCompatActivity(), Serializable { return true } - /** - * move to the chosen story - */ - fun switchToStory(story: Story) { - Workspace.activeStory = story - val intent = Intent(this.applicationContext, Workspace.activePhase.getTheClass()) - startActivity(intent) - } - override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { android.R.id.home -> { - mDrawerLayout!!.openDrawer(GravityCompat.START) + mDrawerLayout.openDrawer(GravityCompat.START) true } R.id.helpButton -> { @@ -94,11 +156,12 @@ class MainActivity : AppCompatActivity(), Serializable { alert.setTitle("Story List Help") val wv = WebView(this) - val iStream = assets.open(Phase.getHelpName(PhaseType.STORY_LIST)) + val iStream = assets.open("story_list.html") val text = iStream.reader().use { - it.readText() } + it.readText() + } - wv.loadData(text,"text/html",null) + wv.loadData(text, "text/html", null) alert.setView(wv) alert.setNegativeButton("Close") { dialog, _ -> dialog!!.dismiss() @@ -116,52 +179,18 @@ class MainActivity : AppCompatActivity(), Serializable { private fun setupDrawer() { val toolbar = findViewById(R.id.toolbar) setSupportActionBar(toolbar) - val actionbar: ActionBar? = supportActionBar - actionbar?.apply { + val actionbar: ActionBar = supportActionBar!! + actionbar.apply { setDisplayHomeAsUpEnabled(true) setHomeAsUpIndicator(R.drawable.ic_menu_white_24dp) } - supportActionBar!!.setDisplayHomeAsUpEnabled(true) - supportActionBar!!.setHomeButtonEnabled(true) - mDrawerLayout = findViewById(R.id.drawer_layout) - //Lock from opening with left swipe - mDrawerLayout!!.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) + mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) val navigationView: NavigationView = findViewById(R.id.nav_view) + val drawerLayout = mDrawerLayout navigationView.setNavigationItemSelectedListener { menuItem -> - // set item as selected to persist highlight - menuItem.isChecked = true - // close drawer when item is tapped - mDrawerLayout!!.closeDrawers() - - // Add code here to update the UI based on the item selected - // For example, swap UI fragments here - val intent: Intent - when (menuItem.itemId) { - R.id.nav_workspace -> { - intent = Intent(this, WorkspaceUpdateActivity::class.java) - this.startActivity(intent) - this.finish() - } - R.id.nav_stories -> { - // Current fragment - } - R.id.nav_registration -> { - intent = Intent(this, RegistrationActivity::class.java) - this.startActivity(intent) - this.finish() - } - R.id.nav_license -> { - val dialog = AlertDialog.Builder(this) - .setTitle(this.getString(R.string.license_title)) - .setMessage(this.getString(R.string.license_body)) - .setPositiveButton(this.getString(R.string.ok)) { _, _ -> }.create() - dialog.show() - } - } - - true + handleDrawerItemSelection(this, menuItem, drawerLayout, R.id.nav_stories) } } diff --git a/app/src/main/java/org/sil/storyproducer/controller/MultiRecordFrag.kt b/app/src/main/java/org/sil/storyproducer/controller/MultiRecordFrag.kt index 4531ac1a..9888794c 100644 --- a/app/src/main/java/org/sil/storyproducer/controller/MultiRecordFrag.kt +++ b/app/src/main/java/org/sil/storyproducer/controller/MultiRecordFrag.kt @@ -1,182 +1,177 @@ -package org.sil.storyproducer.controller - -import android.app.Activity -import android.app.AlertDialog -import android.content.Intent -import android.os.Bundle -import android.os.Environment -import android.provider.MediaStore -import android.support.v4.content.FileProvider -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.EditText -import android.widget.ImageView -import android.widget.Toast -import com.crashlytics.android.Crashlytics -import org.sil.storyproducer.BuildConfig -import org.sil.storyproducer.R -import org.sil.storyproducer.model.PROJECT_DIR -import org.sil.storyproducer.model.SLIDE_NUM -import org.sil.storyproducer.model.SlideType -import org.sil.storyproducer.model.Workspace -import org.sil.storyproducer.tools.file.copyToWorkspacePath -import org.sil.storyproducer.tools.toolbar.MultiRecordRecordingToolbar -import org.sil.storyproducer.tools.toolbar.PlayBackRecordingToolbar -import org.sil.storyproducer.tools.toolbar.RecordingToolbar -import java.io.File - -/** - * The fragment for the Draft view. This is where a user can draft out the story slide by slide - */ -abstract class MultiRecordFrag : SlidePhaseFrag(), PlayBackRecordingToolbar.ToolbarMediaListener { - protected open var recordingToolbar: RecordingToolbar = MultiRecordRecordingToolbar() - - private var tempPicFile: File? = null - - - override fun onCreateView(inflater: LayoutInflater, - container: ViewGroup?, savedInstanceState: Bundle?): View? { - super.onCreateView(inflater, container, savedInstanceState) - if (Workspace.activeStory.slides[slideNum].slideType != SlideType.LOCALCREDITS) { - setToolbar() - } - - setupCameraAndEditButton() - - return rootView - } - - /** - * Setup camera button for updating background image - * and edit button for renaming text and local credits - */ - fun setupCameraAndEditButton() { - // display the image selection button, if on the title slide - if(Workspace.activeStory.slides[slideNum].slideType in - arrayOf(SlideType.FRONTCOVER,SlideType.LOCALSONG)) - { - val imageFab: ImageView = rootView!!.findViewById(R.id.insert_image_view) as ImageView - imageFab.visibility = View.VISIBLE - imageFab.setOnClickListener { - val chooser = Intent(Intent.ACTION_CHOOSER) - chooser.putExtra(Intent.EXTRA_TITLE, "Select From:") - - val galleryIntent = Intent(Intent.ACTION_GET_CONTENT) - galleryIntent.type = "image/*" - chooser.putExtra(Intent.EXTRA_INTENT, galleryIntent) - - val cameraIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) - cameraIntent.resolveActivity(activity!!.packageManager).also { - tempPicFile = File.createTempFile("temp", ".jpg", activity?.getExternalFilesDir(Environment.DIRECTORY_PICTURES)) - cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, FileProvider.getUriForFile(activity!!, "${BuildConfig.APPLICATION_ID}.fileprovider", tempPicFile!!)) - } - chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, arrayOf(cameraIntent)) - - startActivityForResult(chooser, ACTIVITY_SELECT_IMAGE) - } - } - - // display the image selection button, if on the title slide - if(Workspace.activeStory.slides[slideNum].slideType in - arrayOf(SlideType.FRONTCOVER,SlideType.LOCALCREDITS)) - { - //for these, use the edit text button instead of the text in the lower half. - //In the phases that these are not there, do nothing. - val editBox = rootView?.findViewById(R.id.fragment_dramatization_edit_text) as EditText? - editBox?.visibility = View.INVISIBLE - - val editFab = rootView!!.findViewById(R.id.edit_text_view) as ImageView? - editFab?.visibility = View.VISIBLE - editFab?.setOnClickListener { - val editText = EditText(context) - editText.id = R.id.edit_text_input - - // Programmatically set layout properties for edit text field - val params = ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT) - // Apply layout properties - editText.layoutParams = params - editText.minLines = 5 - editText.text.insert(0,Workspace.activeSlide!!.translatedContent) - - val dialog = AlertDialog.Builder(context) - .setTitle(getString(R.string.enter_text)) - .setView(editText) - .setNegativeButton(getString(R.string.cancel), null) - .setPositiveButton(getString(R.string.save)) { _, _ -> - Workspace.activeSlide!!.translatedContent = editText.text.toString() - setPic(rootView!!.findViewById(R.id.fragment_image_view) as ImageView) - }.create() - - dialog.show() - } - } - } - - - /** - * Change the picture behind the screen. - */ - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - try { - if (resultCode == Activity.RESULT_OK && requestCode == ACTIVITY_SELECT_IMAGE) { - //copy image into workspace - var uri = data?.data - if (uri == null) uri = FileProvider.getUriForFile(context!!, "${BuildConfig.APPLICATION_ID}.fileprovider", tempPicFile!!) //it was a camera intent - Workspace.activeStory.slides[slideNum].imageFile = "$PROJECT_DIR/${slideNum}_Local.png" - copyToWorkspacePath(context!!, uri!!, - "${Workspace.activeStory.title}/${Workspace.activeStory.slides[slideNum].imageFile}") - tempPicFile?.delete() - setPic(rootView!!.findViewById(R.id.fragment_image_view) as ImageView) - } - }catch (e:Exception){ - Toast.makeText(context,"Error",Toast.LENGTH_SHORT).show() - Crashlytics.logException(e) - } - } - - /** - * This function serves to handle page changes and stops the audio streams from - * continuing. - * - * @param isVisibleToUser whether fragment is currently visible to user - */ - override fun setUserVisibleHint(isVisibleToUser: Boolean) { - super.setUserVisibleHint(isVisibleToUser) - - // Make sure that we are currently visible - if (this.isVisible) { - // If we are becoming invisible, then... - if (!isVisibleToUser) { - recordingToolbar.stopToolbarMedia() - } - } - } - - protected open fun setToolbar() { - val bundle = Bundle() - bundle.putInt(SLIDE_NUM, slideNum) - recordingToolbar.arguments = bundle - childFragmentManager.beginTransaction().replace(R.id.toolbar_for_recording_toolbar, recordingToolbar).commit() - - recordingToolbar.keepToolbarVisible() - } - - override fun onStartedToolbarMedia() { - super.onStartedToolbarMedia() - - stopSlidePlayBack() - } - - override fun onStartedSlidePlayBack() { - super.onStartedSlidePlayBack() - - recordingToolbar.stopToolbarMedia() - } - - companion object { - private const val ACTIVITY_SELECT_IMAGE = 53 - } -} +package org.sil.storyproducer.controller + +import android.app.Activity +import android.app.AlertDialog +import android.content.Intent +import android.os.Bundle +import android.os.Environment +import android.provider.MediaStore +import android.support.v4.content.FileProvider +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.EditText +import android.widget.ImageView +import android.widget.Toast +import com.crashlytics.android.Crashlytics +import org.sil.storyproducer.BuildConfig +import org.sil.storyproducer.R +import org.sil.storyproducer.model.PROJECT_DIR +import org.sil.storyproducer.model.SLIDE_NUM +import org.sil.storyproducer.model.PHASE_TYPE +import org.sil.storyproducer.model.PhaseType +import org.sil.storyproducer.model.SlideType +import org.sil.storyproducer.model.Workspace +import org.sil.storyproducer.tools.file.copyToWorkspacePath +import org.sil.storyproducer.tools.toolbar.MultiRecordRecordingToolbar +import org.sil.storyproducer.tools.toolbar.PlayBackRecordingToolbar +import org.sil.storyproducer.tools.toolbar.RecordingToolbar +import java.io.File + +/** + * The fragment for the Draft view. This is where a user can draft out the story slide by slide + */ +abstract class MultiRecordFrag : SlidePhaseFrag(), PlayBackRecordingToolbar.ToolbarMediaListener { + protected open var recordingToolbar: RecordingToolbar = MultiRecordRecordingToolbar() + + private var tempPicFile: File? = null + + override fun initializeViews() { + super.initializeViews() + if (Workspace.activeStory.slides[slideNumber].slideType != SlideType.LOCALCREDITS) { + setToolbar() + } + setupCameraAndEditButton() + } + + /** + * Setup camera button for updating background image + * and edit button for renaming text and local credits + */ + fun setupCameraAndEditButton() { + // display the image selection button, if on the title slide + if(Workspace.activeStory.slides[slideNumber].slideType in arrayOf(SlideType.FRONTCOVER,SlideType.LOCALSONG) + && phaseType != PhaseType.BACKT) + { + val imageFab: ImageView = rootView.findViewById(R.id.insert_image_view) as ImageView + imageFab.visibility = View.VISIBLE + imageFab.setOnClickListener { + val chooser = Intent(Intent.ACTION_CHOOSER) + chooser.putExtra(Intent.EXTRA_TITLE, "Select From:") + + val galleryIntent = Intent(Intent.ACTION_GET_CONTENT) + galleryIntent.type = "image/*" + chooser.putExtra(Intent.EXTRA_INTENT, galleryIntent) + + val cameraIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) + cameraIntent.resolveActivity(activity!!.packageManager).also { + tempPicFile = File.createTempFile("temp", ".jpg", activity?.getExternalFilesDir(Environment.DIRECTORY_PICTURES)) + cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, FileProvider.getUriForFile(activity!!, "${BuildConfig.APPLICATION_ID}.fileprovider", tempPicFile!!)) + } + chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, arrayOf(cameraIntent)) + + startActivityForResult(chooser, ACTIVITY_SELECT_IMAGE) + } + + //for these, use the edit text button instead of the text in the lower half. + //In the phases that these are not there, do nothing. + val editBox = rootView.findViewById(R.id.fragment_dramatization_edit_text) as EditText? + editBox?.visibility = View.INVISIBLE + + val editFab = rootView.findViewById(R.id.edit_text_view) as ImageView? + editFab?.visibility = View.VISIBLE + editFab?.setOnClickListener { + val editText = EditText(context) + editText.id = R.id.edit_text_input + + // Programmatically set layout properties for edit text field + val params = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT) + // Apply layout properties + editText.layoutParams = params + editText.minLines = 5 + editText.text.insert(0,Workspace.activeSlide!!.translatedContent) + + val dialog = AlertDialog.Builder(context) + .setTitle(getString(R.string.enter_text)) + .setView(editText) + .setNegativeButton(getString(R.string.cancel), null) + .setPositiveButton(getString(R.string.save)) { _, _ -> + Workspace.activeSlide!!.translatedContent = editText.text.toString() + setPic() + }.create() + + dialog.show() + } + } + } + + + /** + * Change the picture behind the screen. + */ + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + try { + if (resultCode == Activity.RESULT_OK && requestCode == ACTIVITY_SELECT_IMAGE) { + //copy image into workspace + var uri = data?.data + if (uri == null) uri = FileProvider.getUriForFile(context!!, "${BuildConfig.APPLICATION_ID}.fileprovider", tempPicFile!!) //it was a camera intent + Workspace.activeStory.slides[slideNumber].imageFile = "$PROJECT_DIR/${slideNumber}_Local.png" + copyToWorkspacePath(context!!, uri!!, + "${Workspace.activeStory.title}/${Workspace.activeStory.slides[slideNumber].imageFile}") + tempPicFile?.delete() + setPic() + } + }catch (e:Exception){ + Toast.makeText(context,"Error",Toast.LENGTH_SHORT).show() + Crashlytics.logException(e) + } + } + + /** + * This function serves to handle page changes and stops the audio streams from + * continuing. + * + * @param isVisibleToUser whether fragment is currently visible to user + */ + override fun setUserVisibleHint(isVisibleToUser: Boolean) { + super.setUserVisibleHint(isVisibleToUser) + + // Make sure that we are currently visible + if (this.isVisible) { + // If we are becoming invisible, then... + if (!isVisibleToUser) { + if (Workspace.activeStory.slides[slideNumber].slideType != SlideType.LOCALCREDITS) { + recordingToolbar.stopToolbarMedia() + } + } + } + } + + protected open fun setToolbar() { + val bundle = Bundle() + bundle.putInt(SLIDE_NUM, slideNumber) + bundle.putInt(PHASE_TYPE, phaseType.ordinal) + recordingToolbar.arguments = bundle + childFragmentManager.beginTransaction().replace(R.id.toolbar_for_recording_toolbar, recordingToolbar).commit() + + recordingToolbar.keepToolbarVisible() + } + + override fun onStartedToolbarMedia() { + super.onStartedToolbarMedia() + + stopSlidePlayBack() + } + + override fun onStartedSlidePlayBack() { + super.onStartedSlidePlayBack() + + recordingToolbar.stopToolbarMedia() + } + + companion object { + private const val ACTIVITY_SELECT_IMAGE = 53 + } +} diff --git a/app/src/main/java/org/sil/storyproducer/controller/RegistrationActivity.kt b/app/src/main/java/org/sil/storyproducer/controller/RegistrationActivity.kt index b03bf3ff..0284f5fe 100644 --- a/app/src/main/java/org/sil/storyproducer/controller/RegistrationActivity.kt +++ b/app/src/main/java/org/sil/storyproducer/controller/RegistrationActivity.kt @@ -3,33 +3,31 @@ package org.sil.storyproducer.controller import android.Manifest import android.app.Activity import android.app.AlertDialog -import android.content.ContentResolver import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.net.Uri import android.os.Build import android.os.Bundle -import android.provider.Settings.Secure +import android.provider.Settings import android.support.design.widget.TextInputEditText -import android.support.design.widget.TextInputLayout import android.support.v4.app.ActivityCompat import android.support.v4.content.res.ResourcesCompat import android.support.v7.app.AppCompatActivity import android.text.Html import android.util.Log import android.view.View -import android.view.ViewGroup import android.view.inputmethod.InputMethodManager -import android.widget.* -import com.android.volley.Request +import android.widget.Button +import android.widget.EditText +import android.widget.Spinner +import android.widget.Toast import com.android.volley.Response import com.android.volley.toolbox.StringRequest import com.crashlytics.android.Crashlytics import org.sil.storyproducer.R import org.sil.storyproducer.model.Workspace import org.sil.storyproducer.tools.Network.VolleySingleton -import java.util.* /** * The purpose of this class is to create the Registration activity. @@ -42,16 +40,9 @@ import java.util.* * * * setAccordionListener() is called which adds click listeners to the header sections of the accordion. * - * 1. onPostCreate() is called and calls the following: + * 2. onPostCreate() is called and calls the following: * - * 1. setupInputFields() is called which takes a root ScrollView. - * - * * getInputFields() is called and takes the root ScrollView and does an in-order - * traversal of the nodes in the registration xml to find the TextInputEditText - * and Spinner inputs. Each TextInputEditText and Spinner inputs are added to the - * sectionViews[] for parsing and saving. - * - * 1. addSubmitButtonSave() is called which only parses the TextInpuEditText(not the Spinner input) to check for valid inputs. + * 3. addSubmitButtonSave() is called which only parses the TextInpuEditText(not the Spinner input) to check for valid inputs. * * * textFieldParsed() is called. This checks to see if all fields were entered * * A confirmation dialog is launched to ask if the user wants to submit the info @@ -66,91 +57,266 @@ import java.util.* * * [android.support.design.widget.TextInputEditText] for inputting text for registration fields. * */ -const val FIRST_ACTIVITY_KEY = "first" - open class RegistrationActivity : AppCompatActivity() { - private val sectionIds = intArrayOf(R.id.language_section, R.id.translator_section, R.id.consultant_section, R.id.trainer_section, R.id.archive_section) - private val headerIds = intArrayOf(R.id.language_header, R.id.translator_header, R.id.consultant_header, R.id.trainer_header, R.id.archive_header) - private val sectionViews = arrayOfNulls(sectionIds.size) - private val headerViews = arrayOfNulls(headerIds.size) + private lateinit var projectLanguageEditText: TextInputEditText + private lateinit var projectEthnoCodeEditText: TextInputEditText + private lateinit var projectCountryEditText: TextInputEditText + private lateinit var projectRegionEditText: TextInputEditText + private lateinit var projectCityEditText: TextInputEditText + private lateinit var projectMajorityLanguageEditText: TextInputEditText + private lateinit var projectOrthographySpinner: Spinner + + private lateinit var translatorNameEditText: TextInputEditText + private lateinit var translatorEducationEditText: TextInputEditText + private lateinit var translatorLanguagesEditText: TextInputEditText + private lateinit var translatorPhoneEditText: TextInputEditText + private lateinit var translatorEmailEditText: TextInputEditText + private lateinit var translatorCommunicationPreferenceSpinner: Spinner + private lateinit var translatorLocationEditText: TextInputEditText + + private lateinit var consultantNameEditText: TextInputEditText + private lateinit var consultantLanguagesEditText: TextInputEditText + private lateinit var consultantPhoneEditText: TextInputEditText + private lateinit var consultantEmailEditText: TextInputEditText + private lateinit var consultantCommunicationPreferenceSpinner: Spinner + private lateinit var consultantLocationEditText: TextInputEditText + private lateinit var consultantLocationTypeSpinner: Spinner + + private lateinit var trainerNameEditText: TextInputEditText + private lateinit var trainerLanguagesEditText: TextInputEditText + private lateinit var trainerPhoneEditText: TextInputEditText + private lateinit var trainerEmailEditText: TextInputEditText + private lateinit var trainerCommunicationPreferenceSpinner: Spinner + private lateinit var trainerLocationEditText: TextInputEditText + + private lateinit var archiveEmail1EditText: TextInputEditText + private lateinit var archiveEmail2EditText: TextInputEditText + private lateinit var archiveEmail3EditText: TextInputEditText + + private lateinit var sectionViews: Array + private lateinit var headerViews: Array var resp: String? = null var testErr = "" - var js: MutableMap = hashMapOf() - private var inputFields: List? = null + private lateinit var inputFields: Array override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - //first get permissions - val PERMISSION_ALL = 1 - val PERMISSIONS = arrayOf(Manifest.permission.RECORD_AUDIO, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE) - - if (!hasPermissions(this, *PERMISSIONS)) { - ActivityCompat.requestPermissions(this, PERMISSIONS, PERMISSION_ALL) + val permissions = arrayOf(Manifest.permission.RECORD_AUDIO, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE) + if (!hasPermissions(this, *permissions)) { + val permissionsAll = 1 + ActivityCompat.requestPermissions(this, permissions, permissionsAll) } setContentView(R.layout.activity_registration) - //Initialize sectionViews[] with the integer id's of the various LinearLayouts - //Add the listeners to the LinearLayouts's header section. - for (i in sectionIds.indices) { - sectionViews[i] = findViewById(sectionIds[i]) - headerViews[i] = findViewById(headerIds[i]) - setAccordionListener(findViewById(headerIds[i]), sectionViews[i]!!) + // TODO @pwhite: Some of the ugliness could be removed while + // simultaneously improving user experience if some field for the + // translator, consultant, and trainer were put in a `Person` + // abstraction. The UI could reflect this be reusing the same fragment + // for each of the forms. Or perhaps the ROCC can serve some + // pre-populated people which the user can select from; this would mean + // that the user would not have to fill out each of the forms and + // potentially make a mistake with an email, for example. Of course, + // this ought not to be required since the application might not be + // online. + + // @pwhite: This is a lot of boilerplate to load the current values of + // the registration.json file. I'm not the most pleased, but I find + // this far better than doing a walk of the UI tree to find all the + // text and spinner controls and setting them from a JSON object based + // on some string manipulation of the android resource IDs. This was a + // previous solution which has been replaced by this ugliness; although + // this solution is rather ugly, it is extremely clear what is + // happening, and quite easy to modify. In addition, this is likely + // more efficient than the previous solution since we know exactly what + // controls exist and thus don't need to walk the UI tree to find them. + + // NOW @pwhite: Add phone make, model and manufacturer as before. + val orthographySpinnerOptions = resources.getStringArray(R.array.orthography_list) + val communicationSpinnerOptions = resources.getStringArray(R.array.communication_list) + val locationTypeSpinnerOptions = resources.getStringArray(R.array.location_type_list) + + projectLanguageEditText = findViewById(R.id.input_language) + projectLanguageEditText.setText(Workspace.registration.projectLanguage) + projectEthnoCodeEditText = findViewById(R.id.input_ethnologue) + projectEthnoCodeEditText.setText(Workspace.registration.projectEthnoCode) + projectCountryEditText = findViewById(R.id.input_country) + projectCountryEditText.setText(Workspace.registration.projectCountry) + projectRegionEditText = findViewById(R.id.input_location) + projectRegionEditText.setText(Workspace.registration.projectRegion) + projectCityEditText = findViewById(R.id.input_town) + projectCityEditText.setText(Workspace.registration.projectCity) + projectMajorityLanguageEditText = findViewById(R.id.input_lwc) + projectMajorityLanguageEditText.setText(Workspace.registration.projectMajorityLanguage) + projectOrthographySpinner = findViewById(R.id.input_orthography) + projectOrthographySpinner.setSelection( + indexOfOrZero(orthographySpinnerOptions, Workspace.registration.projectOrthography)) + + translatorNameEditText = findViewById(R.id.input_translator_name) + translatorNameEditText.setText(Workspace.registration.translatorName) + translatorEducationEditText = findViewById(R.id.input_translator_education) + translatorEducationEditText.setText(Workspace.registration.translatorEducation) + translatorLanguagesEditText = findViewById(R.id.input_translator_languages) + translatorLanguagesEditText.setText(Workspace.registration.translatorLanguages) + translatorPhoneEditText = findViewById(R.id.input_translator_phone) + translatorPhoneEditText.setText(Workspace.registration.translatorPhone) + translatorEmailEditText = findViewById(R.id.input_translator_email) + translatorEmailEditText.setText(Workspace.registration.translatorEmail) + translatorCommunicationPreferenceSpinner = findViewById(R.id.input_translator_communication_preference) + translatorCommunicationPreferenceSpinner.setSelection( + indexOfOrZero(communicationSpinnerOptions, Workspace.registration.translatorCommunicationPreference)) + translatorLocationEditText = findViewById(R.id.input_translator_location) + translatorLocationEditText.setText(Workspace.registration.translatorLocation) + + consultantNameEditText = findViewById(R.id.input_consultant_name) + consultantNameEditText.setText(Workspace.registration.consultantName) + consultantLanguagesEditText = findViewById(R.id.input_consultant_languages) + consultantLanguagesEditText.setText(Workspace.registration.consultantLanguages) + consultantPhoneEditText = findViewById(R.id.input_consultant_phone) + consultantPhoneEditText.setText(Workspace.registration.consultantPhone) + consultantEmailEditText = findViewById(R.id.input_consultant_email) + consultantEmailEditText.setText(Workspace.registration.consultantEmail) + consultantCommunicationPreferenceSpinner = findViewById(R.id.input_consultant_communication_preference) + consultantCommunicationPreferenceSpinner.setSelection( + indexOfOrZero(communicationSpinnerOptions, Workspace.registration.consultantCommunicationPreference)) + consultantLocationEditText = findViewById(R.id.input_consultant_location) + consultantLocationEditText.setText(Workspace.registration.consultantLocation) + consultantLocationTypeSpinner = findViewById(R.id.input_consultant_location_type) + consultantLocationTypeSpinner.setSelection( + indexOfOrZero(locationTypeSpinnerOptions, Workspace.registration.consultantLocationType)) + + trainerNameEditText = findViewById(R.id.input_trainer_name) + trainerNameEditText.setText(Workspace.registration.trainerName) + trainerLanguagesEditText = findViewById(R.id.input_trainer_languages) + trainerLanguagesEditText.setText(Workspace.registration.trainerLanguages) + trainerPhoneEditText = findViewById(R.id.input_trainer_phone) + trainerPhoneEditText.setText(Workspace.registration.trainerPhone) + trainerEmailEditText = findViewById(R.id.input_trainer_email) + trainerEmailEditText.setText(Workspace.registration.trainerEmail) + trainerCommunicationPreferenceSpinner = findViewById(R.id.input_trainer_communication_preference) + trainerCommunicationPreferenceSpinner.setSelection( + indexOfOrZero(communicationSpinnerOptions, Workspace.registration.trainerCommunicationPreference)) + trainerLocationEditText = findViewById(R.id.input_trainer_location) + trainerLocationEditText.setText(Workspace.registration.trainerLocation) + + archiveEmail1EditText = findViewById(R.id.input_database_email_1) + archiveEmail1EditText.setText(Workspace.registration.archiveEmail1) + archiveEmail2EditText = findViewById(R.id.input_database_email_2) + archiveEmail2EditText.setText(Workspace.registration.archiveEmail2) + archiveEmail3EditText = findViewById(R.id.input_database_email_3) + archiveEmail3EditText.setText(Workspace.registration.archiveEmail3) + + inputFields = arrayOf( + projectLanguageEditText, + projectEthnoCodeEditText, + projectCountryEditText, + projectRegionEditText, + projectCityEditText, + projectMajorityLanguageEditText, + projectOrthographySpinner, + translatorNameEditText, + translatorEducationEditText, + translatorLanguagesEditText, + translatorPhoneEditText, + translatorEmailEditText, + translatorCommunicationPreferenceSpinner, + translatorLocationEditText, + consultantNameEditText, + consultantLanguagesEditText, + consultantPhoneEditText, + consultantEmailEditText, + consultantCommunicationPreferenceSpinner, + consultantLocationEditText, + consultantLocationTypeSpinner, + trainerNameEditText, + trainerLanguagesEditText, + trainerPhoneEditText, + trainerEmailEditText, + trainerCommunicationPreferenceSpinner, + trainerLocationEditText, + archiveEmail1EditText, + archiveEmail2EditText, + archiveEmail3EditText) + + // Initialize sectionViews[] with the integer id's of the various LinearLayouts + // Add the listeners to the LinearLayouts's header section. + + sectionViews = arrayOf( + findViewById(R.id.language_section), + findViewById(R.id.translator_section), + findViewById(R.id.consultant_section), + findViewById(R.id.trainer_section), + findViewById(R.id.archive_section)) + headerViews = arrayOf( + findViewById(R.id.language_header), + findViewById(R.id.translator_header), + findViewById(R.id.consultant_header), + findViewById(R.id.trainer_header), + findViewById(R.id.archive_header)) + + for (i in sectionViews.indices) { + setAccordionListener(headerViews[i], sectionViews[i]) } } - override fun onPause(){ + fun indexOfOrZero(arr: Array, string: String): Int { + return maxOf(0, arr.indexOf(string)) + } + + override fun onPause() { super.onPause() storeRegistrationInfo() } override fun onPostCreate(savedInstanceState: Bundle?) { super.onPostCreate(savedInstanceState) - setupInputFields() addSubmitButtonSave() addRegistrationSkip() addEthnologueQuestion() } - /** - * Initializes the inputFields to the inputs of this activity. - */ - private fun setupInputFields() { - val view = findViewById(R.id.registration_scroll_view) - inputFields = getInputFields(view) - } - /** * Sets the on click listener for the submit button. */ private fun addSubmitButtonSave() { val submitButton = findViewById