mirror of
https://github.com/Aviortheking/Neo-Store.git
synced 2025-04-23 19:32:16 +00:00
Reformatted Code and Updated VersionName/VersionCode
This commit is contained in:
parent
93442f74d1
commit
efc02add3d
@ -25,8 +25,8 @@ android {
|
|||||||
applicationId 'com.looker.droidify'
|
applicationId 'com.looker.droidify'
|
||||||
minSdkVersion 28
|
minSdkVersion 28
|
||||||
targetSdkVersion 29
|
targetSdkVersion 29
|
||||||
versionCode 1
|
versionCode 2
|
||||||
versionName '0.1'
|
versionName '0.2'
|
||||||
|
|
||||||
def languages = [ 'en' ]
|
def languages = [ 'en' ]
|
||||||
buildConfigField 'String[]', 'LANGUAGES', '{ "' + languages.join('", "') + '" }'
|
buildConfigField 'String[]', 'LANGUAGES', '{ "' + languages.join('", "') + '" }'
|
||||||
|
@ -20,11 +20,6 @@ import android.widget.FrameLayout
|
|||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toolbar
|
import android.widget.Toolbar
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
|
||||||
import io.reactivex.rxjava3.core.Observable
|
|
||||||
import io.reactivex.rxjava3.core.Single
|
|
||||||
import io.reactivex.rxjava3.disposables.Disposable
|
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
|
||||||
import com.looker.droidify.R
|
import com.looker.droidify.R
|
||||||
import com.looker.droidify.database.Database
|
import com.looker.droidify.database.Database
|
||||||
import com.looker.droidify.entity.Repository
|
import com.looker.droidify.entity.Repository
|
||||||
@ -33,452 +28,530 @@ import com.looker.droidify.service.Connection
|
|||||||
import com.looker.droidify.service.SyncService
|
import com.looker.droidify.service.SyncService
|
||||||
import com.looker.droidify.utility.RxUtils
|
import com.looker.droidify.utility.RxUtils
|
||||||
import com.looker.droidify.utility.Utils
|
import com.looker.droidify.utility.Utils
|
||||||
import com.looker.droidify.utility.extension.resources.*
|
import com.looker.droidify.utility.extension.resources.getColorFromAttr
|
||||||
import com.looker.droidify.utility.extension.text.*
|
import com.looker.droidify.utility.extension.resources.inflate
|
||||||
|
import com.looker.droidify.utility.extension.text.nullIfEmpty
|
||||||
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
|
import io.reactivex.rxjava3.core.Observable
|
||||||
|
import io.reactivex.rxjava3.core.Single
|
||||||
|
import io.reactivex.rxjava3.disposables.Disposable
|
||||||
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
import java.util.Locale
|
import java.util.*
|
||||||
import kotlin.math.*
|
import kotlin.collections.ArrayList
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
class EditRepositoryFragment(): ScreenFragment() {
|
class EditRepositoryFragment() : ScreenFragment() {
|
||||||
companion object {
|
companion object {
|
||||||
private const val EXTRA_REPOSITORY_ID = "repositoryId"
|
private const val EXTRA_REPOSITORY_ID = "repositoryId"
|
||||||
|
|
||||||
private val checkPaths = listOf("", "fdroid/repo", "repo")
|
private val checkPaths = listOf("", "fdroid/repo", "repo")
|
||||||
}
|
|
||||||
|
|
||||||
constructor(repositoryId: Long?): this() {
|
|
||||||
arguments = Bundle().apply {
|
|
||||||
repositoryId?.let { putLong(EXTRA_REPOSITORY_ID, it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Layout(view: View) {
|
|
||||||
val address = view.findViewById<EditText>(R.id.address)!!
|
|
||||||
val addressMirror = view.findViewById<View>(R.id.address_mirror)!!
|
|
||||||
val addressError = view.findViewById<TextView>(R.id.address_error)!!
|
|
||||||
val fingerprint = view.findViewById<EditText>(R.id.fingerprint)!!
|
|
||||||
val fingerprintError = view.findViewById<TextView>(R.id.fingerprint_error)!!
|
|
||||||
val username = view.findViewById<EditText>(R.id.username)!!
|
|
||||||
val usernameError = view.findViewById<TextView>(R.id.username_error)!!
|
|
||||||
val password = view.findViewById<EditText>(R.id.password)!!
|
|
||||||
val passwordError = view.findViewById<TextView>(R.id.password_error)!!
|
|
||||||
val overlay = view.findViewById<View>(R.id.overlay)!!
|
|
||||||
val skip = view.findViewById<View>(R.id.skip)!!
|
|
||||||
}
|
|
||||||
|
|
||||||
private val repositoryId: Long?
|
|
||||||
get() = requireArguments().let { if (it.containsKey(EXTRA_REPOSITORY_ID))
|
|
||||||
it.getLong(EXTRA_REPOSITORY_ID) else null }
|
|
||||||
|
|
||||||
private lateinit var errorColorFilter: PorterDuffColorFilter
|
|
||||||
|
|
||||||
private var saveMenuItem: MenuItem? = null
|
|
||||||
private var layout: Layout? = null
|
|
||||||
|
|
||||||
private val syncConnection = Connection(SyncService::class.java)
|
|
||||||
private var repositoriesDisposable: Disposable? = null
|
|
||||||
private var checkDisposable: Disposable? = null
|
|
||||||
|
|
||||||
private var takenAddresses = emptySet<String>()
|
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
|
||||||
return inflater.inflate(R.layout.fragment, container, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
|
|
||||||
syncConnection.bind(requireContext())
|
|
||||||
|
|
||||||
val toolbar = view.findViewById<Toolbar>(R.id.toolbar)!!
|
|
||||||
screenActivity.onToolbarCreated(toolbar)
|
|
||||||
if (repositoryId != null) {
|
|
||||||
toolbar.setTitle(R.string.edit_repository)
|
|
||||||
} else {
|
|
||||||
toolbar.setTitle(R.string.add_repository)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toolbar.menu.apply {
|
constructor(repositoryId: Long?) : this() {
|
||||||
saveMenuItem = add(R.string.save)
|
arguments = Bundle().apply {
|
||||||
.setIcon(Utils.getToolbarIcon(toolbar.context, R.drawable.ic_save))
|
repositoryId?.let { putLong(EXTRA_REPOSITORY_ID, it) }
|
||||||
.setEnabled(false)
|
|
||||||
.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_ALWAYS)
|
|
||||||
.setOnMenuItemClickListener {
|
|
||||||
onSaveRepositoryClick(true)
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val content = view.findViewById<FrameLayout>(R.id.fragment_content)!!
|
private class Layout(view: View) {
|
||||||
errorColorFilter = PorterDuffColorFilter(content.context
|
val address = view.findViewById<EditText>(R.id.address)!!
|
||||||
.getColorFromAttr(R.attr.colorError).defaultColor, PorterDuff.Mode.SRC_IN)
|
val addressMirror = view.findViewById<View>(R.id.address_mirror)!!
|
||||||
|
val addressError = view.findViewById<TextView>(R.id.address_error)!!
|
||||||
|
val fingerprint = view.findViewById<EditText>(R.id.fingerprint)!!
|
||||||
|
val fingerprintError = view.findViewById<TextView>(R.id.fingerprint_error)!!
|
||||||
|
val username = view.findViewById<EditText>(R.id.username)!!
|
||||||
|
val usernameError = view.findViewById<TextView>(R.id.username_error)!!
|
||||||
|
val password = view.findViewById<EditText>(R.id.password)!!
|
||||||
|
val passwordError = view.findViewById<TextView>(R.id.password_error)!!
|
||||||
|
val overlay = view.findViewById<View>(R.id.overlay)!!
|
||||||
|
val skip = view.findViewById<View>(R.id.skip)!!
|
||||||
|
}
|
||||||
|
|
||||||
content.addView(content.inflate(R.layout.edit_repository))
|
private val repositoryId: Long?
|
||||||
val layout = Layout(content)
|
get() = requireArguments().let {
|
||||||
this.layout = layout
|
if (it.containsKey(EXTRA_REPOSITORY_ID))
|
||||||
|
it.getLong(EXTRA_REPOSITORY_ID) else null
|
||||||
|
}
|
||||||
|
|
||||||
layout.fingerprint.hint = generateSequence { "FF" }.take(32).joinToString(separator = " ")
|
private lateinit var errorColorFilter: PorterDuffColorFilter
|
||||||
layout.fingerprint.addTextChangedListener(object: TextWatcher {
|
|
||||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit
|
|
||||||
override fun onTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit
|
|
||||||
|
|
||||||
private val validChar: (Char) -> Boolean = { it in '0' .. '9' || it in 'a' .. 'f' || it in 'A' .. 'F' }
|
private var saveMenuItem: MenuItem? = null
|
||||||
|
private var layout: Layout? = null
|
||||||
|
|
||||||
private fun logicalPosition(s: String, position: Int): Int {
|
private val syncConnection = Connection(SyncService::class.java)
|
||||||
return if (position > 0) s.asSequence().take(position).count(validChar) else position
|
private var repositoriesDisposable: Disposable? = null
|
||||||
}
|
private var checkDisposable: Disposable? = null
|
||||||
|
|
||||||
private fun realPosition(s: String, position: Int): Int {
|
private var takenAddresses = emptySet<String>()
|
||||||
return if (position > 0) {
|
|
||||||
var left = position
|
override fun onCreateView(
|
||||||
val index = s.indexOfFirst {
|
inflater: LayoutInflater,
|
||||||
validChar(it) && run {
|
container: ViewGroup?,
|
||||||
left -= 1
|
savedInstanceState: Bundle?
|
||||||
left <= 0
|
): View {
|
||||||
}
|
return inflater.inflate(R.layout.fragment, container, false)
|
||||||
}
|
}
|
||||||
if (index >= 0) min(index + 1, s.length) else s.length
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
syncConnection.bind(requireContext())
|
||||||
|
|
||||||
|
val toolbar = view.findViewById<Toolbar>(R.id.toolbar)!!
|
||||||
|
screenActivity.onToolbarCreated(toolbar)
|
||||||
|
if (repositoryId != null) {
|
||||||
|
toolbar.setTitle(R.string.edit_repository)
|
||||||
} else {
|
} else {
|
||||||
position
|
toolbar.setTitle(R.string.add_repository)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun afterTextChanged(s: Editable) {
|
toolbar.menu.apply {
|
||||||
val inputString = s.toString()
|
saveMenuItem = add(R.string.save)
|
||||||
val outputString = inputString.toUpperCase(Locale.US)
|
.setIcon(Utils.getToolbarIcon(toolbar.context, R.drawable.ic_save))
|
||||||
.filter(validChar).windowed(2, 2, true).take(32).joinToString(separator = " ")
|
.setEnabled(false)
|
||||||
if (inputString != outputString) {
|
.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_ALWAYS)
|
||||||
val inputStart = logicalPosition(inputString, Selection.getSelectionStart(s))
|
.setOnMenuItemClickListener {
|
||||||
val inputEnd = logicalPosition(inputString, Selection.getSelectionEnd(s))
|
onSaveRepositoryClick(true)
|
||||||
s.replace(0, s.length, outputString)
|
true
|
||||||
Selection.setSelection(s, realPosition(outputString, inputStart), realPosition(outputString, inputEnd))
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
val content = view.findViewById<FrameLayout>(R.id.fragment_content)!!
|
||||||
val repository = repositoryId?.let(Database.RepositoryAdapter::get)
|
errorColorFilter = PorterDuffColorFilter(
|
||||||
if (repository == null) {
|
content.context
|
||||||
val clipboardManager = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
.getColorFromAttr(R.attr.colorError).defaultColor, PorterDuff.Mode.SRC_IN
|
||||||
val text = clipboardManager.primaryClip
|
)
|
||||||
?.let { if (it.itemCount > 0) it else null }
|
|
||||||
?.getItemAt(0)?.text?.toString().orEmpty()
|
content.addView(content.inflate(R.layout.edit_repository))
|
||||||
val (addressText, fingerprintText) = try {
|
val layout = Layout(content)
|
||||||
val uri = Uri.parse(URL(text).toString())
|
this.layout = layout
|
||||||
val fingerprintText = uri.getQueryParameter("fingerprint")?.nullIfEmpty()
|
|
||||||
?: uri.getQueryParameter("FINGERPRINT")?.nullIfEmpty()
|
layout.fingerprint.hint = generateSequence { "FF" }.take(32).joinToString(separator = " ")
|
||||||
Pair(uri.buildUpon().path(uri.path?.pathCropped)
|
layout.fingerprint.addTextChangedListener(object : TextWatcher {
|
||||||
.query(null).fragment(null).build().toString(), fingerprintText)
|
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) =
|
||||||
} catch (e: Exception) {
|
Unit
|
||||||
Pair(null, null)
|
|
||||||
}
|
override fun onTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit
|
||||||
layout.address.setText(addressText?.nullIfEmpty() ?: layout.address.hint)
|
|
||||||
layout.fingerprint.setText(fingerprintText)
|
private val validChar: (Char) -> Boolean =
|
||||||
} else {
|
{ it in '0'..'9' || it in 'a'..'f' || it in 'A'..'F' }
|
||||||
layout.address.setText(repository.address)
|
|
||||||
val mirrors = repository.mirrors.map { it.withoutKnownPath }
|
private fun logicalPosition(s: String, position: Int): Int {
|
||||||
if (mirrors.isNotEmpty()) {
|
return if (position > 0) s.asSequence().take(position)
|
||||||
layout.addressMirror.visibility = View.VISIBLE
|
.count(validChar) else position
|
||||||
layout.address.apply { setPaddingRelative(paddingStart, paddingTop,
|
}
|
||||||
paddingEnd + layout.addressMirror.layoutParams.width, paddingBottom) }
|
|
||||||
layout.addressMirror.setOnClickListener { SelectMirrorDialog(mirrors)
|
private fun realPosition(s: String, position: Int): Int {
|
||||||
.show(childFragmentManager, SelectMirrorDialog::class.java.name) }
|
return if (position > 0) {
|
||||||
}
|
var left = position
|
||||||
layout.fingerprint.setText(repository.fingerprint)
|
val index = s.indexOfFirst {
|
||||||
val (usernameText, passwordText) = repository.authentication.nullIfEmpty()
|
validChar(it) && run {
|
||||||
?.let { if (it.startsWith("Basic ")) it.substring(6) else null }
|
left -= 1
|
||||||
?.let {
|
left <= 0
|
||||||
try {
|
}
|
||||||
Base64.decode(it, Base64.NO_WRAP).toString(Charset.defaultCharset())
|
}
|
||||||
} catch (e: Exception) {
|
if (index >= 0) min(index + 1, s.length) else s.length
|
||||||
e.printStackTrace()
|
} else {
|
||||||
null
|
position
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun afterTextChanged(s: Editable) {
|
||||||
|
val inputString = s.toString()
|
||||||
|
val outputString = inputString.uppercase(Locale.US)
|
||||||
|
.filter(validChar).windowed(2, 2, true).take(32).joinToString(separator = " ")
|
||||||
|
if (inputString != outputString) {
|
||||||
|
val inputStart = logicalPosition(inputString, Selection.getSelectionStart(s))
|
||||||
|
val inputEnd = logicalPosition(inputString, Selection.getSelectionEnd(s))
|
||||||
|
s.replace(0, s.length, outputString)
|
||||||
|
Selection.setSelection(
|
||||||
|
s,
|
||||||
|
realPosition(outputString, inputStart),
|
||||||
|
realPosition(outputString, inputEnd)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
val repository = repositoryId?.let(Database.RepositoryAdapter::get)
|
||||||
|
if (repository == null) {
|
||||||
|
val clipboardManager =
|
||||||
|
requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
|
val text = clipboardManager.primaryClip
|
||||||
|
?.let { if (it.itemCount > 0) it else null }
|
||||||
|
?.getItemAt(0)?.text?.toString().orEmpty()
|
||||||
|
val (addressText, fingerprintText) = try {
|
||||||
|
val uri = Uri.parse(URL(text).toString())
|
||||||
|
val fingerprintText = uri.getQueryParameter("fingerprint")?.nullIfEmpty()
|
||||||
|
?: uri.getQueryParameter("FINGERPRINT")?.nullIfEmpty()
|
||||||
|
Pair(
|
||||||
|
uri.buildUpon().path(uri.path?.pathCropped)
|
||||||
|
.query(null).fragment(null).build().toString(), fingerprintText
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Pair(null, null)
|
||||||
|
}
|
||||||
|
layout.address.setText(addressText?.nullIfEmpty() ?: layout.address.hint)
|
||||||
|
layout.fingerprint.setText(fingerprintText)
|
||||||
|
} else {
|
||||||
|
layout.address.setText(repository.address)
|
||||||
|
val mirrors = repository.mirrors.map { it.withoutKnownPath }
|
||||||
|
if (mirrors.isNotEmpty()) {
|
||||||
|
layout.addressMirror.visibility = View.VISIBLE
|
||||||
|
layout.address.apply {
|
||||||
|
setPaddingRelative(
|
||||||
|
paddingStart, paddingTop,
|
||||||
|
paddingEnd + layout.addressMirror.layoutParams.width, paddingBottom
|
||||||
|
)
|
||||||
|
}
|
||||||
|
layout.addressMirror.setOnClickListener {
|
||||||
|
SelectMirrorDialog(mirrors)
|
||||||
|
.show(childFragmentManager, SelectMirrorDialog::class.java.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layout.fingerprint.setText(repository.fingerprint)
|
||||||
|
val (usernameText, passwordText) = repository.authentication.nullIfEmpty()
|
||||||
|
?.let { if (it.startsWith("Basic ")) it.substring(6) else null }
|
||||||
|
?.let {
|
||||||
|
try {
|
||||||
|
Base64.decode(it, Base64.NO_WRAP).toString(Charset.defaultCharset())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?.let {
|
||||||
|
val index = it.indexOf(':')
|
||||||
|
if (index >= 0) Pair(
|
||||||
|
it.substring(0, index),
|
||||||
|
it.substring(index + 1)
|
||||||
|
) else null
|
||||||
|
}
|
||||||
|
?: Pair(null, null)
|
||||||
|
layout.username.setText(usernameText)
|
||||||
|
layout.password.setText(passwordText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
layout.address.addTextChangedListener(SimpleTextWatcher { invalidateAddress() })
|
||||||
|
layout.fingerprint.addTextChangedListener(SimpleTextWatcher { invalidateFingerprint() })
|
||||||
|
layout.username.addTextChangedListener(SimpleTextWatcher { invalidateUsernamePassword() })
|
||||||
|
layout.password.addTextChangedListener(SimpleTextWatcher { invalidateUsernamePassword() })
|
||||||
|
|
||||||
|
(layout.overlay.parent as ViewGroup).layoutTransition?.setDuration(200L)
|
||||||
|
layout.overlay.background!!.apply {
|
||||||
|
mutate()
|
||||||
|
alpha = 0xcc
|
||||||
|
}
|
||||||
|
layout.skip.setOnClickListener {
|
||||||
|
if (checkDisposable != null) {
|
||||||
|
checkDisposable?.dispose()
|
||||||
|
checkDisposable = null
|
||||||
|
onSaveRepositoryClick(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositoriesDisposable = Observable.just(Unit)
|
||||||
|
.concatWith(Database.observable(Database.Subject.Repositories))
|
||||||
|
.observeOn(Schedulers.io())
|
||||||
|
.flatMapSingle { RxUtils.querySingle { Database.RepositoryAdapter.getAll(it) } }
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe { it ->
|
||||||
|
takenAddresses = it.asSequence().filter { it.id != repositoryId }
|
||||||
|
.flatMap { (it.mirrors + it.address).asSequence() }
|
||||||
|
.map { it.withoutKnownPath }.toSet()
|
||||||
|
invalidateAddress()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
?.let {
|
|
||||||
val index = it.indexOf(':')
|
|
||||||
if (index >= 0) Pair(it.substring(0, index), it.substring(index + 1)) else null
|
|
||||||
}
|
|
||||||
?: Pair(null, null)
|
|
||||||
layout.username.setText(usernameText)
|
|
||||||
layout.password.setText(passwordText)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
layout.address.addTextChangedListener(SimpleTextWatcher { invalidateAddress() })
|
override fun onDestroyView() {
|
||||||
layout.fingerprint.addTextChangedListener(SimpleTextWatcher { invalidateFingerprint() })
|
super.onDestroyView()
|
||||||
layout.username.addTextChangedListener(SimpleTextWatcher { invalidateUsernamePassword() })
|
|
||||||
layout.password.addTextChangedListener(SimpleTextWatcher { invalidateUsernamePassword() })
|
|
||||||
|
|
||||||
(layout.overlay.parent as ViewGroup).layoutTransition?.setDuration(200L)
|
saveMenuItem = null
|
||||||
layout.overlay.background!!.apply {
|
layout = null
|
||||||
mutate()
|
|
||||||
alpha = 0xcc
|
syncConnection.unbind(requireContext())
|
||||||
}
|
repositoriesDisposable?.dispose()
|
||||||
layout.skip.setOnClickListener {
|
repositoriesDisposable = null
|
||||||
if (checkDisposable != null) {
|
|
||||||
checkDisposable?.dispose()
|
checkDisposable?.dispose()
|
||||||
checkDisposable = null
|
checkDisposable = null
|
||||||
onSaveRepositoryClick(false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
repositoriesDisposable = Observable.just(Unit)
|
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||||
.concatWith(Database.observable(Database.Subject.Repositories))
|
super.onActivityCreated(savedInstanceState)
|
||||||
.observeOn(Schedulers.io())
|
|
||||||
.flatMapSingle { RxUtils.querySingle { Database.RepositoryAdapter.getAll(it) } }
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe {
|
|
||||||
takenAddresses = it.asSequence().filter { it.id != repositoryId }
|
|
||||||
.flatMap { (it.mirrors + it.address).asSequence() }
|
|
||||||
.map { it.withoutKnownPath }.toSet()
|
|
||||||
invalidateAddress()
|
invalidateAddress()
|
||||||
}
|
invalidateFingerprint()
|
||||||
}
|
invalidateUsernamePassword()
|
||||||
|
|
||||||
override fun onDestroyView() {
|
|
||||||
super.onDestroyView()
|
|
||||||
|
|
||||||
saveMenuItem = null
|
|
||||||
layout = null
|
|
||||||
|
|
||||||
syncConnection.unbind(requireContext())
|
|
||||||
repositoriesDisposable?.dispose()
|
|
||||||
repositoriesDisposable = null
|
|
||||||
checkDisposable?.dispose()
|
|
||||||
checkDisposable = null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
|
||||||
super.onActivityCreated(savedInstanceState)
|
|
||||||
|
|
||||||
invalidateAddress()
|
|
||||||
invalidateFingerprint()
|
|
||||||
invalidateUsernamePassword()
|
|
||||||
}
|
|
||||||
|
|
||||||
private var addressError = false
|
|
||||||
private var fingerprintError = false
|
|
||||||
private var usernamePasswordError = false
|
|
||||||
|
|
||||||
private fun invalidateAddress() {
|
|
||||||
invalidateAddress(layout!!.address.text.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun invalidateAddress(addressText: String) {
|
|
||||||
val layout = layout!!
|
|
||||||
val normalizedAddress = normalizeAddress(addressText)
|
|
||||||
val addressErrorResId = if (normalizedAddress != null) {
|
|
||||||
if (normalizedAddress.withoutKnownPath in takenAddresses) {
|
|
||||||
R.string.already_exists
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
R.string.invalid_address
|
|
||||||
}
|
|
||||||
layout.address.setError(addressErrorResId != null)
|
|
||||||
layout.addressError.visibility = if (addressErrorResId != null) View.VISIBLE else View.GONE
|
|
||||||
if (addressErrorResId != null) {
|
|
||||||
layout.addressError.setText(addressErrorResId)
|
|
||||||
}
|
|
||||||
addressError = addressErrorResId != null
|
|
||||||
invalidateState()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun invalidateFingerprint() {
|
|
||||||
val layout = layout!!
|
|
||||||
val fingerprint = layout.fingerprint.text.toString().replace(" ", "")
|
|
||||||
val fingerprintInvalid = fingerprint.isNotEmpty() && fingerprint.length != 64
|
|
||||||
layout.fingerprintError.visibility = if (fingerprintInvalid) View.VISIBLE else View.GONE
|
|
||||||
if (fingerprintInvalid) {
|
|
||||||
layout.fingerprintError.setText(R.string.invalid_fingerprint_format)
|
|
||||||
}
|
|
||||||
layout.fingerprint.setError(fingerprintInvalid)
|
|
||||||
fingerprintError = fingerprintInvalid
|
|
||||||
invalidateState()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun invalidateUsernamePassword() {
|
|
||||||
val layout = layout!!
|
|
||||||
val username = layout.username.text.toString()
|
|
||||||
val password = layout.password.text.toString()
|
|
||||||
val usernameInvalid = username.contains(':')
|
|
||||||
val usernameEmpty = username.isEmpty() && password.isNotEmpty()
|
|
||||||
val passwordEmpty = username.isNotEmpty() && password.isEmpty()
|
|
||||||
layout.usernameError.visibility = if (usernameInvalid || usernameEmpty) View.VISIBLE else View.GONE
|
|
||||||
layout.passwordError.visibility = if (passwordEmpty) View.VISIBLE else View.GONE
|
|
||||||
if (usernameInvalid) {
|
|
||||||
layout.usernameError.setText(R.string.invalid_username_format)
|
|
||||||
} else if (usernameEmpty) {
|
|
||||||
layout.usernameError.setText(R.string.username_missing)
|
|
||||||
}
|
|
||||||
layout.username.setError(usernameEmpty)
|
|
||||||
if (passwordEmpty) {
|
|
||||||
layout.passwordError.setText(R.string.password_missing)
|
|
||||||
}
|
|
||||||
layout.password.setError(passwordEmpty)
|
|
||||||
usernamePasswordError = usernameInvalid || usernameEmpty || passwordEmpty
|
|
||||||
invalidateState()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun invalidateState() {
|
|
||||||
val layout = layout!!
|
|
||||||
saveMenuItem!!.isEnabled = !addressError && !fingerprintError &&
|
|
||||||
!usernamePasswordError && checkDisposable == null
|
|
||||||
layout.apply { sequenceOf(address, addressMirror, fingerprint, username, password)
|
|
||||||
.forEach { it.isEnabled = checkDisposable == null } }
|
|
||||||
layout.overlay.visibility = if (checkDisposable != null) View.VISIBLE else View.GONE
|
|
||||||
}
|
|
||||||
|
|
||||||
private val String.pathCropped: String
|
|
||||||
get() {
|
|
||||||
val index = indexOfLast { it != '/' }
|
|
||||||
return if (index >= 0 && index < length - 1) substring(0, index + 1) else this
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val String.withoutKnownPath: String
|
private var addressError = false
|
||||||
get() {
|
private var fingerprintError = false
|
||||||
val cropped = pathCropped
|
private var usernamePasswordError = false
|
||||||
val endsWith = checkPaths.asSequence().filter { it.isNotEmpty() }
|
|
||||||
.sortedByDescending { it.length }.find { cropped.endsWith("/$it") }
|
private fun invalidateAddress() {
|
||||||
return if (endsWith != null) cropped.substring(0, cropped.length - endsWith.length - 1) else cropped
|
invalidateAddress(layout!!.address.text.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun normalizeAddress(address: String): String? {
|
private fun invalidateAddress(addressText: String) {
|
||||||
val uri = try {
|
val layout = layout!!
|
||||||
val uri = URI(address)
|
val normalizedAddress = normalizeAddress(addressText)
|
||||||
if (uri.isAbsolute) uri.normalize() else null
|
val addressErrorResId = if (normalizedAddress != null) {
|
||||||
} catch (e: Exception) {
|
if (normalizedAddress.withoutKnownPath in takenAddresses) {
|
||||||
null
|
R.string.already_exists
|
||||||
}
|
|
||||||
val path = uri?.path?.pathCropped
|
|
||||||
return if (uri != null && path != null) {
|
|
||||||
try {
|
|
||||||
URI(uri.scheme, uri.userInfo, uri.host, uri.port, path, uri.query, uri.fragment).toString()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setMirror(address: String) {
|
|
||||||
layout?.address?.setText(address)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun EditText.setError(error: Boolean) {
|
|
||||||
val drawable = background.mutate()
|
|
||||||
drawable.colorFilter = if (error) errorColorFilter else null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onSaveRepositoryClick(check: Boolean) {
|
|
||||||
if (checkDisposable == null) {
|
|
||||||
val layout = layout!!
|
|
||||||
val address = normalizeAddress(layout.address.text.toString())!!
|
|
||||||
val fingerprint = layout.fingerprint.text.toString().replace(" ", "")
|
|
||||||
val username = layout.username.text.toString().nullIfEmpty()
|
|
||||||
val password = layout.password.text.toString().nullIfEmpty()
|
|
||||||
val paths = sequenceOf("", "fdroid/repo", "repo")
|
|
||||||
val authentication = username?.let { u -> password
|
|
||||||
?.let { p -> Base64.encodeToString("$u:$p".toByteArray(Charset.defaultCharset()), Base64.NO_WRAP) } }
|
|
||||||
?.let { "Basic $it" }.orEmpty()
|
|
||||||
|
|
||||||
if (check) {
|
|
||||||
checkDisposable = paths
|
|
||||||
.fold(Single.just("")) { oldAddressSingle, checkPath -> oldAddressSingle
|
|
||||||
.flatMap { oldAddress ->
|
|
||||||
if (oldAddress.isEmpty()) {
|
|
||||||
val builder = Uri.parse(address).buildUpon()
|
|
||||||
.let { if (checkPath.isEmpty()) it else it.appendEncodedPath(checkPath) }
|
|
||||||
val newAddress = builder.build()
|
|
||||||
val indexAddress = builder.appendPath("index.jar").build()
|
|
||||||
RxUtils
|
|
||||||
.callSingle { Downloader
|
|
||||||
.createCall(Request.Builder().method("HEAD", null)
|
|
||||||
.url(indexAddress.toString().toHttpUrl()), authentication, null) }
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.map { if (it.code == 200) newAddress.toString() else "" }
|
|
||||||
} else {
|
|
||||||
Single.just(oldAddress)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe { result, throwable ->
|
|
||||||
checkDisposable = null
|
|
||||||
throwable?.printStackTrace()
|
|
||||||
val resultAddress = result?.let { if (it.isEmpty()) null else it } ?: address
|
|
||||||
val allow = resultAddress == address || run {
|
|
||||||
layout.address.setText(resultAddress)
|
|
||||||
invalidateAddress(resultAddress)
|
|
||||||
!addressError
|
|
||||||
}
|
|
||||||
if (allow) {
|
|
||||||
onSaveRepositoryProceedInvalidate(resultAddress, fingerprint, authentication)
|
|
||||||
} else {
|
} else {
|
||||||
invalidateState()
|
null
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
invalidateState()
|
R.string.invalid_address
|
||||||
} else {
|
|
||||||
onSaveRepositoryProceedInvalidate(address, fingerprint, authentication)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onSaveRepositoryProceedInvalidate(address: String, fingerprint: String, authentication: String) {
|
|
||||||
val binder = syncConnection.binder
|
|
||||||
if (binder != null) {
|
|
||||||
val repositoryId = repositoryId
|
|
||||||
if (repositoryId != null && binder.isCurrentlySyncing(repositoryId)) {
|
|
||||||
MessageDialog(MessageDialog.Message.CantEditSyncing).show(childFragmentManager)
|
|
||||||
invalidateState()
|
|
||||||
} else {
|
|
||||||
val repository = repositoryId?.let(Database.RepositoryAdapter::get)
|
|
||||||
?.edit(address, fingerprint, authentication)
|
|
||||||
?: Repository.newRepository(address, fingerprint, authentication)
|
|
||||||
val changedRepository = Database.RepositoryAdapter.put(repository)
|
|
||||||
if (repositoryId == null && changedRepository.enabled) {
|
|
||||||
binder.sync(changedRepository)
|
|
||||||
}
|
}
|
||||||
requireActivity().onBackPressed()
|
layout.address.setError(addressErrorResId != null)
|
||||||
}
|
layout.addressError.visibility = if (addressErrorResId != null) View.VISIBLE else View.GONE
|
||||||
} else {
|
if (addressErrorResId != null) {
|
||||||
invalidateState()
|
layout.addressError.setText(addressErrorResId)
|
||||||
}
|
}
|
||||||
}
|
addressError = addressErrorResId != null
|
||||||
|
invalidateState()
|
||||||
private class SimpleTextWatcher(private val callback: (Editable) -> Unit): TextWatcher {
|
|
||||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit
|
|
||||||
override fun onTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit
|
|
||||||
override fun afterTextChanged(s: Editable) = callback(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
class SelectMirrorDialog(): DialogFragment() {
|
|
||||||
companion object {
|
|
||||||
private const val EXTRA_MIRRORS = "mirrors"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(mirrors: List<String>): this() {
|
private fun invalidateFingerprint() {
|
||||||
arguments = Bundle().apply {
|
val layout = layout!!
|
||||||
putStringArrayList(EXTRA_MIRRORS, ArrayList(mirrors))
|
val fingerprint = layout.fingerprint.text.toString().replace(" ", "")
|
||||||
}
|
val fingerprintInvalid = fingerprint.isNotEmpty() && fingerprint.length != 64
|
||||||
|
layout.fingerprintError.visibility = if (fingerprintInvalid) View.VISIBLE else View.GONE
|
||||||
|
if (fingerprintInvalid) {
|
||||||
|
layout.fingerprintError.setText(R.string.invalid_fingerprint_format)
|
||||||
|
}
|
||||||
|
layout.fingerprint.setError(fingerprintInvalid)
|
||||||
|
fingerprintError = fingerprintInvalid
|
||||||
|
invalidateState()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): AlertDialog {
|
private fun invalidateUsernamePassword() {
|
||||||
val mirrors = requireArguments().getStringArrayList(EXTRA_MIRRORS)!!
|
val layout = layout!!
|
||||||
return AlertDialog.Builder(requireContext())
|
val username = layout.username.text.toString()
|
||||||
.setTitle(R.string.select_mirror)
|
val password = layout.password.text.toString()
|
||||||
.setItems(mirrors.toTypedArray()) { _, position -> (parentFragment as EditRepositoryFragment)
|
val usernameInvalid = username.contains(':')
|
||||||
.setMirror(mirrors[position]) }
|
val usernameEmpty = username.isEmpty() && password.isNotEmpty()
|
||||||
.setNegativeButton(R.string.cancel, null)
|
val passwordEmpty = username.isNotEmpty() && password.isEmpty()
|
||||||
.create()
|
layout.usernameError.visibility =
|
||||||
|
if (usernameInvalid || usernameEmpty) View.VISIBLE else View.GONE
|
||||||
|
layout.passwordError.visibility = if (passwordEmpty) View.VISIBLE else View.GONE
|
||||||
|
if (usernameInvalid) {
|
||||||
|
layout.usernameError.setText(R.string.invalid_username_format)
|
||||||
|
} else if (usernameEmpty) {
|
||||||
|
layout.usernameError.setText(R.string.username_missing)
|
||||||
|
}
|
||||||
|
layout.username.setError(usernameEmpty)
|
||||||
|
if (passwordEmpty) {
|
||||||
|
layout.passwordError.setText(R.string.password_missing)
|
||||||
|
}
|
||||||
|
layout.password.setError(passwordEmpty)
|
||||||
|
usernamePasswordError = usernameInvalid || usernameEmpty || passwordEmpty
|
||||||
|
invalidateState()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun invalidateState() {
|
||||||
|
val layout = layout!!
|
||||||
|
saveMenuItem!!.isEnabled = !addressError && !fingerprintError &&
|
||||||
|
!usernamePasswordError && checkDisposable == null
|
||||||
|
layout.apply {
|
||||||
|
sequenceOf(address, addressMirror, fingerprint, username, password)
|
||||||
|
.forEach { it.isEnabled = checkDisposable == null }
|
||||||
|
}
|
||||||
|
layout.overlay.visibility = if (checkDisposable != null) View.VISIBLE else View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
private val String.pathCropped: String
|
||||||
|
get() {
|
||||||
|
val index = indexOfLast { it != '/' }
|
||||||
|
return if (index >= 0 && index < length - 1) substring(0, index + 1) else this
|
||||||
|
}
|
||||||
|
|
||||||
|
private val String.withoutKnownPath: String
|
||||||
|
get() {
|
||||||
|
val cropped = pathCropped
|
||||||
|
val endsWith = checkPaths.asSequence().filter { it.isNotEmpty() }
|
||||||
|
.sortedByDescending { it.length }.find { cropped.endsWith("/$it") }
|
||||||
|
return if (endsWith != null) cropped.substring(
|
||||||
|
0,
|
||||||
|
cropped.length - endsWith.length - 1
|
||||||
|
) else cropped
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun normalizeAddress(address: String): String? {
|
||||||
|
val uri = try {
|
||||||
|
val uri = URI(address)
|
||||||
|
if (uri.isAbsolute) uri.normalize() else null
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
val path = uri?.path?.pathCropped
|
||||||
|
return if (uri != null && path != null) {
|
||||||
|
try {
|
||||||
|
URI(
|
||||||
|
uri.scheme,
|
||||||
|
uri.userInfo,
|
||||||
|
uri.host,
|
||||||
|
uri.port,
|
||||||
|
path,
|
||||||
|
uri.query,
|
||||||
|
uri.fragment
|
||||||
|
).toString()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setMirror(address: String) {
|
||||||
|
layout?.address?.setText(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun EditText.setError(error: Boolean) {
|
||||||
|
val drawable = background.mutate()
|
||||||
|
drawable.colorFilter = if (error) errorColorFilter else null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onSaveRepositoryClick(check: Boolean) {
|
||||||
|
if (checkDisposable == null) {
|
||||||
|
val layout = layout!!
|
||||||
|
val address = normalizeAddress(layout.address.text.toString())!!
|
||||||
|
val fingerprint = layout.fingerprint.text.toString().replace(" ", "")
|
||||||
|
val username = layout.username.text.toString().nullIfEmpty()
|
||||||
|
val password = layout.password.text.toString().nullIfEmpty()
|
||||||
|
val paths = sequenceOf("", "fdroid/repo", "repo")
|
||||||
|
val authentication = username?.let { u ->
|
||||||
|
password
|
||||||
|
?.let { p ->
|
||||||
|
Base64.encodeToString(
|
||||||
|
"$u:$p".toByteArray(Charset.defaultCharset()),
|
||||||
|
Base64.NO_WRAP
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?.let { "Basic $it" }.orEmpty()
|
||||||
|
|
||||||
|
if (check) {
|
||||||
|
checkDisposable = paths
|
||||||
|
.fold(Single.just("")) { oldAddressSingle, checkPath ->
|
||||||
|
oldAddressSingle
|
||||||
|
.flatMap { oldAddress ->
|
||||||
|
if (oldAddress.isEmpty()) {
|
||||||
|
val builder = Uri.parse(address).buildUpon()
|
||||||
|
.let {
|
||||||
|
if (checkPath.isEmpty()) it else it.appendEncodedPath(
|
||||||
|
checkPath
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val newAddress = builder.build()
|
||||||
|
val indexAddress = builder.appendPath("index.jar").build()
|
||||||
|
RxUtils
|
||||||
|
.callSingle {
|
||||||
|
Downloader
|
||||||
|
.createCall(
|
||||||
|
Request.Builder().method("HEAD", null)
|
||||||
|
.url(indexAddress.toString().toHttpUrl()),
|
||||||
|
authentication,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.map { if (it.code == 200) newAddress.toString() else "" }
|
||||||
|
} else {
|
||||||
|
Single.just(oldAddress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe { result, throwable ->
|
||||||
|
checkDisposable = null
|
||||||
|
throwable?.printStackTrace()
|
||||||
|
val resultAddress =
|
||||||
|
result?.let { if (it.isEmpty()) null else it } ?: address
|
||||||
|
val allow = resultAddress == address || run {
|
||||||
|
layout.address.setText(resultAddress)
|
||||||
|
invalidateAddress(resultAddress)
|
||||||
|
!addressError
|
||||||
|
}
|
||||||
|
if (allow) {
|
||||||
|
onSaveRepositoryProceedInvalidate(
|
||||||
|
resultAddress,
|
||||||
|
fingerprint,
|
||||||
|
authentication
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
invalidateState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
invalidateState()
|
||||||
|
} else {
|
||||||
|
onSaveRepositoryProceedInvalidate(address, fingerprint, authentication)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onSaveRepositoryProceedInvalidate(
|
||||||
|
address: String,
|
||||||
|
fingerprint: String,
|
||||||
|
authentication: String
|
||||||
|
) {
|
||||||
|
val binder = syncConnection.binder
|
||||||
|
if (binder != null) {
|
||||||
|
val repositoryId = repositoryId
|
||||||
|
if (repositoryId != null && binder.isCurrentlySyncing(repositoryId)) {
|
||||||
|
MessageDialog(MessageDialog.Message.CantEditSyncing).show(childFragmentManager)
|
||||||
|
invalidateState()
|
||||||
|
} else {
|
||||||
|
val repository = repositoryId?.let(Database.RepositoryAdapter::get)
|
||||||
|
?.edit(address, fingerprint, authentication)
|
||||||
|
?: Repository.newRepository(address, fingerprint, authentication)
|
||||||
|
val changedRepository = Database.RepositoryAdapter.put(repository)
|
||||||
|
if (repositoryId == null && changedRepository.enabled) {
|
||||||
|
binder.sync(changedRepository)
|
||||||
|
}
|
||||||
|
requireActivity().onBackPressed()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
invalidateState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SimpleTextWatcher(private val callback: (Editable) -> Unit) : TextWatcher {
|
||||||
|
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit
|
||||||
|
override fun onTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit
|
||||||
|
override fun afterTextChanged(s: Editable) = callback(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
class SelectMirrorDialog() : DialogFragment() {
|
||||||
|
companion object {
|
||||||
|
private const val EXTRA_MIRRORS = "mirrors"
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(mirrors: List<String>) : this() {
|
||||||
|
arguments = Bundle().apply {
|
||||||
|
putStringArrayList(EXTRA_MIRRORS, ArrayList(mirrors))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): AlertDialog {
|
||||||
|
val mirrors = requireArguments().getStringArrayList(EXTRA_MIRRORS)!!
|
||||||
|
return AlertDialog.Builder(requireContext())
|
||||||
|
.setTitle(R.string.select_mirror)
|
||||||
|
.setItems(mirrors.toTypedArray()) { _, position ->
|
||||||
|
(parentFragment as EditRepositoryFragment)
|
||||||
|
.setMirror(mirrors[position])
|
||||||
|
}
|
||||||
|
.setNegativeButton(R.string.cancel, null)
|
||||||
|
.create()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -8,54 +8,59 @@ import androidx.recyclerview.widget.RecyclerView
|
|||||||
import com.looker.droidify.R
|
import com.looker.droidify.R
|
||||||
import com.looker.droidify.database.Database
|
import com.looker.droidify.database.Database
|
||||||
import com.looker.droidify.entity.Repository
|
import com.looker.droidify.entity.Repository
|
||||||
import com.looker.droidify.utility.extension.resources.*
|
import com.looker.droidify.utility.extension.resources.inflate
|
||||||
import com.looker.droidify.widget.CursorRecyclerAdapter
|
import com.looker.droidify.widget.CursorRecyclerAdapter
|
||||||
|
|
||||||
class RepositoriesAdapter(private val onClick: (Repository) -> Unit,
|
class RepositoriesAdapter(
|
||||||
private val onSwitch: (repository: Repository, isEnabled: Boolean) -> Boolean):
|
private val onClick: (Repository) -> Unit,
|
||||||
CursorRecyclerAdapter<RepositoriesAdapter.ViewType, RecyclerView.ViewHolder>() {
|
private val onSwitch: (repository: Repository, isEnabled: Boolean) -> Boolean
|
||||||
enum class ViewType { REPOSITORY }
|
) :
|
||||||
|
CursorRecyclerAdapter<RepositoriesAdapter.ViewType, RecyclerView.ViewHolder>() {
|
||||||
|
enum class ViewType { REPOSITORY }
|
||||||
|
|
||||||
private class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
|
private class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||||
val name = itemView.findViewById<TextView>(R.id.name)!!
|
val name = itemView.findViewById<TextView>(R.id.name)!!
|
||||||
val enabled = itemView.findViewById<Switch>(R.id.enabled)!!
|
val enabled = itemView.findViewById<Switch>(R.id.enabled)!!
|
||||||
|
|
||||||
var listenSwitch = true
|
var listenSwitch = true
|
||||||
}
|
|
||||||
|
|
||||||
override val viewTypeClass: Class<ViewType>
|
|
||||||
get() = ViewType::class.java
|
|
||||||
|
|
||||||
override fun getItemEnumViewType(position: Int): ViewType {
|
|
||||||
return ViewType.REPOSITORY
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getRepository(position: Int): Repository {
|
|
||||||
return Database.RepositoryAdapter.transform(moveTo(position))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: ViewType): RecyclerView.ViewHolder {
|
|
||||||
return ViewHolder(parent.inflate(R.layout.repository_item)).apply {
|
|
||||||
itemView.setOnClickListener { onClick(getRepository(adapterPosition)) }
|
|
||||||
enabled.setOnCheckedChangeListener { _, isChecked ->
|
|
||||||
if (listenSwitch) {
|
|
||||||
if (!onSwitch(getRepository(adapterPosition), isChecked)) {
|
|
||||||
listenSwitch = false
|
|
||||||
enabled.isChecked = !isChecked
|
|
||||||
listenSwitch = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
override val viewTypeClass: Class<ViewType>
|
||||||
holder as ViewHolder
|
get() = ViewType::class.java
|
||||||
val repository = getRepository(position)
|
|
||||||
val lastListenSwitch = holder.listenSwitch
|
override fun getItemEnumViewType(position: Int): ViewType {
|
||||||
holder.listenSwitch = false
|
return ViewType.REPOSITORY
|
||||||
holder.enabled.isChecked = repository.enabled
|
}
|
||||||
holder.listenSwitch = lastListenSwitch
|
|
||||||
holder.name.text = repository.name
|
private fun getRepository(position: Int): Repository {
|
||||||
}
|
return Database.RepositoryAdapter.transform(moveTo(position))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(
|
||||||
|
parent: ViewGroup,
|
||||||
|
viewType: ViewType
|
||||||
|
): RecyclerView.ViewHolder {
|
||||||
|
return ViewHolder(parent.inflate(R.layout.repository_item)).apply {
|
||||||
|
itemView.setOnClickListener { onClick(getRepository(adapterPosition)) }
|
||||||
|
enabled.setOnCheckedChangeListener { _, isChecked ->
|
||||||
|
if (listenSwitch) {
|
||||||
|
if (!onSwitch(getRepository(adapterPosition), isChecked)) {
|
||||||
|
listenSwitch = false
|
||||||
|
enabled.isChecked = !isChecked
|
||||||
|
listenSwitch = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
|
holder as ViewHolder
|
||||||
|
val repository = getRepository(position)
|
||||||
|
val lastListenSwitch = holder.listenSwitch
|
||||||
|
holder.listenSwitch = false
|
||||||
|
holder.enabled.isChecked = repository.enabled
|
||||||
|
holder.listenSwitch = lastListenSwitch
|
||||||
|
holder.name.text = repository.name
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,58 +16,64 @@ import com.looker.droidify.service.Connection
|
|||||||
import com.looker.droidify.service.SyncService
|
import com.looker.droidify.service.SyncService
|
||||||
import com.looker.droidify.utility.Utils
|
import com.looker.droidify.utility.Utils
|
||||||
|
|
||||||
class RepositoriesFragment: ScreenFragment(), CursorOwner.Callback {
|
class RepositoriesFragment : ScreenFragment(), CursorOwner.Callback {
|
||||||
private var recyclerView: RecyclerView? = null
|
private var recyclerView: RecyclerView? = null
|
||||||
|
|
||||||
private val syncConnection = Connection(SyncService::class.java)
|
private val syncConnection = Connection(SyncService::class.java)
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun onCreateView(
|
||||||
return inflater.inflate(R.layout.fragment, container, false).apply {
|
inflater: LayoutInflater,
|
||||||
val content = findViewById<FrameLayout>(R.id.fragment_content)!!
|
container: ViewGroup?,
|
||||||
content.addView(RecyclerView(content.context).apply {
|
savedInstanceState: Bundle?
|
||||||
id = android.R.id.list
|
): View {
|
||||||
layoutManager = LinearLayoutManager(context)
|
return inflater.inflate(R.layout.fragment, container, false).apply {
|
||||||
isMotionEventSplittingEnabled = false
|
val content = findViewById<FrameLayout>(R.id.fragment_content)!!
|
||||||
setHasFixedSize(true)
|
content.addView(RecyclerView(content.context).apply {
|
||||||
adapter = RepositoriesAdapter({ screenActivity.navigateRepository(it.id) },
|
id = android.R.id.list
|
||||||
{ repository, isEnabled -> repository.enabled != isEnabled &&
|
layoutManager = LinearLayoutManager(context)
|
||||||
syncConnection.binder?.setEnabled(repository, isEnabled) == true })
|
isMotionEventSplittingEnabled = false
|
||||||
recyclerView = this
|
setHasFixedSize(true)
|
||||||
}, FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)
|
adapter = RepositoriesAdapter({ screenActivity.navigateRepository(it.id) },
|
||||||
}
|
{ repository, isEnabled ->
|
||||||
}
|
repository.enabled != isEnabled &&
|
||||||
|
syncConnection.binder?.setEnabled(repository, isEnabled) == true
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
})
|
||||||
super.onViewCreated(view, savedInstanceState)
|
recyclerView = this
|
||||||
|
}, FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)
|
||||||
syncConnection.bind(requireContext())
|
|
||||||
screenActivity.cursorOwner.attach(this, CursorOwner.Request.Repositories)
|
|
||||||
|
|
||||||
val toolbar = view.findViewById<Toolbar>(R.id.toolbar)!!
|
|
||||||
screenActivity.onToolbarCreated(toolbar)
|
|
||||||
toolbar.setTitle(R.string.repositories)
|
|
||||||
|
|
||||||
toolbar.menu.apply {
|
|
||||||
add(R.string.add_repository)
|
|
||||||
.setIcon(Utils.getToolbarIcon(toolbar.context, R.drawable.ic_add))
|
|
||||||
.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_ALWAYS)
|
|
||||||
.setOnMenuItemClickListener {
|
|
||||||
view.post { screenActivity.navigateAddRepository() }
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onDestroyView()
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
recyclerView = null
|
syncConnection.bind(requireContext())
|
||||||
|
screenActivity.cursorOwner.attach(this, CursorOwner.Request.Repositories)
|
||||||
|
|
||||||
syncConnection.unbind(requireContext())
|
val toolbar = view.findViewById<Toolbar>(R.id.toolbar)!!
|
||||||
screenActivity.cursorOwner.detach(this)
|
screenActivity.onToolbarCreated(toolbar)
|
||||||
}
|
toolbar.setTitle(R.string.repositories)
|
||||||
|
|
||||||
override fun onCursorData(request: CursorOwner.Request, cursor: Cursor?) {
|
toolbar.menu.apply {
|
||||||
(recyclerView?.adapter as? RepositoriesAdapter)?.cursor = cursor
|
add(R.string.add_repository)
|
||||||
}
|
.setIcon(Utils.getToolbarIcon(toolbar.context, R.drawable.ic_add))
|
||||||
|
.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_ALWAYS)
|
||||||
|
.setOnMenuItemClickListener {
|
||||||
|
view.post { screenActivity.navigateAddRepository() }
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
|
||||||
|
recyclerView = null
|
||||||
|
|
||||||
|
syncConnection.unbind(requireContext())
|
||||||
|
screenActivity.cursorOwner.detach(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCursorData(request: CursorOwner.Request, cursor: Cursor?) {
|
||||||
|
(recyclerView?.adapter as? RepositoriesAdapter)?.cursor = cursor
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,158 +9,181 @@ import android.view.LayoutInflater
|
|||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.FrameLayout
|
import android.widget.*
|
||||||
import android.widget.LinearLayout
|
|
||||||
import android.widget.ScrollView
|
|
||||||
import android.widget.TextView
|
|
||||||
import android.widget.Toolbar
|
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
|
||||||
import io.reactivex.rxjava3.core.Observable
|
|
||||||
import io.reactivex.rxjava3.disposables.Disposable
|
|
||||||
import com.looker.droidify.R
|
import com.looker.droidify.R
|
||||||
import com.looker.droidify.database.Database
|
import com.looker.droidify.database.Database
|
||||||
import com.looker.droidify.service.Connection
|
import com.looker.droidify.service.Connection
|
||||||
import com.looker.droidify.service.SyncService
|
import com.looker.droidify.service.SyncService
|
||||||
import com.looker.droidify.utility.Utils
|
import com.looker.droidify.utility.Utils
|
||||||
import com.looker.droidify.utility.extension.resources.*
|
import com.looker.droidify.utility.extension.resources.getColorFromAttr
|
||||||
import java.util.Date
|
import com.looker.droidify.utility.extension.resources.inflate
|
||||||
import java.util.Locale
|
import com.looker.droidify.utility.extension.resources.sizeScaled
|
||||||
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
|
import io.reactivex.rxjava3.core.Observable
|
||||||
|
import io.reactivex.rxjava3.disposables.Disposable
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
class RepositoryFragment(): ScreenFragment() {
|
class RepositoryFragment() : ScreenFragment() {
|
||||||
companion object {
|
companion object {
|
||||||
private const val EXTRA_REPOSITORY_ID = "repositoryId"
|
private const val EXTRA_REPOSITORY_ID = "repositoryId"
|
||||||
}
|
|
||||||
|
|
||||||
constructor(repositoryId: Long): this() {
|
|
||||||
arguments = Bundle().apply {
|
|
||||||
putLong(EXTRA_REPOSITORY_ID, repositoryId)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private val repositoryId: Long
|
constructor(repositoryId: Long) : this() {
|
||||||
get() = requireArguments().getLong(EXTRA_REPOSITORY_ID)
|
arguments = Bundle().apply {
|
||||||
|
putLong(EXTRA_REPOSITORY_ID, repositoryId)
|
||||||
private var layout: LinearLayout? = null
|
|
||||||
|
|
||||||
private val syncConnection = Connection(SyncService::class.java)
|
|
||||||
private var repositoryDisposable: Disposable? = null
|
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
|
||||||
return inflater.inflate(R.layout.fragment, container, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
|
|
||||||
syncConnection.bind(requireContext())
|
|
||||||
repositoryDisposable = Observable.just(Unit)
|
|
||||||
.concatWith(Database.observable(Database.Subject.Repository(repositoryId)))
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe { updateRepositoryView() }
|
|
||||||
|
|
||||||
val toolbar = view.findViewById<Toolbar>(R.id.toolbar)!!
|
|
||||||
screenActivity.onToolbarCreated(toolbar)
|
|
||||||
toolbar.setTitle(R.string.repository)
|
|
||||||
|
|
||||||
toolbar.menu.apply {
|
|
||||||
add(R.string.edit_repository)
|
|
||||||
.setIcon(Utils.getToolbarIcon(toolbar.context, R.drawable.ic_edit))
|
|
||||||
.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_ALWAYS)
|
|
||||||
.setOnMenuItemClickListener {
|
|
||||||
view.post { screenActivity.navigateEditRepository(repositoryId) }
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
add(R.string.delete)
|
|
||||||
.setIcon(Utils.getToolbarIcon(toolbar.context, R.drawable.ic_delete))
|
|
||||||
.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_ALWAYS)
|
|
||||||
.setOnMenuItemClickListener {
|
|
||||||
MessageDialog(MessageDialog.Message.DeleteRepositoryConfirm).show(childFragmentManager)
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val content = view.findViewById<FrameLayout>(R.id.fragment_content)!!
|
private val repositoryId: Long
|
||||||
val scroll = ScrollView(content.context)
|
get() = requireArguments().getLong(EXTRA_REPOSITORY_ID)
|
||||||
scroll.id = android.R.id.list
|
|
||||||
scroll.isFillViewport = true
|
|
||||||
content.addView(scroll, FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)
|
|
||||||
val layout = LinearLayout(scroll.context)
|
|
||||||
layout.orientation = LinearLayout.VERTICAL
|
|
||||||
resources.sizeScaled(8).let { layout.setPadding(0, it, 0, it) }
|
|
||||||
this.layout = layout
|
|
||||||
scroll.addView(layout, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroyView() {
|
private var layout: LinearLayout? = null
|
||||||
super.onDestroyView()
|
|
||||||
|
|
||||||
layout = null
|
private val syncConnection = Connection(SyncService::class.java)
|
||||||
syncConnection.unbind(requireContext())
|
private var repositoryDisposable: Disposable? = null
|
||||||
repositoryDisposable?.dispose()
|
|
||||||
repositoryDisposable = null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateRepositoryView() {
|
override fun onCreateView(
|
||||||
val repository = Database.RepositoryAdapter.get(repositoryId)
|
inflater: LayoutInflater,
|
||||||
val layout = layout!!
|
container: ViewGroup?,
|
||||||
layout.removeAllViews()
|
savedInstanceState: Bundle?
|
||||||
if (repository == null) {
|
): View {
|
||||||
layout.addTitleText(R.string.address, getString(R.string.unknown))
|
return inflater.inflate(R.layout.fragment, container, false)
|
||||||
} else {
|
}
|
||||||
layout.addTitleText(R.string.address, repository.address)
|
|
||||||
if (repository.updated > 0L) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
layout.addTitleText(R.string.name, repository.name)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
layout.addTitleText(R.string.description, repository.description.replace('\n', ' '))
|
|
||||||
layout.addTitleText(R.string.last_update, run {
|
syncConnection.bind(requireContext())
|
||||||
val lastUpdated = repository.updated
|
repositoryDisposable = Observable.just(Unit)
|
||||||
if (lastUpdated > 0L) {
|
.concatWith(Database.observable(Database.Subject.Repository(repositoryId)))
|
||||||
val date = Date(repository.updated)
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
val format = if (DateUtils.isToday(date.time)) DateUtils.FORMAT_SHOW_TIME else
|
.subscribe { updateRepositoryView() }
|
||||||
DateUtils.FORMAT_SHOW_TIME or DateUtils.FORMAT_SHOW_DATE
|
|
||||||
DateUtils.formatDateTime(layout.context, date.time, format)
|
val toolbar = view.findViewById<Toolbar>(R.id.toolbar)!!
|
||||||
} else {
|
screenActivity.onToolbarCreated(toolbar)
|
||||||
getString(R.string.unknown)
|
toolbar.setTitle(R.string.repository)
|
||||||
}
|
|
||||||
})
|
toolbar.menu.apply {
|
||||||
if (repository.enabled && (repository.lastModified.isNotEmpty() || repository.entityTag.isNotEmpty())) {
|
add(R.string.edit_repository)
|
||||||
layout.addTitleText(R.string.number_of_applications,
|
.setIcon(Utils.getToolbarIcon(toolbar.context, R.drawable.ic_edit))
|
||||||
Database.ProductAdapter.getCount(repository.id).toString())
|
.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_ALWAYS)
|
||||||
|
.setOnMenuItemClickListener {
|
||||||
|
view.post { screenActivity.navigateEditRepository(repositoryId) }
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
add(R.string.delete)
|
||||||
|
.setIcon(Utils.getToolbarIcon(toolbar.context, R.drawable.ic_delete))
|
||||||
|
.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_ALWAYS)
|
||||||
|
.setOnMenuItemClickListener {
|
||||||
|
MessageDialog(MessageDialog.Message.DeleteRepositoryConfirm).show(
|
||||||
|
childFragmentManager
|
||||||
|
)
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
layout.addTitleText(R.string.description, getString(R.string.repository_not_used_DESC))
|
val content = view.findViewById<FrameLayout>(R.id.fragment_content)!!
|
||||||
}
|
val scroll = ScrollView(content.context)
|
||||||
if (repository.fingerprint.isEmpty()) {
|
scroll.id = android.R.id.list
|
||||||
if (repository.updated > 0L) {
|
scroll.isFillViewport = true
|
||||||
val builder = SpannableStringBuilder(getString(R.string.repository_unsigned_DESC))
|
content.addView(
|
||||||
builder.setSpan(ForegroundColorSpan(layout.context.getColorFromAttr(R.attr.colorError).defaultColor),
|
scroll,
|
||||||
0, builder.length, SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE)
|
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||||
layout.addTitleText(R.string.fingerprint, builder)
|
FrameLayout.LayoutParams.MATCH_PARENT
|
||||||
|
)
|
||||||
|
val layout = LinearLayout(scroll.context)
|
||||||
|
layout.orientation = LinearLayout.VERTICAL
|
||||||
|
resources.sizeScaled(8).let { layout.setPadding(0, it, 0, it) }
|
||||||
|
this.layout = layout
|
||||||
|
scroll.addView(
|
||||||
|
layout,
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
|
||||||
|
layout = null
|
||||||
|
syncConnection.unbind(requireContext())
|
||||||
|
repositoryDisposable?.dispose()
|
||||||
|
repositoryDisposable = null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateRepositoryView() {
|
||||||
|
val repository = Database.RepositoryAdapter.get(repositoryId)
|
||||||
|
val layout = layout!!
|
||||||
|
layout.removeAllViews()
|
||||||
|
if (repository == null) {
|
||||||
|
layout.addTitleText(R.string.address, getString(R.string.unknown))
|
||||||
|
} else {
|
||||||
|
layout.addTitleText(R.string.address, repository.address)
|
||||||
|
if (repository.updated > 0L) {
|
||||||
|
layout.addTitleText(R.string.name, repository.name)
|
||||||
|
layout.addTitleText(R.string.description, repository.description.replace('\n', ' '))
|
||||||
|
layout.addTitleText(R.string.last_update, run {
|
||||||
|
val lastUpdated = repository.updated
|
||||||
|
if (lastUpdated > 0L) {
|
||||||
|
val date = Date(repository.updated)
|
||||||
|
val format =
|
||||||
|
if (DateUtils.isToday(date.time)) DateUtils.FORMAT_SHOW_TIME else
|
||||||
|
DateUtils.FORMAT_SHOW_TIME or DateUtils.FORMAT_SHOW_DATE
|
||||||
|
DateUtils.formatDateTime(layout.context, date.time, format)
|
||||||
|
} else {
|
||||||
|
getString(R.string.unknown)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (repository.enabled && (repository.lastModified.isNotEmpty() || repository.entityTag.isNotEmpty())) {
|
||||||
|
layout.addTitleText(
|
||||||
|
R.string.number_of_applications,
|
||||||
|
Database.ProductAdapter.getCount(repository.id).toString()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
layout.addTitleText(
|
||||||
|
R.string.description,
|
||||||
|
getString(R.string.repository_not_used_DESC)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (repository.fingerprint.isEmpty()) {
|
||||||
|
if (repository.updated > 0L) {
|
||||||
|
val builder =
|
||||||
|
SpannableStringBuilder(getString(R.string.repository_unsigned_DESC))
|
||||||
|
builder.setSpan(
|
||||||
|
ForegroundColorSpan(layout.context.getColorFromAttr(R.attr.colorError).defaultColor),
|
||||||
|
0, builder.length, SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||||
|
)
|
||||||
|
layout.addTitleText(R.string.fingerprint, builder)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val fingerprint =
|
||||||
|
SpannableStringBuilder(repository.fingerprint.windowed(2, 2, false)
|
||||||
|
.take(32).joinToString(separator = " ") { it.uppercase(Locale.US) })
|
||||||
|
fingerprint.setSpan(
|
||||||
|
TypefaceSpan("monospace"), 0, fingerprint.length,
|
||||||
|
SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||||
|
)
|
||||||
|
layout.addTitleText(R.string.fingerprint, fingerprint)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
val fingerprint = SpannableStringBuilder(repository.fingerprint.windowed(2, 2, false)
|
|
||||||
.take(32).joinToString(separator = " ") { it.toUpperCase(Locale.US) })
|
|
||||||
fingerprint.setSpan(TypefaceSpan("monospace"), 0, fingerprint.length,
|
|
||||||
SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
||||||
layout.addTitleText(R.string.fingerprint, fingerprint)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun LinearLayout.addTitleText(titleResId: Int, text: CharSequence) {
|
private fun LinearLayout.addTitleText(titleResId: Int, text: CharSequence) {
|
||||||
if (text.isNotEmpty()) {
|
if (text.isNotEmpty()) {
|
||||||
val layout = inflate(R.layout.title_text_item)
|
val layout = inflate(R.layout.title_text_item)
|
||||||
val titleView = layout.findViewById<TextView>(R.id.title)!!
|
val titleView = layout.findViewById<TextView>(R.id.title)!!
|
||||||
titleView.setText(titleResId)
|
titleView.setText(titleResId)
|
||||||
val textView = layout.findViewById<TextView>(R.id.text)!!
|
val textView = layout.findViewById<TextView>(R.id.text)!!
|
||||||
textView.text = text
|
textView.text = text
|
||||||
addView(layout)
|
addView(layout)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
internal fun onDeleteConfirm() {
|
internal fun onDeleteConfirm() {
|
||||||
if (syncConnection.binder?.deleteRepository(repositoryId) == true) {
|
if (syncConnection.binder?.deleteRepository(repositoryId) == true) {
|
||||||
requireActivity().onBackPressed()
|
requireActivity().onBackPressed()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -18,233 +18,261 @@ import com.looker.droidify.content.Preferences
|
|||||||
import com.looker.droidify.database.CursorOwner
|
import com.looker.droidify.database.CursorOwner
|
||||||
import com.looker.droidify.utility.KParcelable
|
import com.looker.droidify.utility.KParcelable
|
||||||
import com.looker.droidify.utility.Utils
|
import com.looker.droidify.utility.Utils
|
||||||
import com.looker.droidify.utility.extension.android.*
|
import com.looker.droidify.utility.extension.android.Android
|
||||||
import com.looker.droidify.utility.extension.resources.*
|
import com.looker.droidify.utility.extension.resources.getDrawableFromAttr
|
||||||
import com.looker.droidify.utility.extension.text.*
|
import com.looker.droidify.utility.extension.text.nullIfEmpty
|
||||||
|
|
||||||
abstract class ScreenActivity: FragmentActivity() {
|
|
||||||
companion object {
|
|
||||||
private const val STATE_FRAGMENT_STACK = "fragmentStack"
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class SpecialIntent {
|
|
||||||
object Updates: SpecialIntent()
|
|
||||||
class Install(val packageName: String?, val cacheFileName: String?): SpecialIntent()
|
|
||||||
}
|
|
||||||
|
|
||||||
private class FragmentStackItem(val className: String, val arguments: Bundle?,
|
|
||||||
val savedState: Fragment.SavedState?): KParcelable {
|
|
||||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
|
||||||
dest.writeString(className)
|
|
||||||
dest.writeByte(if (arguments != null) 1 else 0)
|
|
||||||
arguments?.writeToParcel(dest, flags)
|
|
||||||
dest.writeByte(if (savedState != null) 1 else 0)
|
|
||||||
savedState?.writeToParcel(dest, flags)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
abstract class ScreenActivity : FragmentActivity() {
|
||||||
companion object {
|
companion object {
|
||||||
@Suppress("unused") @JvmField val CREATOR = KParcelable.creator {
|
private const val STATE_FRAGMENT_STACK = "fragmentStack"
|
||||||
val className = it.readString()!!
|
|
||||||
val arguments = if (it.readByte().toInt() == 0) null else Bundle.CREATOR.createFromParcel(it)
|
|
||||||
arguments?.classLoader = ScreenActivity::class.java.classLoader
|
|
||||||
val savedState = if (it.readByte().toInt() == 0) null else Fragment.SavedState.CREATOR.createFromParcel(it)
|
|
||||||
FragmentStackItem(className, arguments, savedState)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lateinit var cursorOwner: CursorOwner
|
|
||||||
private set
|
|
||||||
|
|
||||||
private val fragmentStack = mutableListOf<FragmentStackItem>()
|
|
||||||
|
|
||||||
private val currentFragment: Fragment?
|
|
||||||
get() {
|
|
||||||
supportFragmentManager.executePendingTransactions()
|
|
||||||
return supportFragmentManager.findFragmentById(R.id.main_content)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun attachBaseContext(base: Context) {
|
sealed class SpecialIntent {
|
||||||
super.attachBaseContext(Utils.configureLocale(base))
|
object Updates : SpecialIntent()
|
||||||
}
|
class Install(val packageName: String?, val cacheFileName: String?) : SpecialIntent()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
setTheme(Preferences[Preferences.Key.Theme].getResId(resources.configuration))
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
|
|
||||||
window.decorView.systemUiVisibility = window.decorView.systemUiVisibility or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
|
|
||||||
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
|
||||||
addContentView(FrameLayout(this).apply { id = R.id.main_content },
|
|
||||||
ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))
|
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
|
||||||
cursorOwner = CursorOwner()
|
|
||||||
supportFragmentManager.beginTransaction()
|
|
||||||
.add(cursorOwner, CursorOwner::class.java.name)
|
|
||||||
.commit()
|
|
||||||
} else {
|
|
||||||
cursorOwner = supportFragmentManager
|
|
||||||
.findFragmentByTag(CursorOwner::class.java.name) as CursorOwner
|
|
||||||
}
|
}
|
||||||
|
|
||||||
savedInstanceState?.getParcelableArrayList<FragmentStackItem>(STATE_FRAGMENT_STACK)
|
private class FragmentStackItem(
|
||||||
?.let { fragmentStack += it }
|
val className: String, val arguments: Bundle?,
|
||||||
if (savedInstanceState == null) {
|
val savedState: Fragment.SavedState?
|
||||||
replaceFragment(TabsFragment(), null)
|
) : KParcelable {
|
||||||
if ((intent.flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0) {
|
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||||
handleIntent(intent)
|
dest.writeString(className)
|
||||||
}
|
dest.writeByte(if (arguments != null) 1 else 0)
|
||||||
}
|
arguments?.writeToParcel(dest, flags)
|
||||||
}
|
dest.writeByte(if (savedState != null) 1 else 0)
|
||||||
|
savedState?.writeToParcel(dest, flags)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
companion object {
|
||||||
super.onSaveInstanceState(outState)
|
@Suppress("unused")
|
||||||
outState.putParcelableArrayList(STATE_FRAGMENT_STACK, ArrayList(fragmentStack))
|
@JvmField
|
||||||
}
|
val CREATOR = KParcelable.creator {
|
||||||
|
val className = it.readString()!!
|
||||||
override fun onBackPressed() {
|
val arguments =
|
||||||
val currentFragment = currentFragment
|
if (it.readByte().toInt() == 0) null else Bundle.CREATOR.createFromParcel(it)
|
||||||
if (!(currentFragment is ScreenFragment && currentFragment.onBackPressed())) {
|
arguments?.classLoader = ScreenActivity::class.java.classLoader
|
||||||
hideKeyboard()
|
val savedState = if (it.readByte()
|
||||||
if (!popFragment()) {
|
.toInt() == 0
|
||||||
super.onBackPressed()
|
) null else Fragment.SavedState.CREATOR.createFromParcel(it)
|
||||||
}
|
FragmentStackItem(className, arguments, savedState)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun replaceFragment(fragment: Fragment, open: Boolean?) {
|
lateinit var cursorOwner: CursorOwner
|
||||||
if (open != null) {
|
private set
|
||||||
currentFragment?.view?.translationZ = (if (open) Int.MIN_VALUE else Int.MAX_VALUE).toFloat()
|
|
||||||
|
private val fragmentStack = mutableListOf<FragmentStackItem>()
|
||||||
|
|
||||||
|
private val currentFragment: Fragment?
|
||||||
|
get() {
|
||||||
|
supportFragmentManager.executePendingTransactions()
|
||||||
|
return supportFragmentManager.findFragmentById(R.id.main_content)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun attachBaseContext(base: Context) {
|
||||||
|
super.attachBaseContext(Utils.configureLocale(base))
|
||||||
}
|
}
|
||||||
supportFragmentManager
|
|
||||||
.beginTransaction()
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
.apply {
|
setTheme(Preferences[Preferences.Key.Theme].getResId(resources.configuration))
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
window.decorView.systemUiVisibility =
|
||||||
|
window.decorView.systemUiVisibility or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
|
||||||
|
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||||
|
addContentView(
|
||||||
|
FrameLayout(this).apply { id = R.id.main_content },
|
||||||
|
ViewGroup.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
cursorOwner = CursorOwner()
|
||||||
|
supportFragmentManager.beginTransaction()
|
||||||
|
.add(cursorOwner, CursorOwner::class.java.name)
|
||||||
|
.commit()
|
||||||
|
} else {
|
||||||
|
cursorOwner = supportFragmentManager
|
||||||
|
.findFragmentByTag(CursorOwner::class.java.name) as CursorOwner
|
||||||
|
}
|
||||||
|
|
||||||
|
savedInstanceState?.getParcelableArrayList<FragmentStackItem>(STATE_FRAGMENT_STACK)
|
||||||
|
?.let { fragmentStack += it }
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
replaceFragment(TabsFragment(), null)
|
||||||
|
if ((intent.flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0) {
|
||||||
|
handleIntent(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
outState.putParcelableArrayList(STATE_FRAGMENT_STACK, ArrayList(fragmentStack))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBackPressed() {
|
||||||
|
val currentFragment = currentFragment
|
||||||
|
if (!(currentFragment is ScreenFragment && currentFragment.onBackPressed())) {
|
||||||
|
hideKeyboard()
|
||||||
|
if (!popFragment()) {
|
||||||
|
super.onBackPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun replaceFragment(fragment: Fragment, open: Boolean?) {
|
||||||
if (open != null) {
|
if (open != null) {
|
||||||
setCustomAnimations(if (open) R.animator.slide_in else 0,
|
currentFragment?.view?.translationZ =
|
||||||
if (open) R.animator.slide_in_keep else R.animator.slide_out)
|
(if (open) Int.MIN_VALUE else Int.MAX_VALUE).toFloat()
|
||||||
}
|
}
|
||||||
}
|
supportFragmentManager
|
||||||
.replace(R.id.main_content, fragment)
|
.beginTransaction()
|
||||||
.commit()
|
.apply {
|
||||||
}
|
if (open != null) {
|
||||||
|
setCustomAnimations(
|
||||||
private fun pushFragment(fragment: Fragment) {
|
if (open) R.animator.slide_in else 0,
|
||||||
currentFragment?.let { fragmentStack.add(FragmentStackItem(it::class.java.name, it.arguments,
|
if (open) R.animator.slide_in_keep else R.animator.slide_out
|
||||||
supportFragmentManager.saveFragmentInstanceState(it))) }
|
)
|
||||||
replaceFragment(fragment, true)
|
}
|
||||||
}
|
}
|
||||||
|
.replace(R.id.main_content, fragment)
|
||||||
private fun popFragment(): Boolean {
|
.commit()
|
||||||
return fragmentStack.isNotEmpty() && run {
|
|
||||||
val stackItem = fragmentStack.removeAt(fragmentStack.size - 1)
|
|
||||||
val fragment = Class.forName(stackItem.className).newInstance() as Fragment
|
|
||||||
stackItem.arguments?.let(fragment::setArguments)
|
|
||||||
stackItem.savedState?.let(fragment::setInitialSavedState)
|
|
||||||
replaceFragment(fragment, false)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun hideKeyboard() {
|
|
||||||
(getSystemService(INPUT_METHOD_SERVICE) as? InputMethodManager)
|
|
||||||
?.hideSoftInputFromWindow((currentFocus ?: window.decorView).windowToken, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAttachFragment(fragment: Fragment) {
|
|
||||||
super.onAttachFragment(fragment)
|
|
||||||
hideKeyboard()
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun onToolbarCreated(toolbar: Toolbar) {
|
|
||||||
if (fragmentStack.isNotEmpty()) {
|
|
||||||
toolbar.navigationIcon = toolbar.context.getDrawableFromAttr(android.R.attr.homeAsUpIndicator)
|
|
||||||
toolbar.setNavigationOnClickListener { onBackPressed() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onNewIntent(intent: Intent?) {
|
|
||||||
super.onNewIntent(intent)
|
|
||||||
handleIntent(intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected val Intent.packageName: String?
|
|
||||||
get() {
|
|
||||||
val uri = data
|
|
||||||
return when {
|
|
||||||
uri?.scheme == "package" || uri?.scheme == "fdroid.app" -> {
|
|
||||||
uri.schemeSpecificPart?.nullIfEmpty()
|
|
||||||
}
|
|
||||||
uri?.scheme == "market" && uri.host == "details" -> {
|
|
||||||
uri.getQueryParameter("id")?.nullIfEmpty()
|
|
||||||
}
|
|
||||||
uri != null && uri.scheme in setOf("http", "https") -> {
|
|
||||||
val host = uri.host.orEmpty()
|
|
||||||
if (host == "f-droid.org" || host.endsWith(".f-droid.org")) {
|
|
||||||
uri.lastPathSegment?.nullIfEmpty()
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun handleSpecialIntent(specialIntent: SpecialIntent) {
|
private fun pushFragment(fragment: Fragment) {
|
||||||
when (specialIntent) {
|
currentFragment?.let {
|
||||||
is SpecialIntent.Updates -> {
|
fragmentStack.add(
|
||||||
if (currentFragment !is TabsFragment) {
|
FragmentStackItem(
|
||||||
fragmentStack.clear()
|
it::class.java.name, it.arguments,
|
||||||
replaceFragment(TabsFragment(), true)
|
supportFragmentManager.saveFragmentInstanceState(it)
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
val tabsFragment = currentFragment as TabsFragment
|
replaceFragment(fragment, true)
|
||||||
tabsFragment.selectUpdates()
|
|
||||||
}
|
|
||||||
is SpecialIntent.Install -> {
|
|
||||||
val packageName = specialIntent.packageName
|
|
||||||
if (!packageName.isNullOrEmpty()) {
|
|
||||||
val fragment = currentFragment
|
|
||||||
if (fragment !is ProductFragment || fragment.packageName != packageName) {
|
|
||||||
pushFragment(ProductFragment(packageName))
|
|
||||||
}
|
|
||||||
specialIntent.cacheFileName?.let(::startPackageInstaller)
|
|
||||||
}
|
|
||||||
Unit
|
|
||||||
}
|
|
||||||
}::class
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun handleIntent(intent: Intent?) {
|
|
||||||
when (intent?.action) {
|
|
||||||
Intent.ACTION_VIEW -> {
|
|
||||||
val packageName = intent.packageName
|
|
||||||
if (!packageName.isNullOrEmpty()) {
|
|
||||||
val fragment = currentFragment
|
|
||||||
if (fragment !is ProductFragment || fragment.packageName != packageName) {
|
|
||||||
pushFragment(ProductFragment(packageName))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
internal fun startPackageInstaller(cacheFileName: String) {
|
private fun popFragment(): Boolean {
|
||||||
val (uri, flags) = if (Android.sdk(24)) {
|
return fragmentStack.isNotEmpty() && run {
|
||||||
Pair(Cache.getReleaseUri(this, cacheFileName), Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
val stackItem = fragmentStack.removeAt(fragmentStack.size - 1)
|
||||||
} else {
|
val fragment = Class.forName(stackItem.className).newInstance() as Fragment
|
||||||
Pair(Uri.fromFile(Cache.getReleaseFile(this, cacheFileName)), 0)
|
stackItem.arguments?.let(fragment::setArguments)
|
||||||
|
stackItem.savedState?.let(fragment::setInitialSavedState)
|
||||||
|
replaceFragment(fragment, false)
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// TODO Handle deprecation
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
startActivity(Intent(Intent.ACTION_INSTALL_PACKAGE)
|
|
||||||
.setDataAndType(uri, "application/vnd.android.package-archive").setFlags(flags))
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun navigateProduct(packageName: String) = pushFragment(ProductFragment(packageName))
|
private fun hideKeyboard() {
|
||||||
internal fun navigateRepositories() = pushFragment(RepositoriesFragment())
|
(getSystemService(INPUT_METHOD_SERVICE) as? InputMethodManager)
|
||||||
internal fun navigatePreferences() = pushFragment(PreferencesFragment())
|
?.hideSoftInputFromWindow((currentFocus ?: window.decorView).windowToken, 0)
|
||||||
internal fun navigateAddRepository() = pushFragment(EditRepositoryFragment(null))
|
}
|
||||||
internal fun navigateRepository(repositoryId: Long) = pushFragment(RepositoryFragment(repositoryId))
|
|
||||||
internal fun navigateEditRepository(repositoryId: Long) = pushFragment(EditRepositoryFragment(repositoryId))
|
override fun onAttachFragment(fragment: Fragment) {
|
||||||
|
super.onAttachFragment(fragment)
|
||||||
|
hideKeyboard()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun onToolbarCreated(toolbar: Toolbar) {
|
||||||
|
if (fragmentStack.isNotEmpty()) {
|
||||||
|
toolbar.navigationIcon =
|
||||||
|
toolbar.context.getDrawableFromAttr(android.R.attr.homeAsUpIndicator)
|
||||||
|
toolbar.setNavigationOnClickListener { onBackPressed() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNewIntent(intent: Intent?) {
|
||||||
|
super.onNewIntent(intent)
|
||||||
|
handleIntent(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected val Intent.packageName: String?
|
||||||
|
get() {
|
||||||
|
val uri = data
|
||||||
|
return when {
|
||||||
|
uri?.scheme == "package" || uri?.scheme == "fdroid.app" -> {
|
||||||
|
uri.schemeSpecificPart?.nullIfEmpty()
|
||||||
|
}
|
||||||
|
uri?.scheme == "market" && uri.host == "details" -> {
|
||||||
|
uri.getQueryParameter("id")?.nullIfEmpty()
|
||||||
|
}
|
||||||
|
uri != null && uri.scheme in setOf("http", "https") -> {
|
||||||
|
val host = uri.host.orEmpty()
|
||||||
|
if (host == "f-droid.org" || host.endsWith(".f-droid.org")) {
|
||||||
|
uri.lastPathSegment?.nullIfEmpty()
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun handleSpecialIntent(specialIntent: SpecialIntent) {
|
||||||
|
when (specialIntent) {
|
||||||
|
is SpecialIntent.Updates -> {
|
||||||
|
if (currentFragment !is TabsFragment) {
|
||||||
|
fragmentStack.clear()
|
||||||
|
replaceFragment(TabsFragment(), true)
|
||||||
|
}
|
||||||
|
val tabsFragment = currentFragment as TabsFragment
|
||||||
|
tabsFragment.selectUpdates()
|
||||||
|
}
|
||||||
|
is SpecialIntent.Install -> {
|
||||||
|
val packageName = specialIntent.packageName
|
||||||
|
if (!packageName.isNullOrEmpty()) {
|
||||||
|
val fragment = currentFragment
|
||||||
|
if (fragment !is ProductFragment || fragment.packageName != packageName) {
|
||||||
|
pushFragment(ProductFragment(packageName))
|
||||||
|
}
|
||||||
|
specialIntent.cacheFileName?.let(::startPackageInstaller)
|
||||||
|
}
|
||||||
|
Unit
|
||||||
|
}
|
||||||
|
}::class
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun handleIntent(intent: Intent?) {
|
||||||
|
when (intent?.action) {
|
||||||
|
Intent.ACTION_VIEW -> {
|
||||||
|
val packageName = intent.packageName
|
||||||
|
if (!packageName.isNullOrEmpty()) {
|
||||||
|
val fragment = currentFragment
|
||||||
|
if (fragment !is ProductFragment || fragment.packageName != packageName) {
|
||||||
|
pushFragment(ProductFragment(packageName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun startPackageInstaller(cacheFileName: String) {
|
||||||
|
val (uri, flags) = if (Android.sdk(24)) {
|
||||||
|
Pair(Cache.getReleaseUri(this, cacheFileName), Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
} else {
|
||||||
|
Pair(Uri.fromFile(Cache.getReleaseFile(this, cacheFileName)), 0)
|
||||||
|
}
|
||||||
|
// TODO Handle deprecation
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
startActivity(
|
||||||
|
Intent(Intent.ACTION_INSTALL_PACKAGE)
|
||||||
|
.setDataAndType(uri, "application/vnd.android.package-archive").setFlags(flags)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun navigateProduct(packageName: String) = pushFragment(ProductFragment(packageName))
|
||||||
|
internal fun navigateRepositories() = pushFragment(RepositoriesFragment())
|
||||||
|
internal fun navigatePreferences() = pushFragment(SettingsFragment())
|
||||||
|
internal fun navigateAddRepository() = pushFragment(EditRepositoryFragment(null))
|
||||||
|
internal fun navigateRepository(repositoryId: Long) =
|
||||||
|
pushFragment(RepositoryFragment(repositoryId))
|
||||||
|
|
||||||
|
internal fun navigateEditRepository(repositoryId: Long) =
|
||||||
|
pushFragment(EditRepositoryFragment(repositoryId))
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user