10 Commits

15 changed files with 440 additions and 112 deletions

1
.gitignore vendored
View File

@ -106,3 +106,4 @@ app/release/output-metadata.json
# Other # Other
app/src/main/java/it/danieleverducci/lunatracker/TemporaryHardcodedCredentials.kt app/src/main/java/it/danieleverducci/lunatracker/TemporaryHardcodedCredentials.kt
.kotlin/sessions/*

View File

@ -7,13 +7,18 @@ import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.EditText import android.widget.EditText
import android.widget.NumberPicker import android.widget.NumberPicker
import android.widget.PopupWindow import android.widget.PopupWindow
import android.widget.Spinner
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.children
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.progressindicator.LinearProgressIndicator import com.google.android.material.progressindicator.LinearProgressIndicator
@ -24,6 +29,7 @@ import it.danieleverducci.lunatracker.entities.Logbook
import it.danieleverducci.lunatracker.entities.LunaEvent import it.danieleverducci.lunatracker.entities.LunaEvent
import it.danieleverducci.lunatracker.repository.FileLogbookRepository import it.danieleverducci.lunatracker.repository.FileLogbookRepository
import it.danieleverducci.lunatracker.repository.LocalSettingsRepository import it.danieleverducci.lunatracker.repository.LocalSettingsRepository
import it.danieleverducci.lunatracker.repository.LogbookListObtainedListener
import it.danieleverducci.lunatracker.repository.LogbookLoadedListener import it.danieleverducci.lunatracker.repository.LogbookLoadedListener
import it.danieleverducci.lunatracker.repository.LogbookRepository import it.danieleverducci.lunatracker.repository.LogbookRepository
import it.danieleverducci.lunatracker.repository.LogbookSavedListener import it.danieleverducci.lunatracker.repository.LogbookSavedListener
@ -50,7 +56,7 @@ class MainActivity : AppCompatActivity() {
lateinit var handler: Handler lateinit var handler: Handler
var savingEvent = false var savingEvent = false
val updateListRunnable: Runnable = Runnable { val updateListRunnable: Runnable = Runnable {
loadLogbook() loadLogbook(logbook.name)
handler.postDelayed(updateListRunnable, 1000*60) handler.postDelayed(updateListRunnable, 1000*60)
} }
var logbookRepo: LogbookRepository? = null var logbookRepo: LogbookRepository? = null
@ -77,6 +83,7 @@ class MainActivity : AppCompatActivity() {
recyclerView.adapter = adapter recyclerView.adapter = adapter
// Set listeners // Set listeners
findViewById<View>(R.id.logbooks_add_button).setOnClickListener { showAddLogbookDialog(true) }
findViewById<View>(R.id.button_bottle).setOnClickListener { askBabyBottleContent() } findViewById<View>(R.id.button_bottle).setOnClickListener { askBabyBottleContent() }
findViewById<View>(R.id.button_scale).setOnClickListener { askWeightValue() } findViewById<View>(R.id.button_scale).setOnClickListener { askWeightValue() }
findViewById<View>(R.id.button_nipple_left).setOnClickListener { logEvent( findViewById<View>(R.id.button_nipple_left).setOnClickListener { logEvent(
@ -115,10 +122,10 @@ class MainActivity : AppCompatActivity() {
showSettings() showSettings()
}) })
findViewById<View>(R.id.button_no_connection_retry).setOnClickListener({ findViewById<View>(R.id.button_no_connection_retry).setOnClickListener({
loadLogbook() loadLogbook(logbook.name)
}) })
findViewById<View>(R.id.button_sync).setOnClickListener({ findViewById<View>(R.id.button_sync).setOnClickListener({
loadLogbook() loadLogbook(logbook.name)
}) })
} }
@ -156,7 +163,7 @@ class MainActivity : AppCompatActivity() {
adapter.notifyDataSetChanged() adapter.notifyDataSetChanged()
// Reload data // Reload data
loadLogbook() loadLogbookList()
} }
override fun onStop() { override fun onStop() {
@ -304,7 +311,126 @@ class MainActivity : AppCompatActivity() {
alertDialog.show() alertDialog.show()
} }
fun loadLogbook() { fun showAddLogbookDialog(requestedByUser: Boolean) {
val d = AlertDialog.Builder(this)
d.setTitle(R.string.dialog_add_logbook_title)
val dialogView = layoutInflater.inflate(R.layout.dialog_add_logbook, null)
dialogView.findViewById<TextView>(R.id.dialog_add_logbook_message).text = getString(
if (requestedByUser) R.string.dialog_add_logbook_message else R.string.dialog_add_logbook_message_intro
)
val logbookNameEditText = dialogView.findViewById<EditText>(R.id.dialog_add_logbook_logbookname)
d.setView(dialogView)
d.setPositiveButton(android.R.string.ok) { dialogInterface, i -> addLogbook(logbookNameEditText.text.toString()) }
if (requestedByUser) {
d.setCancelable(true)
d.setNegativeButton(android.R.string.cancel) { dialogInterface, i -> dialogInterface.dismiss() }
} else {
d.setCancelable(false)
}
val alertDialog = d.create()
alertDialog.show()
}
fun loadLogbookList() {
setLoading(true)
logbookRepo?.listLogbooks(this, object: LogbookListObtainedListener {
override fun onLogbookListObtained(logbooksNames: ArrayList<String>) {
runOnUiThread({
if (logbooksNames.isEmpty()) {
// First run, no logbook: create one
showAddLogbookDialog(false)
return@runOnUiThread
}
// Show logbooks dropdown
val spinner = findViewById<Spinner>(R.id.logbooks_spinner)
val sAdapter = ArrayAdapter<String>(this@MainActivity, android.R.layout.simple_spinner_item)
sAdapter.setDropDownViewResource(R.layout.row_logbook_spinner)
for (ln in logbooksNames) {
sAdapter.add(
if (ln.isEmpty()) getString(R.string.default_logbook_name) else ln
)
}
spinner.adapter = sAdapter
spinner.onItemSelectedListener = object: AdapterView.OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
// Changed logbook: empty list
adapter.items.clear()
adapter.notifyDataSetChanged()
// Load logbook
loadLogbook(logbooksNames.get(position))
}
override fun onNothingSelected(parent: AdapterView<*>?) {}
}
})
}
override fun onIOError(error: IOException) {
TODO("Not yet implemented")
}
override fun onWebDAVError(error: SardineException) {
TODO("Not yet implemented")
}
override fun onError(error: Exception) {
TODO("Not yet implemented")
}
})
}
fun addLogbook(logbookName: String) {
val newLogbook = Logbook(logbookName)
setLoading(true)
logbookRepo?.saveLogbook(this, newLogbook, object: LogbookSavedListener{
override fun onLogbookSaved() {
Log.d(TAG, "Logbook $logbookName created")
runOnUiThread({
setLoading(false)
loadLogbookList()
Toast.makeText(this@MainActivity, getString(R.string.logbook_created) + logbookName, Toast.LENGTH_SHORT).show()
})
}
override fun onIOError(error: IOException) {
runOnUiThread({
onRepoError(getString(R.string.settings_network_error) + error.toString())
})
}
override fun onWebDAVError(error: SardineException) {
runOnUiThread({
onRepoError(
if(error.toString().contains("401")) {
getString(R.string.settings_webdav_error_denied)
} else {
getString(R.string.settings_webdav_error_generic) + error.toString()
}
)
})
}
override fun onJSONError(error: JSONException) {
runOnUiThread({
onRepoError(getString(R.string.settings_json_error) + error.toString())
})
}
override fun onError(error: Exception) {
runOnUiThread({
onRepoError(getString(R.string.settings_generic_error) + error.toString())
})
}
})
}
fun loadLogbook(name: String) {
if (savingEvent) if (savingEvent)
return return
@ -314,7 +440,7 @@ class MainActivity : AppCompatActivity() {
// Load data // Load data
setLoading(true) setLoading(true)
logbookRepo?.loadLogbook(this, "", object: LogbookLoadedListener{ logbookRepo?.loadLogbook(this, name, object: LogbookLoadedListener{
override fun onLogbookLoaded(lb: Logbook) { override fun onLogbookLoaded(lb: Logbook) {
runOnUiThread({ runOnUiThread({
setLoading(false) setLoading(false)

View File

@ -8,7 +8,10 @@ import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.progressindicator.LinearProgressIndicator import com.google.android.material.progressindicator.LinearProgressIndicator
import com.thegrizzlylabs.sardineandroid.impl.SardineException import com.thegrizzlylabs.sardineandroid.impl.SardineException
import it.danieleverducci.lunatracker.repository.FileLogbookRepository
import it.danieleverducci.lunatracker.repository.LocalSettingsRepository import it.danieleverducci.lunatracker.repository.LocalSettingsRepository
import it.danieleverducci.lunatracker.repository.LogbookListObtainedListener
import it.danieleverducci.lunatracker.repository.LogbookRepository
import it.danieleverducci.lunatracker.repository.WebDAVLogbookRepository import it.danieleverducci.lunatracker.repository.WebDAVLogbookRepository
import okio.IOException import okio.IOException
import org.json.JSONException import org.json.JSONException
@ -72,14 +75,35 @@ open class SettingsActivity : AppCompatActivity() {
textViewWebDAVPass.text.toString() textViewWebDAVPass.text.toString()
) )
progressIndicator.visibility = View.VISIBLE progressIndicator.visibility = View.VISIBLE
webDAVLogbookRepo.createLogbook(this, "", object: WebDAVLogbookRepository.LogbookCreatedListener{
override fun onLogbookCreated() { webDAVLogbookRepo.listLogbooks(this, object: LogbookListObtainedListener{
override fun onLogbookListObtained(logbooksNames: ArrayList<String>) {
if (logbooksNames.isEmpty()) {
// TODO: Ask the user if he wants to upload the local ones or to create a new one
copyLocalLogbooksToWebdav(webDAVLogbookRepo, object: OnCopyLocalLogbooksToWebdavFinishedListener {
override fun onCopyLocalLogbooksToWebdavFinished(errors: String?) {
runOnUiThread({
progressIndicator.visibility = View.INVISIBLE
if (errors == null) {
saveSettings()
Toast.makeText(this@SettingsActivity, R.string.settings_webdav_creation_ok, Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this@SettingsActivity, errors, Toast.LENGTH_SHORT).show()
}
})
}
})
} else {
runOnUiThread({ runOnUiThread({
progressIndicator.visibility = View.INVISIBLE progressIndicator.visibility = View.INVISIBLE
saveSettings() saveSettings()
Toast.makeText(this@SettingsActivity, R.string.settings_webdav_creation_ok, Toast.LENGTH_SHORT).show() Toast.makeText(this@SettingsActivity, R.string.settings_webdav_creation_ok, Toast.LENGTH_SHORT).show()
}) })
} }
}
override fun onIOError(error: IOException) { override fun onIOError(error: IOException) {
runOnUiThread({ runOnUiThread({
@ -99,6 +123,16 @@ open class SettingsActivity : AppCompatActivity() {
}) })
} }
override fun onError(error: Exception) {
runOnUiThread({
progressIndicator.visibility = View.INVISIBLE
Toast.makeText(this@SettingsActivity, getString(R.string.settings_generic_error) + error.toString(), Toast.LENGTH_SHORT).show()
})
}
})
/*webDAVLogbookRepo.createLogbook(this, LogbookRepository.DEFAULT_LOGBOOK_NAME, object: WebDAVLogbookRepository.LogbookCreatedListener{
override fun onJSONError(error: JSONException) { override fun onJSONError(error: JSONException) {
runOnUiThread({ runOnUiThread({
progressIndicator.visibility = View.INVISIBLE progressIndicator.visibility = View.INVISIBLE
@ -106,14 +140,8 @@ open class SettingsActivity : AppCompatActivity() {
}) })
} }
override fun onError(error: Exception) {
runOnUiThread({
progressIndicator.visibility = View.INVISIBLE
Toast.makeText(this@SettingsActivity, getString(R.string.settings_generic_error) + error.toString(), Toast.LENGTH_SHORT).show()
})
}
}) })*/
} }
fun saveSettings() { fun saveSettings() {
@ -129,4 +157,32 @@ open class SettingsActivity : AppCompatActivity() {
finish() finish()
} }
/**
* Copies the local logbooks to webdav.
* @return success
*/
private fun copyLocalLogbooksToWebdav(webDAVLogbookRepository: WebDAVLogbookRepository, listener: OnCopyLocalLogbooksToWebdavFinishedListener) {
Thread(Runnable {
var errors = StringBuilder()
val fileLogbookRepo = FileLogbookRepository()
val logbooks = fileLogbookRepo.getAllLogbooks(this)
for (logbook in logbooks) {
// Copy only if does not already exist
val error = webDAVLogbookRepository.uploadLogbookIfNotExists(this, logbook.name)
if (error != null) {
if (errors.isNotEmpty())
errors.append("\n")
errors.append(String.format(getString(R.string.settings_webdav_upload_error), logbook.name, error))
}
}
listener.onCopyLocalLogbooksToWebdavFinished(
if (errors.isEmpty()) null else errors.toString()
)
}).start()
}
private interface OnCopyLocalLogbooksToWebdavFinishedListener {
fun onCopyLocalLogbooksToWebdavFinished(errors: String?)
}
} }

