Add non-SSO login
This commit is contained in:
parent
120f0dea12
commit
369a897255
|
@ -46,6 +46,10 @@ if (project.hasProperty('sign')) {
|
|||
android.buildTypes.release.signingConfig android.signingConfigs.release
|
||||
}
|
||||
|
||||
ext {
|
||||
retrofitVersion = "2.9.0"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version")
|
||||
implementation("androidx.appcompat:appcompat:1.6.0")
|
||||
|
@ -54,7 +58,10 @@ dependencies {
|
|||
implementation("androidx.coordinatorlayout:coordinatorlayout:1.2.0")
|
||||
implementation("com.squareup.okhttp3:okhttp-sse:4.10.0")
|
||||
implementation("com.github.nextcloud:Android-SingleSignOn:0.6.1")
|
||||
implementation("com.squareup.retrofit2:retrofit:2.9.0")
|
||||
implementation("com.squareup.retrofit2:retrofit:$retrofitVersion")
|
||||
implementation("com.squareup.retrofit2:converter-gson:$retrofitVersion")
|
||||
implementation("com.squareup.retrofit2:adapter-rxjava2:$retrofitVersion")
|
||||
implementation("io.reactivex.rxjava2:rxjava:2.2.21")
|
||||
implementation("androidx.work:work-runtime-ktx:2.7.1")
|
||||
implementation("com.google.android.material:material:1.8.0")
|
||||
}
|
||||
|
|
|
@ -6,11 +6,32 @@ import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundExce
|
|||
import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException
|
||||
import org.unifiedpush.distributor.nextpush.utils.TAG
|
||||
|
||||
private const val PREF_NAME = "NextPush"
|
||||
internal const val PREF_NAME = "NextPush"
|
||||
private const val PREF_DEVICE_ID = "deviceId"
|
||||
private const val PREF_ACCOUNT_TYPE = "account::type"
|
||||
|
||||
enum class AccountType {
|
||||
SSO,
|
||||
Direct;
|
||||
fun toInt(): Int {
|
||||
return this.ordinal
|
||||
}
|
||||
}
|
||||
|
||||
private fun Int.toAccountType(): AccountType {
|
||||
return AccountType.values().getOrNull(this) ?: AccountType.SSO
|
||||
}
|
||||
|
||||
object Account {
|
||||
private var account: AccountFactory? = null
|
||||
|
||||
var Context.accountType: AccountType
|
||||
get() = this.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
||||
.getInt(PREF_ACCOUNT_TYPE, 0).toAccountType()
|
||||
private set(value) = this.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
||||
.edit().putInt(PREF_ACCOUNT_TYPE, value.toInt())
|
||||
.apply()
|
||||
|
||||
var Context.deviceId: String?
|
||||
get() = this.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
||||
.getString(PREF_DEVICE_ID, null)
|
||||
|
@ -29,25 +50,48 @@ object Account {
|
|||
fun getAccount(context: Context, uninitialized: Boolean = false): AccountFactory? {
|
||||
return account
|
||||
?: run {
|
||||
try {
|
||||
SSOAccountFactory().apply {
|
||||
initAccount(context)
|
||||
account = this
|
||||
Log.d(TAG, "New account, type=${context.accountType}")
|
||||
when (context.accountType) {
|
||||
AccountType.SSO -> {
|
||||
try {
|
||||
SSOAccountFactory().apply {
|
||||
initAccount(context)
|
||||
account = this
|
||||
}
|
||||
} catch (e: NextcloudFilesAppAccountNotFoundException) {
|
||||
Log.w(TAG, "Nextcloud application is not found")
|
||||
null
|
||||
} catch (e: NoCurrentAccountSelectedException) {
|
||||
if (uninitialized) {
|
||||
SSOAccountFactory()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: NextcloudFilesAppAccountNotFoundException) {
|
||||
Log.w(TAG, "Nextcloud application is not found")
|
||||
null
|
||||
} catch (e: NoCurrentAccountSelectedException) {
|
||||
if (uninitialized) {
|
||||
SSOAccountFactory()
|
||||
} else {
|
||||
null
|
||||
AccountType.Direct -> {
|
||||
DirectAccountFactory().apply {
|
||||
initAccount(context)
|
||||
account = this
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.setTypeSSO() {
|
||||
account = null
|
||||
accountType = AccountType.SSO
|
||||
DirectAccountFactory.setCredentials(this, null, null, null)
|
||||
}
|
||||
|
||||
fun Context.setTypeDirect(url: String, username: String, password: String) {
|
||||
account = null
|
||||
accountType = AccountType.Direct
|
||||
DirectAccountFactory.setCredentials(this, url, username, password)
|
||||
}
|
||||
|
||||
fun isConnected(context: Context): Boolean {
|
||||
return getAccount(context)?.isConnected(context) == true
|
||||
return getAccount(context)?.initAccount(context) == true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,12 +5,11 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
|
||||
interface AccountFactory {
|
||||
val apiFactory: Class<*>
|
||||
val name: String?
|
||||
val url: String?
|
||||
fun initAccount(context: Context)
|
||||
fun isConnected(context: Context): Boolean
|
||||
var name: String?
|
||||
var url: String?
|
||||
fun initAccount(context: Context): Boolean
|
||||
fun connect(activity: Activity)
|
||||
fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?, block: (success: Boolean) -> Unit)
|
||||
fun getAccount(context: Context): Any?
|
||||
fun logout(context: Context)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
package org.unifiedpush.distributor.nextpush.account
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import okhttp3.* // ktlint-disable no-wildcard-imports
|
||||
import org.unifiedpush.distributor.nextpush.activities.StartActivity
|
||||
import org.unifiedpush.distributor.nextpush.api.provider.ApiProvider.Companion.mApiEndpoint
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
private const val PREF_CONNECTED = "direct_account::connected"
|
||||
private const val PREF_URL = "direct_account::url"
|
||||
private const val PREF_USERNAME = "direct_account::username"
|
||||
private const val PREF_PASSWORD = "direct_account::password"
|
||||
|
||||
class DirectAccountFactory : AccountFactory {
|
||||
override var name: String? = null
|
||||
override var url: String? = null
|
||||
|
||||
private var Context.connected: Boolean
|
||||
get() = this.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
||||
.getBoolean(PREF_CONNECTED, false)
|
||||
set(value) = this.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
||||
.edit().putBoolean(PREF_CONNECTED, value)
|
||||
.apply()
|
||||
|
||||
override fun initAccount(context: Context): Boolean {
|
||||
url = context.url
|
||||
name = context.username
|
||||
|
||||
return context.connected
|
||||
}
|
||||
|
||||
override fun connect(activity: Activity) {
|
||||
activity.connected = false
|
||||
val client = getAccount(activity) as OkHttpClient? ?: return retActivity(activity)
|
||||
val url = activity.url ?: return retActivity(activity)
|
||||
|
||||
val request = Request.Builder()
|
||||
.url("$url/$mApiEndpoint/")
|
||||
.build()
|
||||
|
||||
val call = client.newCall(request)
|
||||
call.enqueue(object : Callback {
|
||||
private val TAG = "DirectAccountCallback"
|
||||
override fun onFailure(call: Call, e: IOException) {
|
||||
Log.e(TAG, "Could not connect", e)
|
||||
retActivity(activity)
|
||||
}
|
||||
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
Log.e(TAG, "Status: ${response.code}")
|
||||
activity.connected = response.code == 200
|
||||
response.close()
|
||||
retActivity(activity)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onActivityResult(
|
||||
activity: Activity,
|
||||
requestCode: Int,
|
||||
resultCode: Int,
|
||||
data: Intent?,
|
||||
block: (success: Boolean) -> Unit
|
||||
) {
|
||||
block(activity.connected)
|
||||
}
|
||||
|
||||
override fun getAccount(context: Context): Any? {
|
||||
val username = context.username ?: return null
|
||||
val password = context.password ?: return null
|
||||
return OkHttpClient.Builder()
|
||||
.connectTimeout(30, TimeUnit.SECONDS)
|
||||
.authenticator(DirectAuth(username, password))
|
||||
.followRedirects(false)
|
||||
.build()
|
||||
}
|
||||
|
||||
override fun logout(context: Context) {
|
||||
context.connected = false
|
||||
setCredentials(context, null, null, null)
|
||||
}
|
||||
|
||||
private fun retActivity(activity: Activity) {
|
||||
(activity as StartActivity).onActivityResult(0, 0, null)
|
||||
}
|
||||
|
||||
inner class DirectAuth(private val username: String, private val password: String) : Authenticator {
|
||||
override fun authenticate(route: Route?, response: Response): Request? {
|
||||
if (responseCount(response) >= 3) {
|
||||
return null
|
||||
}
|
||||
val credential = Credentials.basic(username, password)
|
||||
return response.request.newBuilder().header("Authorization", credential).build()
|
||||
}
|
||||
|
||||
private fun responseCount(_response: Response): Int {
|
||||
var response = _response
|
||||
var result = 1
|
||||
while (response.priorResponse?.also {
|
||||
response = it
|
||||
} != null
|
||||
) {
|
||||
result++
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private var Context.url: String?
|
||||
get() = this.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
||||
.getString(PREF_URL, null)
|
||||
set(value) = value?.let {
|
||||
this.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
||||
.edit().putString(PREF_URL, it)
|
||||
.apply()
|
||||
} ?: run {
|
||||
this.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
||||
.edit().remove(PREF_URL)
|
||||
.apply()
|
||||
}
|
||||
|
||||
private var Context.username: String?
|
||||
get() = this.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
||||
.getString(PREF_USERNAME, null)
|
||||
set(value) = value?.let {
|
||||
this.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
||||
.edit().putString(PREF_USERNAME, it)
|
||||
.apply()
|
||||
} ?: run {
|
||||
this.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
||||
.edit().remove(PREF_USERNAME)
|
||||
.apply()
|
||||
}
|
||||
|
||||
private var Context.password: String?
|
||||
get() = this.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
||||
.getString(PREF_PASSWORD, null)
|
||||
set(value) = value?.let {
|
||||
this.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
||||
.edit().putString(PREF_PASSWORD, it)
|
||||
.apply()
|
||||
} ?: run {
|
||||
this.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
||||
.edit().remove(PREF_PASSWORD)
|
||||
.apply()
|
||||
}
|
||||
|
||||
fun setCredentials(context: Context, url: String?, username: String?, password: String?) {
|
||||
context.url = url
|
||||
context.username = username
|
||||
context.password = password
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,23 +17,17 @@ import com.nextcloud.android.sso.helper.SingleAccountHelper
|
|||
import com.nextcloud.android.sso.model.SingleSignOnAccount
|
||||
import com.nextcloud.android.sso.ui.UiExceptionManager
|
||||
import org.unifiedpush.distributor.nextpush.R
|
||||
import org.unifiedpush.distributor.nextpush.api.provider.ApiSSOFactory
|
||||
import org.unifiedpush.distributor.nextpush.utils.TAG
|
||||
|
||||
class SSOAccountFactory : AccountFactory {
|
||||
override val apiFactory: Class<*> = ApiSSOFactory::class.java
|
||||
override val name: String?
|
||||
override var name: String? = null
|
||||
get() = ssoAccount?.name
|
||||
override val url: String?
|
||||
override var url: String? = null
|
||||
get() = ssoAccount?.url
|
||||
|
||||
private var ssoAccount: SingleSignOnAccount? = null
|
||||
|
||||
override fun initAccount(context: Context) {
|
||||
ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(context)
|
||||
}
|
||||
|
||||
override fun isConnected(context: Context): Boolean {
|
||||
override fun initAccount(context: Context): Boolean {
|
||||
try {
|
||||
ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(context)
|
||||
} catch (e: NextcloudFilesAppAccountNotFoundException) {
|
||||
|
@ -93,6 +87,14 @@ class SSOAccountFactory : AccountFactory {
|
|||
return ssoAccount
|
||||
}
|
||||
|
||||
override fun logout(context: Context) {
|
||||
AccountImporter.clearAllAuthTokens(context)
|
||||
AccountImporter.getSharedPreferences(context)
|
||||
.edit()
|
||||
.remove("PREF_CURRENT_ACCOUNT_STRING")
|
||||
.apply()
|
||||
}
|
||||
|
||||
private fun nextcloudAppNotInstalledDialog(context: Context) {
|
||||
val message = TextView(context)
|
||||
val builder = AlertDialog.Builder(context)
|
||||
|
|
|
@ -9,8 +9,6 @@ import android.view.MenuItem
|
|||
import android.view.View
|
||||
import android.widget.* // ktlint-disable no-wildcard-imports
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.nextcloud.android.sso.AccountImporter
|
||||
import com.nextcloud.android.sso.AccountImporter.clearAllAuthTokens
|
||||
import org.unifiedpush.distributor.nextpush.R
|
||||
import org.unifiedpush.distributor.nextpush.account.Account.getAccount
|
||||
import org.unifiedpush.distributor.nextpush.account.Account.isConnected
|
||||
|
@ -90,11 +88,7 @@ class MainActivity : AppCompatActivity() {
|
|||
alert.setMessage(R.string.logout_alert_content)
|
||||
alert.setPositiveButton(R.string.ok) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
clearAllAuthTokens(this)
|
||||
AccountImporter.getSharedPreferences(this)
|
||||
.edit()
|
||||
.remove("PREF_CURRENT_ACCOUNT_STRING")
|
||||
.apply()
|
||||
getAccount(this)?.logout(this)
|
||||
deleteDevice(this) {
|
||||
StartService.stopService()
|
||||
FailureHandler.clearFails()
|
||||
|
|
|
@ -4,15 +4,27 @@ import android.app.Activity
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.text.InputType
|
||||
import android.util.Log
|
||||
import android.widget.Button
|
||||
import android.widget.EditText
|
||||
import android.widget.ImageView
|
||||
import android.widget.RelativeLayout
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.isGone
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
import org.unifiedpush.distributor.nextpush.R
|
||||
import org.unifiedpush.distributor.nextpush.account.Account
|
||||
import org.unifiedpush.distributor.nextpush.account.Account.setTypeDirect
|
||||
import org.unifiedpush.distributor.nextpush.account.Account.setTypeSSO
|
||||
import org.unifiedpush.distributor.nextpush.activities.MainActivity.Companion.goToMainActivity
|
||||
import org.unifiedpush.distributor.nextpush.activities.PermissionsRequest.requestAppPermissions
|
||||
import org.unifiedpush.distributor.nextpush.utils.TAG
|
||||
|
||||
class StartActivity : AppCompatActivity() {
|
||||
private var onResult: ((activity: Activity, requestCode: Int, resultCode: Int, data: Intent?, block: (success: Boolean) -> Unit) -> Unit)? = null
|
||||
private var passwordIsVisible = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -22,20 +34,43 @@ class StartActivity : AppCompatActivity() {
|
|||
goToMainActivity(this)
|
||||
finish()
|
||||
}
|
||||
findViewById<Button>(R.id.sso_connection).setOnClickListener {
|
||||
Account.getAccount(this, uninitialized = true)?.let {
|
||||
onResult = { activity: Activity, i: Int, i1: Int, intent: Intent?, block: (success: Boolean) -> Unit ->
|
||||
it.onActivityResult(activity, i, i1, intent, block)
|
||||
findViewById<Button>(R.id.btn_sso_login).setOnClickListener {
|
||||
setTypeSSO()
|
||||
login()
|
||||
}
|
||||
findViewById<Button>(R.id.btn_manual_login).setOnClickListener {
|
||||
val url = findViewById<EditText>(R.id.edt_url).text.toString().let {
|
||||
if (it.last() != '/') {
|
||||
"$it/"
|
||||
} else {
|
||||
it
|
||||
}
|
||||
it.connect(this)
|
||||
}
|
||||
val username = findViewById<EditText>(R.id.edt_username).text.toString()
|
||||
val password = findViewById<EditText>(R.id.edt_password).text.toString()
|
||||
setTypeDirect(url, username, password)
|
||||
login()
|
||||
}
|
||||
findViewById<TextView>(R.id.manual_login).setOnClickListener {
|
||||
findViewById<RelativeLayout>(R.id.manual_login_wrapper).apply {
|
||||
isGone = !isGone
|
||||
}
|
||||
}
|
||||
findViewById<ImageView>(R.id.ic_show_password).setOnClickListener {
|
||||
passwordIsVisible = !passwordIsVisible
|
||||
findViewById<TextInputEditText>(R.id.edt_password).inputType =
|
||||
when (passwordIsVisible) {
|
||||
false -> InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
|
||||
true -> InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
onResult?.let {
|
||||
it(this, requestCode, resultCode, data) { success ->
|
||||
Log.d(TAG, "Connection succeed=$success")
|
||||
if (success) {
|
||||
goToMainActivity(this)
|
||||
finish()
|
||||
|
@ -44,6 +79,15 @@ class StartActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun login() {
|
||||
Account.getAccount(this, uninitialized = true)?.let {
|
||||
onResult = { activity: Activity, i: Int, i1: Int, intent: Intent?, block: (success: Boolean) -> Unit ->
|
||||
it.onActivityResult(activity, i, i1, intent, block)
|
||||
}
|
||||
it.connect(this)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun goToStartActivity(context: Context) {
|
||||
val intent = Intent(
|
||||
|
|
|
@ -10,8 +10,11 @@ import okhttp3.OkHttpClient
|
|||
import okhttp3.Request
|
||||
import okhttp3.sse.EventSource
|
||||
import okhttp3.sse.EventSources
|
||||
import org.unifiedpush.distributor.nextpush.account.Account.accountType
|
||||
import org.unifiedpush.distributor.nextpush.account.Account.deviceId
|
||||
import org.unifiedpush.distributor.nextpush.account.Account.getAccount
|
||||
import org.unifiedpush.distributor.nextpush.account.AccountType
|
||||
import org.unifiedpush.distributor.nextpush.api.provider.ApiDirectFactory
|
||||
import org.unifiedpush.distributor.nextpush.api.provider.ApiProvider
|
||||
import org.unifiedpush.distributor.nextpush.api.provider.ApiProvider.Companion.mApiEndpoint
|
||||
import org.unifiedpush.distributor.nextpush.api.provider.ApiProviderFactory
|
||||
|
@ -32,7 +35,10 @@ object Api {
|
|||
(
|
||||
provider ?: run {
|
||||
Log.d(TAG, "Setting SSOProvider")
|
||||
ApiSSOFactory(this).apply {
|
||||
when (accountType) {
|
||||
AccountType.SSO -> ApiSSOFactory(this)
|
||||
AccountType.Direct -> ApiDirectFactory(this)
|
||||
}.apply {
|
||||
provider = this
|
||||
}
|
||||
}
|
||||
|
@ -92,7 +98,7 @@ object Api {
|
|||
.readTimeout(0, TimeUnit.SECONDS)
|
||||
.retryOnConnectionFailure(false)
|
||||
.build()
|
||||
val url = "$baseUrl$mApiEndpoint/device/$deviceId"
|
||||
val url = "$baseUrl${mApiEndpoint}device/$deviceId"
|
||||
|
||||
val request = Request.Builder().url(url)
|
||||
.get()
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
package org.unifiedpush.distributor.nextpush.api.provider
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import okhttp3.* // ktlint-disable no-wildcard-imports
|
||||
import org.unifiedpush.distributor.nextpush.account.Account.getAccount
|
||||
import org.unifiedpush.distributor.nextpush.api.provider.ApiProvider.Companion.mApiEndpoint
|
||||
import org.unifiedpush.distributor.nextpush.utils.TAG
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
|
||||
class ApiDirectFactory(val context: Context) : ApiProviderFactory {
|
||||
|
||||
private var apiProvider: ApiProvider? = null
|
||||
|
||||
override fun getProviderAndExecute(block: (ApiProvider) -> Unit) {
|
||||
val account = getAccount(context) ?: run {
|
||||
Log.w(TAG, "No account found")
|
||||
return
|
||||
}
|
||||
val url = account.url ?: run {
|
||||
Log.w(TAG, "No url found")
|
||||
return
|
||||
}
|
||||
val client = account.getAccount(context) as OkHttpClient? ?: run {
|
||||
Log.w(TAG, "No client found")
|
||||
return
|
||||
}
|
||||
apiProvider?.let(block)
|
||||
?: run {
|
||||
Log.d(TAG, "Creating new provider")
|
||||
Retrofit.Builder()
|
||||
.client(client)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
|
||||
.baseUrl("$url$mApiEndpoint").build()
|
||||
.create(ApiProvider::class.java).let {
|
||||
apiProvider = it
|
||||
block(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun destroyProvider() {
|
||||
// Nothing
|
||||
}
|
||||
}
|
|
@ -9,23 +9,23 @@ import retrofit2.http.Path
|
|||
|
||||
interface ApiProvider {
|
||||
|
||||
@PUT("/device/")
|
||||
@PUT("device/")
|
||||
fun createDevice(
|
||||
@Body subscribeMap: Map<String, String>
|
||||
): Observable<ApiResponse>?
|
||||
|
||||
@DELETE("/device/{deviceId}")
|
||||
@DELETE("device/{deviceId}")
|
||||
fun deleteDevice(@Path("deviceId") deviceId: String): Observable<ApiResponse>?
|
||||
|
||||
@PUT("/app/")
|
||||
@PUT("app/")
|
||||
fun createApp(
|
||||
@Body authorizeMap: Map<String, String>
|
||||
): Observable<ApiResponse>?
|
||||
|
||||
@DELETE("/app/{token}")
|
||||
@DELETE("app/{token}")
|
||||
fun deleteApp(@Path("token") token: String): Observable<ApiResponse>?
|
||||
|
||||
companion object {
|
||||
const val mApiEndpoint = "/index.php/apps/uppush"
|
||||
const val mApiEndpoint = "/index.php/apps/uppush/"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -95,7 +95,7 @@ object Distributor {
|
|||
private fun getEndpoint(context: Context, connectorToken: String): String {
|
||||
val db = getDb(context)
|
||||
val appToken = db.getAppToken(connectorToken)
|
||||
return "${getAccount(context)?.url}$mApiEndpoint/push/$appToken"
|
||||
return "${getAccount(context)?.url}${mApiEndpoint}/push/$appToken"
|
||||
}
|
||||
|
||||
fun checkToken(context: Context, connectorToken: String, app: String): String {
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"/>
|
||||
</vector>
|
|
@ -7,11 +7,14 @@
|
|||
android:background="@color/nextcloud"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<!-- Part comes from https://github.com/nextcloud/news-android/blob/v.0.9.9.78/News-Android-App/src/main/res/layout/activity_login_dialog.xml -->
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="vertical"
|
||||
android:padding="32dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
@ -40,19 +43,15 @@
|
|||
app:layout_constraintTop_toBottomOf="@+id/img_nextcloud_logo" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/sso_connection"
|
||||
android:id="@+id/btn_sso_login"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:backgroundTint="@color/white"
|
||||
android:padding="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/sso_connection_button"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@color/nextcloud"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="24dp"
|
||||
style="@style/NextcloudButton"
|
||||
app:layout_constraintTop_toBottomOf="@+id/textView3" />
|
||||
|
||||
|
||||
|
@ -66,7 +65,113 @@
|
|||
android:gravity="center"
|
||||
android:text="@string/manual_login"
|
||||
android:textColor="@color/material_grey_100"
|
||||
app:layout_constraintTop_toBottomOf="@+id/sso_connection" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/btn_sso_login" />
|
||||
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/manual_login_wrapper"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/username_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:counterOverflowTextColor="@android:color/white"
|
||||
android:theme="@style/TextInputLayoutAppearance"
|
||||
style="@style/TextInputLayoutStyle">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/edt_username"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="start"
|
||||
android:hint="@string/login_hint_username"
|
||||
android:inputType="textEmailAddress"
|
||||
android:textAlignment="viewStart"
|
||||
style="@style/TextInputEditTextLogin"/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/password_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/username_container"
|
||||
android:layout_alignParentStart="true"
|
||||
app:counterOverflowTextColor="@android:color/white"
|
||||
android:theme="@style/TextInputLayoutAppearance"
|
||||
style="@style/TextInputLayoutStyle">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/edt_password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="start"
|
||||
android:hint="@string/login_hint_password"
|
||||
android:inputType="textPassword"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="viewStart"
|
||||
style="@style/TextInputEditTextLogin"/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/url_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/password_container"
|
||||
android:layout_alignParentStart="true"
|
||||
app:counterOverflowTextColor="@android:color/white"
|
||||
android:theme="@style/TextInputLayoutAppearance"
|
||||
style="@style/TextInputLayoutStyle">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/edt_url"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="start"
|
||||
android:hint="@string/login_hint_nextcloud_root_url"
|
||||
android:inputType="textUri"
|
||||
android:maxLines="1"
|
||||
android:selectAllOnFocus="true"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="viewStart"
|
||||
style="@style/TextInputEditTextLogin"
|
||||
android:theme="@style/TextInputEditTextLogin" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ic_show_password"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignTop="@+id/password_container"
|
||||
android:layout_alignEnd="@+id/password_container"
|
||||
android:layout_alignBottom="@+id/password_container"
|
||||
android:layout_gravity="end"
|
||||
android:contentDescription="@string/login_show_password_img_description"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingEnd="10dp"
|
||||
android:src="@drawable/ic_action_visibility"
|
||||
app:tint="@color/white" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_manual_login"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/url_container"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/login_button_sign_in"
|
||||
style="@style/NextcloudButton"
|
||||
app:cornerRadius="24dp"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -28,4 +28,9 @@
|
|||
<string name="no_notification_dialog_title">Notifications refused</string>
|
||||
<string name="no_notification_dialog_message">You won\'t be notified when the application is disconnected.\nYou can enable notifications later in the settings.</string>
|
||||
<string name="manual_login">Manual login</string>
|
||||
<string name="login_hint_username">Username</string>
|
||||
<string name="login_hint_password">Application password</string>
|
||||
<string name="login_hint_nextcloud_root_url">Nextcloud root url</string>
|
||||
<string name="login_button_sign_in">Sign in</string>
|
||||
<string name="login_show_password_img_description">Show password</string>
|
||||
</resources>
|
||||
|
|
|
@ -22,4 +22,54 @@
|
|||
<style name="Theme.NextPush.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
|
||||
|
||||
<style name="Theme.NextPush.PopupOverlay" parent="ThemeOverlay.AppCompat.DayNight" />
|
||||
|
||||
<style name="ErrorText" parent="TextAppearance.AppCompat">
|
||||
<item name="android:textColor">@color/design_default_color_error</item>
|
||||
<item name="android:textSize">16sp</item>
|
||||
</style>
|
||||
|
||||
<style name="HintText" parent="TextAppearance.AppCompat">
|
||||
<item name="android:textColor">@android:color/white</item>
|
||||
<item name="android:textSize">14sp</item>
|
||||
</style>
|
||||
|
||||
<style name="NextcloudButton" parent="ThemeOverlay.Material3.Button">
|
||||
<item name="android:backgroundTint">@android:color/white</item>
|
||||
<item name="android:textAllCaps">false</item>
|
||||
<item name="android:textColor">@color/nextcloud</item>
|
||||
<item name="android:textStyle">bold</item>
|
||||
<item name="android:padding">16dp</item>
|
||||
</style>
|
||||
|
||||
<style name="TextInputLayoutAppearance" parent="Base.Widget.MaterialComponents.TextInputLayout">
|
||||
<item name="android:textCursorDrawable">@null</item>
|
||||
<item name="android:background">@android:color/transparent</item>
|
||||
|
||||
<item name="hintTextAppearance">@style/HintText</item>
|
||||
<item name="errorTextAppearance">@style/ErrorText</item>
|
||||
<item name="android:textColor">@android:color/white</item>
|
||||
<item name="android:textColorHint">@android:color/white</item>
|
||||
|
||||
<item name="colorPrimary">@android:color/white</item>
|
||||
<item name="colorPrimaryDark">@android:color/white</item>
|
||||
<item name="colorAccent">@android:color/white</item>
|
||||
<item name="colorControlNormal">@android:color/white</item>
|
||||
<item name="colorControlActivated">@android:color/white</item>
|
||||
<item name="colorControlHighlight">@android:color/white</item>
|
||||
</style>
|
||||
|
||||
<style name="TextInputLayoutStyle">
|
||||
<item name="android:textColor">@android:color/white</item>
|
||||
<item name="android:textColorHint">@android:color/white</item>
|
||||
</style>
|
||||
|
||||
<style name="TextInputEditTextLogin" parent="ThemeOverlay.MaterialComponents.TextInputEditText">
|
||||
<item name="android:background">@android:color/transparent</item>
|
||||
<item name="android:textColor">@android:color/white</item>
|
||||
|
||||
<item name="colorPrimary">@android:color/white</item>
|
||||
<item name="colorPrimaryDark">@android:color/white</item>
|
||||
<item name="colorAccent">@android:color/white</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
Loading…
Reference in New Issue