mirror of
https://github.com/bitfireAT/davx5-ose.git
synced 2026-01-27 08:08:29 -05:00
Compare commits
8 Commits
reuse-http
...
v2.4-beta1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17602f89d6 | ||
|
|
039593b9e6 | ||
|
|
baaeb343dd | ||
|
|
2164088e1d | ||
|
|
94f8cb72c9 | ||
|
|
8e51c3ac9a | ||
|
|
d9af394610 | ||
|
|
7ff0e55546 |
@@ -19,7 +19,7 @@ android {
|
||||
defaultConfig {
|
||||
applicationId "at.bitfire.davdroid"
|
||||
|
||||
versionCode 272
|
||||
versionCode 274
|
||||
buildConfigField "long", "buildTime", System.currentTimeMillis() + "L"
|
||||
buildConfigField "boolean", "customCerts", "true"
|
||||
|
||||
@@ -42,7 +42,7 @@ android {
|
||||
flavorDimensions "distribution"
|
||||
productFlavors {
|
||||
standard {
|
||||
versionName "2.3-ose"
|
||||
versionName "2.4-beta1-ose"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +89,8 @@ dependencies {
|
||||
implementation 'androidx.lifecycle:lifecycle-livedata:2.0.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.0.0'
|
||||
implementation 'androidx.preference:preference:1.0.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
implementation 'com.google.android:flexbox:1.1.0'
|
||||
implementation 'com.google.android.material:material:1.0.0'
|
||||
|
||||
implementation(':dav4jvm') {
|
||||
|
||||
@@ -14,7 +14,9 @@ import android.os.Parcelable
|
||||
import at.bitfire.dav4jvm.Response
|
||||
import at.bitfire.dav4jvm.UrlUtils
|
||||
import at.bitfire.dav4jvm.property.*
|
||||
import at.bitfire.davdroid.DavUtils
|
||||
import at.bitfire.davdroid.model.ServiceDB.Collections
|
||||
import at.bitfire.ical4android.MiscUtils
|
||||
import okhttp3.HttpUrl
|
||||
|
||||
/**
|
||||
@@ -44,6 +46,7 @@ data class CollectionInfo(
|
||||
var timeZone: String? = null,
|
||||
var supportsVEVENT: Boolean = false,
|
||||
var supportsVTODO: Boolean = false,
|
||||
var supportsVJOURNAL: Boolean = false,
|
||||
var selected: Boolean = false,
|
||||
|
||||
// subscriptions
|
||||
@@ -152,6 +155,8 @@ data class CollectionInfo(
|
||||
return values
|
||||
}
|
||||
|
||||
fun title() = displayName ?: DavUtils.lastSegmentOfUrl(url)
|
||||
|
||||
|
||||
private fun getAsBooleanOrNull(values: ContentValues, field: String): Boolean? {
|
||||
val i = values.getAsInteger(field)
|
||||
@@ -191,6 +196,7 @@ data class CollectionInfo(
|
||||
dest.writeString(timeZone)
|
||||
dest.writeByte(if (supportsVEVENT) 1 else 0)
|
||||
dest.writeByte(if (supportsVTODO) 1 else 0)
|
||||
dest.writeByte(if (supportsVJOURNAL) 1 else 0)
|
||||
dest.writeByte(if (selected) 1 else 0)
|
||||
|
||||
dest.writeString(source)
|
||||
@@ -237,6 +243,7 @@ data class CollectionInfo(
|
||||
parcel.readByte() != 0.toByte(),
|
||||
parcel.readByte() != 0.toByte(),
|
||||
parcel.readByte() != 0.toByte(),
|
||||
parcel.readByte() != 0.toByte(),
|
||||
|
||||
parcel.readString(),
|
||||
|
||||
|
||||
@@ -8,28 +8,31 @@
|
||||
|
||||
package at.bitfire.davdroid.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.app.Application
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.Spanned
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.*
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.FragmentPagerAdapter
|
||||
import androidx.loader.app.LoaderManager
|
||||
import androidx.loader.content.AsyncTaskLoader
|
||||
import androidx.loader.content.Loader
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import at.bitfire.davdroid.App
|
||||
import at.bitfire.davdroid.BuildConfig
|
||||
import at.bitfire.davdroid.R
|
||||
import at.bitfire.davdroid.log.Logger
|
||||
import com.mikepenz.aboutlibraries.LibsBuilder
|
||||
import kotlinx.android.synthetic.main.about_davdroid.*
|
||||
import kotlinx.android.synthetic.main.about.*
|
||||
import kotlinx.android.synthetic.main.activity_about.*
|
||||
import org.apache.commons.io.IOUtils
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
class AboutActivity: AppCompatActivity() {
|
||||
|
||||
@@ -44,7 +47,6 @@ class AboutActivity: AppCompatActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContentView(R.layout.activity_about)
|
||||
|
||||
setSupportActionBar(toolbar)
|
||||
@@ -72,66 +74,63 @@ class AboutActivity: AppCompatActivity() {
|
||||
|
||||
override fun getPageTitle(position: Int): String =
|
||||
when (position) {
|
||||
1 -> getString(R.string.about_libraries)
|
||||
else -> getString(R.string.app_name)
|
||||
0 -> getString(R.string.app_name)
|
||||
else -> getString(R.string.about_libraries)
|
||||
}
|
||||
|
||||
override fun getItem(position: Int) =
|
||||
when (position) {
|
||||
1 -> LibsBuilder()
|
||||
0 -> AppFragment()
|
||||
else -> LibsBuilder()
|
||||
.withAutoDetect(false)
|
||||
.withFields(R.string::class.java.fields)
|
||||
.withLicenseShown(true)
|
||||
.supportFragment()
|
||||
else -> DavdroidFragment()
|
||||
}!!
|
||||
}
|
||||
|
||||
|
||||
class DavdroidFragment: Fragment(), LoaderManager.LoaderCallbacks<Spanned> {
|
||||
class AppFragment: Fragment() {
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?) =
|
||||
inflater.inflate(R.layout.about_davdroid, container, false)!!
|
||||
inflater.inflate(R.layout.about, container, false)!!
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
app_name.text = getString(R.string.app_name)
|
||||
app_version.text = getString(R.string.about_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)
|
||||
build_time.text = getString(R.string.about_build_date, SimpleDateFormat.getDateInstance().format(BuildConfig.buildTime))
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
|
||||
icon.setImageDrawable(resources.getDrawableForDensity(R.mipmap.ic_launcher, DisplayMetrics.DENSITY_XXXHIGH))
|
||||
|
||||
pixels.text = HtmlCompat.fromHtml(pixelsHtml, HtmlCompat.FROM_HTML_MODE_LEGACY)
|
||||
|
||||
if (true /* open-source version */) {
|
||||
warranty.setText(R.string.about_license_info_no_warranty)
|
||||
LoaderManager.getInstance(this).initLoader(0, null, this)
|
||||
|
||||
val model = ViewModelProviders.of(this).get(LicenseModel::class.java)
|
||||
model.htmlText.observe(this, Observer { spanned ->
|
||||
license_text.text = spanned
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateLoader(id: Int, args: Bundle?) =
|
||||
HtmlAssetLoader(requireActivity(), "gplv3.html")
|
||||
|
||||
override fun onLoadFinished(loader: Loader<Spanned>, license: Spanned?) {
|
||||
Logger.log.info("LOAD FINISHED")
|
||||
license_text.text = license
|
||||
}
|
||||
|
||||
override fun onLoaderReset(loader: Loader<Spanned>) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class HtmlAssetLoader(
|
||||
context: Context,
|
||||
val fileName: String
|
||||
): AsyncTaskLoader<Spanned>(context) {
|
||||
class LicenseModel(
|
||||
application: Application
|
||||
): AndroidViewModel(application) {
|
||||
|
||||
override fun onStartLoading() {
|
||||
forceLoad()
|
||||
}
|
||||
val htmlText = MutableLiveData<Spanned>()
|
||||
|
||||
override fun loadInBackground(): Spanned =
|
||||
context.resources.assets.open(fileName).use {
|
||||
HtmlCompat.fromHtml(IOUtils.toString(it, Charsets.UTF_8), HtmlCompat.FROM_HTML_MODE_LEGACY)
|
||||
init {
|
||||
thread {
|
||||
getApplication<Application>().resources.assets.open("gplv3.html").use {
|
||||
val spanned = HtmlCompat.fromHtml(IOUtils.toString(it, Charsets.UTF_8), HtmlCompat.FROM_HTML_MODE_LEGACY)
|
||||
htmlText.postValue(spanned)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -216,7 +216,7 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop
|
||||
SetReadOnlyTask(WeakReference(this), info.id!!, nowChecked).execute()
|
||||
}
|
||||
R.id.delete_collection ->
|
||||
DeleteCollectionFragment.ConfirmDeleteCollectionFragment.newInstance(account, info).show(supportFragmentManager, null)
|
||||
DeleteCollectionFragment.newInstance(account, info).show(supportFragmentManager, null)
|
||||
R.id.properties ->
|
||||
CollectionInfoFragment.newInstance(info).show(supportFragmentManager, null)
|
||||
}
|
||||
|
||||
@@ -9,19 +9,30 @@
|
||||
package at.bitfire.davdroid.ui
|
||||
|
||||
import android.accounts.Account
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.Filter
|
||||
import android.widget.SpinnerAdapter
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.NavUtils
|
||||
import androidx.loader.app.LoaderManager
|
||||
import androidx.loader.content.AsyncTaskLoader
|
||||
import androidx.loader.content.Loader
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import at.bitfire.davdroid.Constants
|
||||
import at.bitfire.davdroid.R
|
||||
import at.bitfire.davdroid.databinding.ActivityCreateCalendarBinding
|
||||
import at.bitfire.davdroid.model.CollectionInfo
|
||||
import at.bitfire.davdroid.model.ServiceDB
|
||||
import at.bitfire.ical4android.DateUtils
|
||||
@@ -32,35 +43,46 @@ import net.fortuna.ical4j.model.Calendar
|
||||
import okhttp3.HttpUrl
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import java.util.*
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
class CreateCalendarActivity: AppCompatActivity(), LoaderManager.LoaderCallbacks<CreateCalendarActivity.AccountInfo>, ColorPickerDialogListener {
|
||||
class CreateCalendarActivity: AppCompatActivity(), ColorPickerDialogListener {
|
||||
|
||||
companion object {
|
||||
const val EXTRA_ACCOUNT = "account"
|
||||
}
|
||||
|
||||
private lateinit var account: Account
|
||||
private lateinit var model: Model
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
account = intent.extras.getParcelable(EXTRA_ACCOUNT)!!
|
||||
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
setContentView(R.layout.activity_create_calendar)
|
||||
color.setOnClickListener { _ ->
|
||||
model = ViewModelProviders.of(this).get(Model::class.java)
|
||||
(intent?.extras?.getParcelable(EXTRA_ACCOUNT) as? Account)?.let {
|
||||
model.initialize(it)
|
||||
}
|
||||
model.homeSets.observe(this, Observer {
|
||||
if (it.isEmpty)
|
||||
// no known homesets, we don't know where to create the calendar
|
||||
finish()
|
||||
})
|
||||
|
||||
val binding = DataBindingUtil.setContentView<ActivityCreateCalendarBinding>(this, R.layout.activity_create_calendar)
|
||||
binding.lifecycleOwner = this
|
||||
binding.model = model
|
||||
|
||||
binding.color.setOnClickListener { _ ->
|
||||
ColorPickerDialog.newBuilder()
|
||||
.setShowAlphaSlider(false)
|
||||
.setColor((color.background as ColorDrawable).color)
|
||||
.show(this)
|
||||
}
|
||||
|
||||
LoaderManager.getInstance(this).initLoader(0, null, this)
|
||||
binding.timezone.setAdapter(model.timezones)
|
||||
}
|
||||
|
||||
override fun onColorSelected(dialogId: Int, rgb: Int) {
|
||||
color.setBackgroundColor(rgb)
|
||||
model.color.value = rgb
|
||||
}
|
||||
|
||||
override fun onDialogDismissed(dialogId: Int) {
|
||||
@@ -75,105 +97,192 @@ class CreateCalendarActivity: AppCompatActivity(), LoaderManager.LoaderCallbacks
|
||||
override fun onOptionsItemSelected(item: MenuItem) =
|
||||
if (item.itemId == android.R.id.home) {
|
||||
val intent = Intent(this, AccountActivity::class.java)
|
||||
intent.putExtra(AccountActivity.EXTRA_ACCOUNT, account)
|
||||
intent.putExtra(AccountActivity.EXTRA_ACCOUNT, model.account)
|
||||
NavUtils.navigateUpTo(this, intent)
|
||||
true
|
||||
} else
|
||||
false
|
||||
|
||||
fun onCreateCollection(item: MenuItem) {
|
||||
val homeSet = home_sets.selectedItem as String
|
||||
|
||||
var ok = true
|
||||
HttpUrl.parse(homeSet)?.let {
|
||||
val info = CollectionInfo(it.resolve(UUID.randomUUID().toString() + "/")!!)
|
||||
info.displayName = display_name.text.toString()
|
||||
if (info.displayName.isNullOrBlank()) {
|
||||
display_name.error = getString(R.string.create_collection_display_name_required)
|
||||
|
||||
val parent = model.homeSets.value?.getItem(model.idxHomeSet.value!!) as String
|
||||
HttpUrl.parse(parent)?.let { parentUrl ->
|
||||
val info = CollectionInfo(parentUrl.resolve(UUID.randomUUID().toString() + "/")!!)
|
||||
|
||||
val displayName = model.displayName.value
|
||||
if (displayName.isNullOrBlank()) {
|
||||
model.displayNameError.value = getString(R.string.create_collection_display_name_required)
|
||||
ok = false
|
||||
} else {
|
||||
info.displayName = displayName
|
||||
model.displayNameError.value = null
|
||||
}
|
||||
|
||||
info.description = StringUtils.trimToNull(description.text.toString())
|
||||
info.color = (color.background as ColorDrawable).color
|
||||
info.description = StringUtils.trimToNull(model.description.value)
|
||||
info.color = model.color.value
|
||||
|
||||
DateUtils.tzRegistry.getTimeZone(time_zone.selectedItem as String)?.let { tz ->
|
||||
val cal = Calendar()
|
||||
cal.components += tz.vTimeZone
|
||||
info.timeZone = cal.toString()
|
||||
val tzId = model.timezone.value
|
||||
if (tzId.isNullOrBlank()) {
|
||||
model.timezoneError.value = getString(R.string.create_calendar_time_zone_required)
|
||||
ok = false
|
||||
} else {
|
||||
DateUtils.tzRegistry.getTimeZone(tzId)?.let { tz ->
|
||||
val cal = Calendar()
|
||||
cal.components += tz.vTimeZone
|
||||
info.timeZone = cal.toString()
|
||||
}
|
||||
model.timezoneError.value = null
|
||||
}
|
||||
|
||||
when (type.checkedRadioButtonId) {
|
||||
R.id.type_events ->
|
||||
info.supportsVEVENT = true
|
||||
R.id.type_tasks ->
|
||||
info.supportsVTODO = true
|
||||
R.id.type_events_and_tasks -> {
|
||||
info.supportsVEVENT = true
|
||||
info.supportsVTODO = true
|
||||
val supportsVEVENT = model.supportVEVENT.value ?: false
|
||||
val supportsVTODO = model.supportVTODO.value ?: false
|
||||
val supportsVJOURNAL = model.supportVJOURNAL.value ?: false
|
||||
if (!supportsVEVENT && !supportsVTODO && !supportsVJOURNAL) {
|
||||
ok = false
|
||||
model.typeError.value = ""
|
||||
} else
|
||||
model.typeError.value = null
|
||||
|
||||
info.type = CollectionInfo.Type.CALENDAR
|
||||
info.supportsVEVENT = supportsVEVENT
|
||||
info.supportsVTODO = supportsVTODO
|
||||
info.supportsVJOURNAL = supportsVJOURNAL
|
||||
|
||||
if (ok)
|
||||
CreateCollectionFragment.newInstance(model.account!!, info).show(supportFragmentManager, null)
|
||||
}
|
||||
}
|
||||
|
||||
class HomesetAdapter(
|
||||
context: Context
|
||||
): ArrayAdapter<String>(context, android.R.layout.simple_list_item_1, android.R.id.text1) {
|
||||
|
||||
init {
|
||||
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
}
|
||||
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||
val data = getItem(position)!!
|
||||
val v = super.getView(position, convertView, parent)
|
||||
v.findViewById<TextView>(android.R.id.text1).apply {
|
||||
setSingleLine()
|
||||
ellipsize = TextUtils.TruncateAt.START
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||
val data = getItem(position)!!
|
||||
val v = super.getDropDownView(position, convertView, parent)
|
||||
v.findViewById<TextView>(android.R.id.text1).apply {
|
||||
ellipsize = TextUtils.TruncateAt.START
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TimeZoneAdapter(
|
||||
context: Context
|
||||
): ArrayAdapter<String>(context, android.R.layout.simple_list_item_1, android.R.id.text1) {
|
||||
|
||||
val tz = TimeZone.getAvailableIDs()
|
||||
|
||||
override fun getFilter(): Filter {
|
||||
return object: Filter() {
|
||||
override fun performFiltering(constraint: CharSequence?): FilterResults {
|
||||
val filtered = constraint?.let {
|
||||
tz.filter { it.contains(constraint, true) }
|
||||
} ?: listOf()
|
||||
val results = FilterResults()
|
||||
results.values = filtered
|
||||
results.count = filtered.size
|
||||
return results
|
||||
}
|
||||
override fun publishResults(constraint: CharSequence?, results: FilterResults) {
|
||||
clear()
|
||||
@Suppress("UNCHECKED_CAST") addAll(results.values as List<String>)
|
||||
if (results.count >= 0)
|
||||
notifyDataSetChanged()
|
||||
else
|
||||
notifyDataSetInvalidated()
|
||||
}
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
info.type = CollectionInfo.Type.CALENDAR
|
||||
CreateCollectionFragment.newInstance(account, info).show(supportFragmentManager, null)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
override fun onCreateLoader(id: Int, args: Bundle?) = AccountInfoLoader(this, account)
|
||||
class Model(
|
||||
application: Application
|
||||
): AndroidViewModel(application) {
|
||||
|
||||
override fun onLoadFinished(loader: Loader<AccountInfo>, info: AccountInfo?) {
|
||||
val timeZones = TimeZone.getAvailableIDs()
|
||||
time_zone.adapter = ArrayAdapter<String>(this, android.R.layout.simple_spinner_dropdown_item, timeZones)
|
||||
class TimeZoneInfo(
|
||||
val id: String,
|
||||
val displayName: String
|
||||
) {
|
||||
override fun toString() = id
|
||||
}
|
||||
|
||||
// select system time zone
|
||||
val defaultTimeZone = TimeZone.getDefault().id
|
||||
for (i in 0 until timeZones.size)
|
||||
if (timeZones[i] == defaultTimeZone) {
|
||||
time_zone.setSelection(i)
|
||||
break
|
||||
var account: Account? = null
|
||||
|
||||
val displayName = MutableLiveData<String>()
|
||||
val displayNameError = MutableLiveData<String>()
|
||||
|
||||
val description = MutableLiveData<String>()
|
||||
val color = MutableLiveData<Int>()
|
||||
|
||||
val homeSets = MutableLiveData<SpinnerAdapter>()
|
||||
val idxHomeSet = MutableLiveData<Int>()
|
||||
|
||||
val timezones = TimeZoneAdapter(application)
|
||||
val timezone = MutableLiveData<String>()
|
||||
val timezoneError = MutableLiveData<String>()
|
||||
|
||||
val typeError = MutableLiveData<String>()
|
||||
val supportVEVENT = MutableLiveData<Boolean>()
|
||||
val supportVTODO = MutableLiveData<Boolean>()
|
||||
val supportVJOURNAL = MutableLiveData<Boolean>()
|
||||
|
||||
fun initialize(account: Account) {
|
||||
synchronized(this) {
|
||||
if (this.account != null)
|
||||
return
|
||||
this.account = account
|
||||
}
|
||||
|
||||
info?.let {
|
||||
home_sets.adapter = ArrayAdapter<String>(this, android.R.layout.simple_spinner_dropdown_item, info.homeSets)
|
||||
}
|
||||
}
|
||||
color.value = Constants.DAVDROID_GREEN_RGBA
|
||||
|
||||
override fun onLoaderReset(loader: Loader<AccountInfo>) {}
|
||||
timezone.value = TimeZone.getDefault().id
|
||||
|
||||
supportVEVENT.value = true
|
||||
supportVTODO.value = true
|
||||
supportVJOURNAL.value = true
|
||||
|
||||
class AccountInfo {
|
||||
val homeSets = LinkedList<String>()
|
||||
}
|
||||
|
||||
class AccountInfoLoader(
|
||||
context: Context,
|
||||
val account: Account
|
||||
): AsyncTaskLoader<AccountInfo>(context) {
|
||||
|
||||
override fun onStartLoading() = forceLoad()
|
||||
|
||||
override fun loadInBackground(): AccountInfo? {
|
||||
val info = AccountInfo()
|
||||
ServiceDB.OpenHelper(context).use { dbHelper ->
|
||||
val db = dbHelper.readableDatabase
|
||||
db.query(ServiceDB.Services._TABLE, arrayOf(ServiceDB.Services.ID),
|
||||
"${ServiceDB.Services.ACCOUNT_NAME}=? AND ${ServiceDB.Services.SERVICE}=?",
|
||||
arrayOf(account.name, ServiceDB.Services.SERVICE_CALDAV), null, null, null).use { cursor ->
|
||||
if (!cursor.moveToNext())
|
||||
return null
|
||||
val strServiceID = cursor.getString(0)
|
||||
|
||||
db.query(ServiceDB.HomeSets._TABLE, arrayOf(ServiceDB.HomeSets.URL),
|
||||
"${ServiceDB.HomeSets.SERVICE_ID}=?", arrayOf(strServiceID), null, null, null).use { c ->
|
||||
while (c.moveToNext())
|
||||
info.homeSets += c.getString(0)
|
||||
thread {
|
||||
// load account info
|
||||
ServiceDB.OpenHelper(getApplication()).use { dbHelper ->
|
||||
val adapter = HomesetAdapter(getApplication())
|
||||
val db = dbHelper.readableDatabase
|
||||
db.query(ServiceDB.Services._TABLE, arrayOf(ServiceDB.Services.ID),
|
||||
"${ServiceDB.Services.ACCOUNT_NAME}=? AND ${ServiceDB.Services.SERVICE}=?",
|
||||
arrayOf(account.name, ServiceDB.Services.SERVICE_CALDAV), null, null, null).use { cursor ->
|
||||
if (cursor.moveToNext()) {
|
||||
val strServiceID = cursor.getString(0)
|
||||
db.query(ServiceDB.HomeSets._TABLE, arrayOf(ServiceDB.HomeSets.URL),
|
||||
"${ServiceDB.HomeSets.SERVICE_ID}=?", arrayOf(strServiceID), null, null, null).use { c ->
|
||||
while (c.moveToNext())
|
||||
adapter.add(c.getString(0))
|
||||
}
|
||||
}
|
||||
}
|
||||
homeSets.postValue(adapter)
|
||||
idxHomeSet.postValue(0)
|
||||
}
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -171,6 +171,11 @@ class CreateCollectionFragment: DialogFragment(), LoaderManager.LoaderCallbacks<
|
||||
attribute(null, "name", "VTODO")
|
||||
endTag(XmlUtils.NS_CALDAV, "comp")
|
||||
}
|
||||
if (info.supportsVJOURNAL) {
|
||||
startTag(XmlUtils.NS_CALDAV, "comp")
|
||||
attribute(null, "name", "VJOURNAL")
|
||||
endTag(XmlUtils.NS_CALDAV, "comp")
|
||||
}
|
||||
endTag(XmlUtils.NS_CALDAV, "supported-calendar-component-set")
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import at.bitfire.davdroid.R
|
||||
class DefaultAccountsDrawerHandler: IAccountsDrawerHandler {
|
||||
|
||||
companion object {
|
||||
private const val BETA_FEEDBACK_URI = "mailto:support@davx5.com?subject=${BuildConfig.APPLICATION_ID}/${BuildConfig.VERSION_NAME} feedback (${BuildConfig.VERSION_CODE})"
|
||||
private const val BETA_FEEDBACK_URI = "mailto:play@bitfire.at?subject=${BuildConfig.APPLICATION_ID}/${BuildConfig.VERSION_NAME} feedback (${BuildConfig.VERSION_CODE})"
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -9,137 +9,120 @@
|
||||
package at.bitfire.davdroid.ui
|
||||
|
||||
import android.accounts.Account
|
||||
import android.app.Dialog
|
||||
import android.app.ProgressDialog
|
||||
import android.content.Context
|
||||
import android.app.Application
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.loader.app.LoaderManager
|
||||
import androidx.loader.content.AsyncTaskLoader
|
||||
import androidx.loader.content.Loader
|
||||
import androidx.lifecycle.*
|
||||
import at.bitfire.dav4jvm.DavResource
|
||||
import at.bitfire.davdroid.HttpClient
|
||||
import at.bitfire.davdroid.R
|
||||
import at.bitfire.davdroid.databinding.DeleteCollectionBinding
|
||||
import at.bitfire.davdroid.model.CollectionInfo
|
||||
import at.bitfire.davdroid.model.ServiceDB
|
||||
import at.bitfire.davdroid.settings.AccountSettings
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
class DeleteCollectionFragment: DialogFragment(), LoaderManager.LoaderCallbacks<Exception> {
|
||||
class DeleteCollectionFragment: DialogFragment() {
|
||||
|
||||
companion object {
|
||||
const val ARG_ACCOUNT = "account"
|
||||
const val ARG_COLLECTION_INFO = "collectionInfo"
|
||||
|
||||
fun newInstance(account: Account, collectionInfo: CollectionInfo): DialogFragment {
|
||||
val frag = DeleteCollectionFragment()
|
||||
val args = Bundle(2)
|
||||
args.putParcelable(ARG_ACCOUNT, account)
|
||||
args.putParcelable(ARG_COLLECTION_INFO, collectionInfo)
|
||||
frag.arguments = args
|
||||
return frag
|
||||
}
|
||||
}
|
||||
|
||||
private lateinit var account: Account
|
||||
private lateinit var collectionInfo: CollectionInfo
|
||||
private lateinit var model: DeleteCollectionModel
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
model = ViewModelProviders.of(this).get(DeleteCollectionModel::class.java)
|
||||
|
||||
account = arguments!!.getParcelable(ARG_ACCOUNT)!!
|
||||
collectionInfo = arguments!!.getParcelable(ARG_COLLECTION_INFO)!!
|
||||
|
||||
LoaderManager.getInstance(this).initLoader(0, null, this)
|
||||
model.account = arguments?.getParcelable(ARG_ACCOUNT) as? Account
|
||||
model.collectionInfo = arguments?.getParcelable(ARG_COLLECTION_INFO) as? CollectionInfo
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val progress = ProgressDialog(context)
|
||||
progress.setTitle(R.string.delete_collection_deleting_collection)
|
||||
progress.setMessage(getString(R.string.please_wait))
|
||||
progress.isIndeterminate = true
|
||||
progress.setCanceledOnTouchOutside(false)
|
||||
isCancelable = false
|
||||
return progress
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
val binding = DeleteCollectionBinding.inflate(layoutInflater, null, false)
|
||||
binding.lifecycleOwner = this
|
||||
binding.model = model
|
||||
|
||||
binding.ok.setOnClickListener {
|
||||
isCancelable = false
|
||||
binding.progress.visibility = View.VISIBLE
|
||||
binding.controls.visibility = View.GONE
|
||||
|
||||
model.deleteCollection().observe(this, Observer { exception ->
|
||||
if (exception == null)
|
||||
// reload collection list
|
||||
(activity as? AccountActivity)?.reload()
|
||||
else
|
||||
requireFragmentManager().beginTransaction()
|
||||
.add(ExceptionInfoFragment.newInstance(exception, model.account), null)
|
||||
.commit()
|
||||
dismiss()
|
||||
})
|
||||
}
|
||||
|
||||
binding.cancel.setOnClickListener {
|
||||
dismiss()
|
||||
}
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
|
||||
override fun onCreateLoader(id: Int, args: Bundle?) =
|
||||
DeleteCollectionLoader(activity!!, account, collectionInfo)
|
||||
class DeleteCollectionModel(
|
||||
application: Application
|
||||
): AndroidViewModel(application) {
|
||||
|
||||
override fun onLoadFinished(loader: Loader<Exception>, exception: Exception?) {
|
||||
dismiss()
|
||||
var account: Account? = null
|
||||
var collectionInfo: CollectionInfo? = null
|
||||
|
||||
if (exception != null)
|
||||
requireFragmentManager().beginTransaction()
|
||||
.add(ExceptionInfoFragment.newInstance(exception, account), null)
|
||||
.commit()
|
||||
else
|
||||
(activity as? AccountActivity)?.reload()
|
||||
}
|
||||
val confirmation = MutableLiveData<Boolean>()
|
||||
val result = MutableLiveData<Exception>()
|
||||
|
||||
override fun onLoaderReset(loader: Loader<Exception>) {}
|
||||
fun deleteCollection(): LiveData<Exception> {
|
||||
thread {
|
||||
val account = requireNotNull(account)
|
||||
val collectionInfo = requireNotNull(collectionInfo)
|
||||
|
||||
val context = getApplication<Application>()
|
||||
HttpClient.Builder(context, AccountSettings(context, account))
|
||||
.setForeground(true)
|
||||
.build().use { httpClient ->
|
||||
try {
|
||||
val collection = DavResource(httpClient.okHttpClient, collectionInfo.url)
|
||||
|
||||
class DeleteCollectionLoader(
|
||||
context: Context,
|
||||
val account: Account,
|
||||
val collectionInfo: CollectionInfo
|
||||
): AsyncTaskLoader<Exception>(context) {
|
||||
// delete collection from server
|
||||
collection.delete(null) {}
|
||||
|
||||
override fun onStartLoading() = forceLoad()
|
||||
// delete collection locally
|
||||
ServiceDB.OpenHelper(context).use { dbHelper ->
|
||||
val db = dbHelper.writableDatabase
|
||||
db.delete(ServiceDB.Collections._TABLE, "${ServiceDB.Collections.ID}=?", arrayOf(collectionInfo.id.toString()))
|
||||
}
|
||||
|
||||
override fun loadInBackground(): Exception? {
|
||||
HttpClient.Builder(context, AccountSettings(context, account))
|
||||
.setForeground(true)
|
||||
.build().use { httpClient ->
|
||||
try {
|
||||
val collection = DavResource(httpClient.okHttpClient, collectionInfo.url)
|
||||
// return success
|
||||
result.postValue(null)
|
||||
|
||||
// delete collection from server
|
||||
collection.delete(null) {}
|
||||
|
||||
// delete collection locally
|
||||
ServiceDB.OpenHelper(context).use { dbHelper ->
|
||||
val db = dbHelper.writableDatabase
|
||||
db.delete(ServiceDB.Collections._TABLE, "${ServiceDB.Collections.ID}=?", arrayOf(collectionInfo.id.toString()))
|
||||
}
|
||||
} catch(e: Exception) {
|
||||
return e
|
||||
}
|
||||
} catch(e: Exception) {
|
||||
// return error
|
||||
result.postValue(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ConfirmDeleteCollectionFragment: DialogFragment() {
|
||||
|
||||
companion object {
|
||||
|
||||
fun newInstance(account: Account, collectionInfo: CollectionInfo): DialogFragment {
|
||||
val frag = ConfirmDeleteCollectionFragment()
|
||||
val args = Bundle(2)
|
||||
args.putParcelable(ARG_ACCOUNT, account)
|
||||
args.putParcelable(ARG_COLLECTION_INFO, collectionInfo)
|
||||
frag.arguments = args
|
||||
return frag
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val collectionInfo = arguments!!.getParcelable(ARG_COLLECTION_INFO) as CollectionInfo
|
||||
val name = if (collectionInfo.displayName.isNullOrBlank())
|
||||
collectionInfo.url.toString()
|
||||
else
|
||||
collectionInfo.displayName
|
||||
|
||||
return AlertDialog.Builder(activity!!)
|
||||
.setTitle(R.string.delete_collection_confirm_title)
|
||||
.setMessage(getString(R.string.delete_collection_confirm_warning, name))
|
||||
.setPositiveButton(android.R.string.yes) { _, _ ->
|
||||
val frag = DeleteCollectionFragment()
|
||||
frag.arguments = arguments
|
||||
frag.show(fragmentManager, null)
|
||||
}
|
||||
.setNegativeButton(android.R.string.no) { _, _ ->
|
||||
dismiss()
|
||||
}
|
||||
.create()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -90,19 +90,19 @@ class DefaultLoginCredentialsFragment: Fragment() {
|
||||
when {
|
||||
model.loginWithEmailAddress.value == true -> {
|
||||
// login with email address
|
||||
model.emailAddressError.value = null
|
||||
val email = model.emailAddress.value.orEmpty()
|
||||
model.usernameError.value = null
|
||||
val email = model.username.value.orEmpty()
|
||||
if (email.matches(Regex(".+@.+"))) {
|
||||
// already looks like an email address
|
||||
try {
|
||||
loginModel.baseURI = URI(MailTo.MAILTO_SCHEME, email, null)
|
||||
valid = true
|
||||
} catch (e: URISyntaxException) {
|
||||
model.emailAddressError.value = e.localizedMessage
|
||||
model.usernameError.value = e.localizedMessage
|
||||
}
|
||||
} else {
|
||||
valid = false
|
||||
model.emailAddressError.value = getString(R.string.login_email_address_error)
|
||||
model.usernameError.value = getString(R.string.login_email_address_error)
|
||||
}
|
||||
|
||||
val password = validatePassword()
|
||||
|
||||
@@ -15,9 +15,7 @@ class DefaultLoginCredentialsModel: ViewModel() {
|
||||
val baseUrl = MutableLiveData<String>()
|
||||
val baseUrlError = MutableLiveData<String>()
|
||||
|
||||
val emailAddress = MutableLiveData<String>()
|
||||
val emailAddressError = MutableLiveData<String>()
|
||||
|
||||
/** user name or email address */
|
||||
val username = MutableLiveData<String>()
|
||||
val usernameError = MutableLiveData<String>()
|
||||
|
||||
@@ -45,7 +43,7 @@ class DefaultLoginCredentialsModel: ViewModel() {
|
||||
baseUrl.value = givenUrl
|
||||
} else {
|
||||
loginWithEmailAddress.value = true
|
||||
emailAddress.value = givenUsername
|
||||
username.value = givenUsername
|
||||
}
|
||||
password.value = givenPassword
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ class DetectConfigurationFragment: Fragment() {
|
||||
inflater.inflate(R.layout.detect_configuration, container, false)!!
|
||||
|
||||
|
||||
private class DetectConfigurationModel(
|
||||
class DetectConfigurationModel(
|
||||
application: Application
|
||||
): AndroidViewModel(application) {
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ object BindingAdapters {
|
||||
@JvmStatic
|
||||
fun setError(textView: TextView, error: String?) {
|
||||
textView.error = error
|
||||
textView.requestFocus()
|
||||
}
|
||||
|
||||
@BindingAdapter("html")
|
||||
@@ -24,4 +23,4 @@ object BindingAdapters {
|
||||
textView.text = null
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:gravity="center_horizontal">
|
||||
|
||||
<LinearLayout
|
||||
@@ -20,10 +21,12 @@
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal">
|
||||
|
||||
<ImageView
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="128dp"
|
||||
android:layout_height="128dp"
|
||||
android:src="@mipmap/ic_launcher"
|
||||
android:scaleType="fitXY"
|
||||
app:srcCompat="@drawable/ic_launcher_foreground"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<TextView
|
||||
@@ -7,118 +7,143 @@
|
||||
~ http://www.gnu.org/licenses/gpl.html
|
||||
-->
|
||||
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent">
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/activity_margin">
|
||||
<data>
|
||||
<import type="android.view.View"/>
|
||||
<variable
|
||||
name="model"
|
||||
type="at.bitfire.davdroid.ui.CreateCalendarActivity.Model"/>
|
||||
</data>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/create_calendar"
|
||||
android:textAppearance="@style/TextView.Heading"/>
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="@dimen/activity_margin">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/create_collection_home_set"/>
|
||||
<Spinner
|
||||
android:id="@+id/home_sets"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:labelFor="@id/display_name"
|
||||
android:text="@string/create_collection_display_name"/>
|
||||
<EditText
|
||||
android:id="@+id/display_name"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:hint="@string/create_calendar_display_name_hint"/>
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:labelFor="@id/description"
|
||||
android:text="@string/create_collection_description"/>
|
||||
<EditText
|
||||
android:id="@+id/description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:inputType="textAutoCorrect"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginBottom="16dp">
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/display_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/create_collection_display_name"
|
||||
app:layout_constraintHorizontal_weight="1"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/color">
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@={model.displayName}"
|
||||
app:error="@{model.displayNameError}" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/color"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:background="@color/secondaryColor"/>
|
||||
android:layout_marginLeft="8dp"
|
||||
android:background="@{model.color, default=@color/primaryColor}"
|
||||
android:contentDescription="@string/create_collection_color"
|
||||
app:layout_constraintStart_toEndOf="@id/display_name"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/display_name"
|
||||
app:layout_constraintBottom_toBottomOf="@id/display_name"/>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/create_collection_description"
|
||||
app:helperText="@string/create_collection_optional"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/display_name">
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@={model.description}" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/homesets_title"
|
||||
android:labelFor="@id/homeset"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/create_collection_color"/>
|
||||
android:text="@string/create_collection_home_set"
|
||||
android:layout_marginTop="16dp"
|
||||
app:layout_constraintTop_toBottomOf="@+id/description"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
<Spinner
|
||||
android:id="@+id/homeset"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:adapter="@{model.homeSets}"
|
||||
android:selectedItemPosition="@={model.idxHomeSet}"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/homesets_title" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/create_calendar_time_zone"/>
|
||||
<Spinner
|
||||
android:id="@+id/time_zone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/create_calendar_type"
|
||||
android:textAppearance="@style/TextView.Heading"/>
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/type"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/type_events"
|
||||
<TextView
|
||||
android:id="@+id/timezone_title"
|
||||
android:labelFor="@id/timezone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:text="@string/create_calendar_time_zone"
|
||||
app:layout_constraintTop_toBottomOf="@id/homeset"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/timezone"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="true"
|
||||
android:text="@string/create_calendar_type_only_events"/>
|
||||
android:text="@={model.timezone}"
|
||||
app:error="@{model.timezoneError}"
|
||||
app:layout_constraintTop_toBottomOf="@+id/timezone_title"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/type_tasks"
|
||||
<TextView
|
||||
android:id="@+id/type"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/create_calendar_type"
|
||||
app:error="@{model.typeError}"
|
||||
app:layout_constraintTop_toBottomOf="@id/timezone"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
<com.google.android.flexbox.FlexboxLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/create_calendar_type_only_tasks"/>
|
||||
app:flexWrap="wrap"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/type">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/type_events_and_tasks"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/create_calendar_type_events_and_tasks"/>
|
||||
<CheckBox
|
||||
android:id="@+id/support_vevent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="8dp"
|
||||
android:checked="@={model.supportVEVENT}"
|
||||
android:text="@string/create_calendar_type_vevent"/>
|
||||
<CheckBox
|
||||
android:id="@+id/support_vtodo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="8dp"
|
||||
android:checked="@={model.supportVTODO}"
|
||||
android:text="@string/create_calendar_type_vtodo"/>
|
||||
<CheckBox
|
||||
android:id="@+id/support_vjournal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="8dp"
|
||||
android:checked="@={model.supportVJOURNAL}"
|
||||
android:text="@string/create_calendar_type_vjournal"/>
|
||||
|
||||
</RadioGroup>
|
||||
</com.google.android.flexbox.FlexboxLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</ScrollView>
|
||||
</ScrollView>
|
||||
</layout>
|
||||
80
app/src/main/res/layout/delete_collection.xml
Normal file
80
app/src/main/res/layout/delete_collection.xml
Normal file
@@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<data>
|
||||
<variable
|
||||
name="model"
|
||||
type="at.bitfire.davdroid.ui.DeleteCollectionFragment.DeleteCollectionModel" />
|
||||
</data>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/activity_margin">
|
||||
|
||||
<TextView
|
||||
style="@style/TextView.Heading"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/delete_collection_confirm_title"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@{@string/delete_collection_confirm_warning(model.collectionInfo.title())}" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="true"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
style="@style/Widget.AppCompat.ProgressBar.Horizontal"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/controls"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<CheckBox
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="@={model.confirmation}"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="@string/delete_collection_data_shall_be_deleted"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="right"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Button
|
||||
android:id="@+id/cancel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Widget.AppCompat.Button.ButtonBar.AlertDialog"
|
||||
android:text="@android:string/cancel"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/ok"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Widget.AppCompat.Button.ButtonBar.AlertDialog"
|
||||
android:enabled="@{model.confirmation ?? false}"
|
||||
android:text="@android:string/ok"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</layout>
|
||||
@@ -65,8 +65,8 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/login_email_address"
|
||||
android:text="@={model.emailAddress}"
|
||||
app:error="@{model.emailAddressError}"
|
||||
android:text="@={model.username}"
|
||||
app:error="@{model.usernameError}"
|
||||
android:inputType="textEmailAddress"/>
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item android:title="@string/create_collection_create"
|
||||
<item android:id="@+id/create_calendar"
|
||||
android:title="@string/create_collection_create"
|
||||
android:onClick="onCreateCollection"
|
||||
app:showAsAction="always"/>
|
||||
|
||||
|
||||
@@ -248,23 +248,26 @@
|
||||
<!-- collection management -->
|
||||
<string name="create_addressbook">Create address book</string>
|
||||
<string name="create_addressbook_display_name_hint">My Address Book</string>
|
||||
<string name="create_calendar">Create CalDAV collection</string>
|
||||
<string name="create_calendar_display_name_hint">My Calendar</string>
|
||||
<string name="create_calendar_time_zone">Time zone:</string>
|
||||
<string name="create_calendar_type">Collection type:</string>
|
||||
<string name="create_calendar_type_only_events">Calendar (only events)</string>
|
||||
<string name="create_calendar_type_only_tasks">Task list (only tasks)</string>
|
||||
<string name="create_calendar">Create calendar</string>
|
||||
<string name="create_calendar_time_zone">Time zone</string>
|
||||
<string name="create_calendar_time_zone_required">Time zone required</string>
|
||||
<string name="create_calendar_type">Possible calendar entries</string>
|
||||
<string name="create_calendar_type_vevent">Events</string>
|
||||
<string name="create_calendar_type_vtodo">Tasks</string>
|
||||
<string name="create_calendar_type_vjournal">Notes / journal</string>
|
||||
<string name="create_calendar_type_events_and_tasks">Combined (events and tasks)</string>
|
||||
<string name="create_collection_color">Set a collection color</string>
|
||||
<string name="create_collection_color">Color</string>
|
||||
<string name="create_collection_creating">Creating collection</string>
|
||||
<string name="create_collection_display_name">Display name (title) of this collection:</string>
|
||||
<string name="create_collection_display_name">Title</string>
|
||||
<string name="create_collection_display_name_required">Title is required</string>
|
||||
<string name="create_collection_description">Description (optional):</string>
|
||||
<string name="create_collection_home_set">Home set:</string>
|
||||
<string name="create_collection_description">Description</string>
|
||||
<string name="create_collection_optional">optional</string>
|
||||
<string name="create_collection_home_set">Storage location</string>
|
||||
<string name="create_collection_create">Create</string>
|
||||
<string name="delete_collection">Delete collection</string>
|
||||
<string name="delete_collection_confirm_title">Are you sure?</string>
|
||||
<string name="delete_collection_confirm_warning">This collection (%s) and all its data will be removed from the server.</string>
|
||||
<string name="delete_collection_data_shall_be_deleted">These data shall be deleted permanently.</string>
|
||||
<string name="delete_collection_deleting_collection">Deleting collection</string>
|
||||
<string name="collection_force_read_only">Force read-only</string>
|
||||
<string name="collection_properties">Properties</string>
|
||||
|
||||
Submodule cert4android updated: aaeb4de094...e4314588f8
Submodule ical4android updated: 9c8b971961...be40a3c720
Submodule vcard4android updated: 8171fd4470...76f8ecbe07
Reference in New Issue
Block a user