View File

@ -31,7 +31,7 @@ class FileLogbookRepository: LogbookRepository {
fun loadLogbook(context: Context, name: String): Logbook { fun loadLogbook(context: Context, name: String): Logbook {
val logbook = Logbook(name) val logbook = Logbook(name)
val fileName = "$FILE_NAME_START{${if (name.isNotEmpty()) "_" else ""}{$name}$FILE_NAME_END" val fileName = getFileName(name)
val file = File(context.getFilesDir(), fileName) val file = File(context.getFilesDir(), fileName)
val json = FileInputStream(file).bufferedReader().use { it.readText() } val json = FileInputStream(file).bufferedReader().use { it.readText() }
val ja = JSONArray(json) val ja = JSONArray(json)
@ -57,8 +57,7 @@ class FileLogbookRepository: LogbookRepository {
} }
fun saveLogbook(context: Context, logbook: Logbook) { fun saveLogbook(context: Context, logbook: Logbook) {
val name = logbook.name val fileName = getFileName(logbook.name)
val fileName = "$FILE_NAME_START${if (name.isNotEmpty()) "_" else ""}${name}$FILE_NAME_END"
val file = File(context.getFilesDir(), fileName) val file = File(context.getFilesDir(), fileName)
val ja = JSONArray() val ja = JSONArray()
for (l in logbook.logs) { for (l in logbook.logs) {
@ -70,7 +69,19 @@ class FileLogbookRepository: LogbookRepository {
override fun listLogbooks( override fun listLogbooks(
context: Context, context: Context,
listener: LogbookListObtainedListener listener: LogbookListObtainedListener
): ArrayList<String> { ) {
listener.onLogbookListObtained(listLogbooks(context))
}
fun getAllLogbooks(context: Context): List<Logbook> {
val logbooks = arrayListOf<Logbook>()
for (logbookName in listLogbooks(context)) {
logbooks.add(loadLogbook(context, logbookName))
}
return logbooks
}
private fun listLogbooks(context: Context): ArrayList<String> {
val logbooksFileNames = context.getFilesDir().list(object: FilenameFilter { val logbooksFileNames = context.getFilesDir().list(object: FilenameFilter {
override fun accept(dir: File?, name: String?): Boolean { override fun accept(dir: File?, name: String?): Boolean {
if (name == null) if (name == null)
@ -81,15 +92,20 @@ class FileLogbookRepository: LogbookRepository {
} }
}) })
if (logbooksFileNames == null || logbooksFileNames.isEmpty()) if (logbooksFileNames == null || logbooksFileNames.isEmpty()) {
return arrayListOf() return arrayListOf()
}
val logbooksNames = arrayListOf<String>() val logbooksNames = arrayListOf<String>()
logbooksFileNames.forEach { it -> logbooksFileNames.forEach { it ->
logbooksNames.add( logbooksNames.add(
it.replace(FILE_NAME_START, "").replace("${FILE_NAME_START}_", "").replace(FILE_NAME_END, "") it.replace("${FILE_NAME_START}_", "").replace(FILE_NAME_START, "").replace(FILE_NAME_END, "")
) )
} }
return logbooksNames return logbooksNames
} }
private fun getFileName(name: String): String {
return "$FILE_NAME_START${if (name.isNotEmpty()) "_" else ""}${name}$FILE_NAME_END"
}
} }

View File

@ -7,9 +7,12 @@ import okio.IOException
import org.json.JSONException import org.json.JSONException
interface LogbookRepository { interface LogbookRepository {
companion object {
val DEFAULT_LOGBOOK_NAME = "" // For compatibility with older app versions
}
fun loadLogbook(context: Context, name: String = "", listener: LogbookLoadedListener) fun loadLogbook(context: Context, name: String = "", listener: LogbookLoadedListener)
fun saveLogbook(context: Context,logbook: Logbook, listener: LogbookSavedListener) fun saveLogbook(context: Context,logbook: Logbook, listener: LogbookSavedListener)
fun listLogbooks(context: Context, listener: LogbookListObtainedListener): ArrayList<String> fun listLogbooks(context: Context, listener: LogbookListObtainedListener)
} }
interface LogbookLoadedListener { interface LogbookLoadedListener {
@ -29,7 +32,7 @@ interface LogbookSavedListener {
} }
interface LogbookListObtainedListener { interface LogbookListObtainedListener {
fun onLogbookListObtained() fun onLogbookListObtained(logbooksNames: ArrayList<String>)
fun onIOError(error: IOException) fun onIOError(error: IOException)
fun onWebDAVError(error: SardineException) fun onWebDAVError(error: SardineException)
fun onError(error: Exception) fun onError(error: Exception)

View File

@ -101,16 +101,22 @@ class WebDAVLogbookRepository(val webDavURL: String, val username: String, val p
override fun listLogbooks( override fun listLogbooks(
context: Context, context: Context,
listener: LogbookListObtainedListener listener: LogbookListObtainedListener
): ArrayList<String> { ) {
Thread(Runnable {
val logbooksNames = arrayListOf<String>() val logbooksNames = arrayListOf<String>()
for (dr: DavResource in sardine.list(webDavURL)){ for (dr: DavResource in sardine.list(webDavURL)){
if(!dr.name.startsWith(FILE_NAME_START))
continue
if(!dr.name.endsWith(FILE_NAME_END))
continue
logbooksNames.add( logbooksNames.add(
dr.name.replace(FileLogbookRepository.Companion.FILE_NAME_START, "") dr.name.replace("${FILE_NAME_START}_", "")
.replace("${FileLogbookRepository.Companion.FILE_NAME_START}_", "") .replace(FILE_NAME_START, "")
.replace(FileLogbookRepository.Companion.FILE_NAME_END, "") .replace(FILE_NAME_END, "")
) )
} }
return logbooksNames listener.onLogbookListObtained(logbooksNames)
}).start()
} }
private fun saveLogbook(context: Context, logbook: Logbook) { private fun saveLogbook(context: Context, logbook: Logbook) {
@ -131,47 +137,46 @@ class WebDAVLogbookRepository(val webDavURL: String, val username: String, val p
/** /**
* Connect to server and check if a logbook already exists. * Connect to server and check if a logbook already exists.
* If it does not exist, try to upload the local one (or create a new one). * If it does not exist, try to upload the local one.
* @return error, or null if no error
*/ */
fun createLogbook(context: Context, name: String, listener: LogbookCreatedListener) { fun uploadLogbookIfNotExists(context: Context, name: String): String? {
Thread(Runnable { val flr = FileLogbookRepository()
try { try {
loadLogbook(name) loadLogbook(name)
listener.onLogbookCreated() Log.d(TAG, "Logbook file $name already exist on the webDav share: will not overwrite it")
return null
} catch (e: SardineException) { } catch (e: SardineException) {
if (e.toString().contains("404")) { if (e.toString().contains("404")) {
// Connection successful, but no existing save. Upload the local one. // Connection successful, but logbook does not exist. Upload the local one.
try { try {
val flr = FileLogbookRepository()
val logbook = flr.loadLogbook(context, name) val logbook = flr.loadLogbook(context, name)
saveLogbook(context, logbook) saveLogbook(context, logbook)
Log.d(TAG, "Local logbook file found, uploaded") Log.d(TAG, "Local logbook file $name found, uploaded")
listener.onLogbookCreated() return null
} catch (e: FileNotFoundException) { } catch (e: FileNotFoundException) {
Log.d(TAG, "No local logbook file found, uploading empty file") Log.e(TAG, "No local logbook file found, this should not happen!")
saveLogbook(context, Logbook(name)) return "No local logbook file found, app is in inconsistent state, please delete and reinstall it"
listener.onLogbookCreated()
} catch (e: SardineException) { } catch (e: SardineException) {
Log.e(TAG, "Unable to upload logbook: $e") Log.e(TAG, "Unable to upload logbook: $e")
listener.onWebDAVError(e) return e.toString()
} }
} else { } else {
Log.e(TAG, e.toString()) Log.e(TAG, e.toString())
listener.onWebDAVError(e) return e.toString()
} }
} catch (e: IOException) { } catch (e: IOException) {
Log.e(TAG, e.toString()) Log.e(TAG, e.toString())
listener.onIOError(e) return e.toString()
} catch (e: SocketTimeoutException) { } catch (e: SocketTimeoutException) {
Log.e(TAG, e.toString()) Log.e(TAG, e.toString())
listener.onIOError(e) return e.toString()
} catch (e: JSONException) { } catch (e: JSONException) {
Log.e(TAG, e.toString()) Log.e(TAG, e.toString())
listener.onJSONError(e) return e.toString()
} catch (e: Exception) { } catch (e: Exception) {
listener.onError(e) return e.toString()
} }
}).start()
} }
private fun getUrl(name: String): String { private fun getUrl(name: String): String {
@ -179,13 +184,4 @@ class WebDAVLogbookRepository(val webDavURL: String, val username: String, val p
Log.d(TAG, fileName) Log.d(TAG, fileName)
return "$webDavURL/$fileName" return "$webDavURL/$fileName"
} }
interface LogbookCreatedListener {
fun onLogbookCreated()
fun onIOError(error: okio.IOException)
fun onWebDAVError(error: SardineException)
fun onJSONError(error: JSONException)
fun onError(error: Exception)
}
} }

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="@drawable/dropdown_list_item_background_pressed"/>
<item android:drawable="@drawable/dropdown_list_item_background_released"/>
</selector>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<stroke
android:width="2dp"
android:color="@color/grey" />
<solid
android:color="@color/grey" />
<corners android:radius="15dp" />
<padding
android:bottom="5dp"
android:left="5dp"
android:right="5dp"
android:top="5dp" />
</shape>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<stroke
android:width="2dp"
android:color="@color/grey" />
<solid
android:color="@color/cardview_dark_background"/>
<corners android:radius="15dp" />
<padding
android:bottom="5dp"
android:left="5dp"
android:right="5dp"
android:top="5dp" />
</shape>

View File

@ -7,16 +7,77 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
android:paddingTop="30dp" android:paddingTop="10dp"
android:paddingLeft="15dp" android:paddingLeft="15dp"
android:paddingRight="15dp"> android:paddingRight="15dp">
<TextView <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="@+id/button_settings"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:layout_gravity="end"
android:src="@drawable/ic_settings"
app:tint="@color/grey"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/title" android:text="@string/title"
android:textSize="30dp" android:textSize="26dp"
android:gravity="center_horizontal"/> android:gravity="center"/>
<ImageView
android:id="@+id/button_sync"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:layout_gravity="start"
android:src="@drawable/ic_sync"
app:tint="@color/grey"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="38dp"
android:layout_margin="10dp"
android:orientation="horizontal"
android:gravity="center_vertical">
<FrameLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/button_background">
<Spinner
android:id="@+id/logbooks_spinner"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
<TextView
android:id="@+id/logbooks_add_button"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginLeft="10dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:textStyle="bold"
android:textColor="@color/accent"
android:textSize="20dp"
android:text="+"
android:background="@drawable/button_background"/>
</LinearLayout>
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
@ -164,24 +225,6 @@
</LinearLayout> </LinearLayout>
<ImageView
android:id="@+id/button_settings"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:layout_gravity="end"
android:src="@drawable/ic_settings"
app:tint="@color/grey"/>
<ImageView
android:id="@+id/button_sync"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:layout_gravity="start"
android:src="@drawable/ic_sync"
app:tint="@color/grey"/>
<LinearLayout <LinearLayout
android:id="@+id/no_connection_screen" android:id="@+id/no_connection_screen"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="20dp">
<TextView
android:id="@+id/dialog_add_logbook_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/dialog_add_logbook_message"/>
<EditText
android:id="@+id/dialog_add_logbook_logbookname"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:lines="1"
android:inputType="text"
android:hint="@string/dialog_add_logbook_logbookname"
android:background="@drawable/textview_background"/>
</LinearLayout>

View File

@ -15,7 +15,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:padding="20dp" android:padding="20dp"
android:background="@drawable/button_background" android:background="@drawable/dropdown_list_item_background"
style="@style/OverflowMenuText" style="@style/OverflowMenuText"
android:text="@string/overflow_event_medicine"/> android:text="@string/overflow_event_medicine"/>
@ -25,7 +25,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginTop="10dp" android:layout_marginTop="10dp"
android:padding="20dp" android:padding="20dp"
android:background="@drawable/button_background" android:background="@drawable/dropdown_list_item_background"
style="@style/OverflowMenuText" style="@style/OverflowMenuText"
android:text="@string/overflow_event_enema"/> android:text="@string/overflow_event_enema"/>
@ -35,7 +35,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginTop="10dp" android:layout_marginTop="10dp"
android:padding="20dp" android:padding="20dp"
android:background="@drawable/button_background" android:background="@drawable/dropdown_list_item_background"
style="@style/OverflowMenuText" style="@style/OverflowMenuText"
android:text="@string/overflow_event_note"/> android:text="@string/overflow_event_note"/>
@ -45,7 +45,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginTop="10dp" android:layout_marginTop="10dp"
android:padding="20dp" android:padding="20dp"
android:background="@drawable/button_background" android:background="@drawable/dropdown_list_item_background"
style="@style/OverflowMenuText" style="@style/OverflowMenuText"
android:text="@string/overflow_event_temperature"/> android:text="@string/overflow_event_temperature"/>
@ -55,7 +55,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginTop="10dp" android:layout_marginTop="10dp"
android:padding="20dp" android:padding="20dp"
android:background="@drawable/button_background" android:background="@drawable/dropdown_list_item_background"
style="@style/OverflowMenuText" style="@style/OverflowMenuText"
android:text="@string/overflow_event_colic"/> android:text="@string/overflow_event_colic"/>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:paddingTop="20dp"
android:paddingBottom="20dp"
android:singleLine="true"
android:ellipsize="end"/>

View File

@ -66,6 +66,7 @@
<string name="settings_webdav_creation_ok">Connessione al server WebDAV avvenuta con successo</string> <string name="settings_webdav_creation_ok">Connessione al server WebDAV avvenuta con successo</string>
<string name="settings_json_error">Sul server esiste un salvataggio, ma è corrotto o illeggibile. Cancellare il file </string> <string name="settings_json_error">Sul server esiste un salvataggio, ma è corrotto o illeggibile. Cancellare il file </string>
<string name="settings_generic_error">Si è verificato un errore: </string> <string name="settings_generic_error">Si è verificato un errore: </string>
<string name="settings_webdav_upload_error">Errore durante l\'upload del logbook locale %1$s su webdav: %2$s</string>
<string name="trim_logbook_dialog_title">Il tuo diario è bello grande!</string> <string name="trim_logbook_dialog_title">Il tuo diario è bello grande!</string>
<string name="trim_logbook_dialog_message_local">Il file del tuo diario sta crescendo molto. Ti suggeriamo di cancellare gli eventi più vecchi per evitare problemi di memoria.</string> <string name="trim_logbook_dialog_message_local">Il file del tuo diario sta crescendo molto. Ti suggeriamo di cancellare gli eventi più vecchi per evitare problemi di memoria.</string>
@ -80,4 +81,11 @@
<string name="dialog_event_detail_title">Dettaglio evento</string> <string name="dialog_event_detail_title">Dettaglio evento</string>
<string name="dialog_add_logbook_title">Aggiungi diario</string>
<string name="dialog_add_logbook_logbookname">👶 Nome del diario</string>
<string name="dialog_add_logbook_message">Scrivi un nome per identificare questo diario. Comparirà in cima allo schermo, e se usi WebDAV sarà incluso anche nel nome del file di salvataggio.\nSe vuoi un\'icona, inserisci una emoji!</string>
<string name="default_logbook_name">👶 Il mio primo diario</string>
<string name="logbook_created">Creato nuovo diario: </string>
</resources> </resources>

View File

@ -80,6 +80,7 @@
<string name="settings_webdav_creation_ok">Successfully connected with WebDAV server</string> <string name="settings_webdav_creation_ok">Successfully connected with WebDAV server</string>
<string name="settings_json_error">There\'s a save file on the server, but is corrupted or unreadable. Please delete it </string> <string name="settings_json_error">There\'s a save file on the server, but is corrupted or unreadable. Please delete it </string>
<string name="settings_generic_error">Error: </string> <string name="settings_generic_error">Error: </string>
<string name="settings_webdav_upload_error">Error while uploading local logbook %1$s to webdav: %2$s</string>
<string name="trim_logbook_dialog_title">Your logbook is pretty big!</string> <string name="trim_logbook_dialog_title">Your logbook is pretty big!</string>
<string name="trim_logbook_dialog_message_local">Your logbook file is growing a lot. We suggest trimming the oldest events to avoid crashes.</string> <string name="trim_logbook_dialog_message_local">Your logbook file is growing a lot. We suggest trimming the oldest events to avoid crashes.</string>
@ -103,4 +104,12 @@
<string name="dialog_event_detail_title">Event detail</string> <string name="dialog_event_detail_title">Event detail</string>
<string name="dialog_add_logbook_title">Add logbook</string>
<string name="dialog_add_logbook_logbookname">👶 Logbook name</string>
<string name="dialog_add_logbook_message">Write a name to identify this logbook. This name will appear on top of the screen and, if you use WebDAV, will be in the save file name as well.</string>
<string name="dialog_add_logbook_message_intro">Welcome! To use this app you need to create at least one logbook. You would probably want to call it with your child\'s name.</string>
<string name="default_logbook_name">👶 My first logbook</string>
<string name="logbook_created">New logbook created: </string>
</resources> </resources>