Commit 0d57747a authored by Vincent Bourgmayer's avatar Vincent Bourgmayer 🎼
Browse files

Merge branch 'master' into '123-allowMeteredConnection-eDrive'

# Conflicts:
#   app/src/main/AndroidManifest.xml
parents ee040bdd f8cb13bd
Pipeline #178199 passed with stage
in 1 minute and 57 seconds
......@@ -5,6 +5,7 @@ stages:
before_script:
- git submodule update --init --recursive
- echo email.key $EMAIL_KEY >> local.properties
- export GRADLE_USER_HOME=$(pwd)/.gradle
- chmod +x ./gradlew
......
......@@ -33,6 +33,7 @@ android {
multiDexEnabled true // >64k methods for Android 4.4
buildConfigField "String", "userAgent", "\"DAVx5\""
buildConfigField "String", "EMAIL_KEY", "\"${emailKey()}\""
// when using this, make sure that notification icons are real bitmaps
vectorDrawables.useSupportLibrary = true
......@@ -161,3 +162,13 @@ dependencies {
testImplementation 'junit:junit:4.12'
testImplementation "com.squareup.okhttp3:mockwebserver:${versions.okhttp}"
}
def emailKey() {
Properties properties = new Properties()
try {
properties.load(project.rootProject.file('local.properties').newDataInputStream())
} catch (ignored) {
// Ignore
}
return properties.getProperty("email.key", "invalid")
}
......@@ -44,6 +44,16 @@
android:label="@string/app_name"
android:theme="@style/AppTheme"
tools:ignore="UnusedAttribute">
<activity
android:name=".ui.setup.CreateAccountActivity"
android:label="@string/create_account"
android:exported="true"
android:parentActivityName=".ui.AccountsActivity" >
<intent-filter>
<action android:name="${applicationId}.ui.setup.CreateAccountActivity" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<service android:name=".DavService"/>
......@@ -216,7 +226,7 @@
<intent-filter>
<action android:name="android.content.SyncAdapter" />
</intent-filter>
<meta-data
android:name="android.content.SyncAdapter"
android:resource="@xml/eelo_sync_calendars" />
......@@ -350,7 +360,7 @@
android:name="android.provider.CONTACTS_STRUCTURE"
android:resource="@xml/contacts" />
</service>
<!-- account type "Google" -->
<service
android:name=".syncadapter.GoogleAccountAuthenticatorService"
......
/*
* Copyright © ECORP SAS 2022.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package foundation.e.accountmanager.ui.setup
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import foundation.e.accountmanager.R
class CreateAccountActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_create_account)
supportFragmentManager.beginTransaction().apply {
replace(R.id.content, SendInviteFragment())
commit()
}
}
}
\ No newline at end of file
/*
* Copyright © ECORP SAS 2022.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package foundation.e.accountmanager.ui.setup
import android.accounts.AccountManager
import android.accounts.AccountManagerCallback
import android.app.Activity.RESULT_OK
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import foundation.e.accountmanager.R
class InviteSuccessfulFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
(activity as AppCompatActivity?)?.supportActionBar?.hide()
return inflater.inflate(R.layout.fragment_invite_successful, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val instructions = view.findViewById<TextView>(R.id.instructions)
val formattedText = view.context.getText(R.string.instructions)
instructions.text = formattedText
view.findViewById<Button>(R.id.sign_in).setOnClickListener {
try {
activity?.let {
val accountManager = AccountManager.get(it)
accountManager.addAccount(
"e.foundation.webdav.eelo",
null,
null,
null,
it,
AccountManagerCallback<Bundle?> {
activity?.setResult(RESULT_OK, Intent())
activity?.finish()
},
null
)
}
} catch (e: Exception) {
}
}
view.findViewById<Button>(R.id.sign_in_later).setOnClickListener {
activity?.setResult(RESULT_OK, Intent())
activity?.finish()
}
}
}
\ No newline at end of file
/*
* Copyright © ECORP SAS 2022.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package foundation.e.accountmanager.ui.setup
import android.accounts.AccountManager
import android.accounts.AccountManagerCallback
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.ConnectivityManager
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textfield.TextInputLayout
import foundation.e.accountmanager.BuildConfig
import foundation.e.accountmanager.R
import okhttp3.*
import java.io.IOException
import java.math.BigInteger
import java.security.MessageDigest
class SendInviteFragment : Fragment() {
private var email = ""
private val key = BuildConfig.EMAIL_KEY
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_send_invite, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val emailTextField = view.findViewById<EditText>(R.id.emailId)
val emailTextLayout = view.findViewById<TextInputLayout>(R.id.emailId_layout)
val sendButton = view.findViewById<Button>(R.id.send_button)
emailTextField.addTextChangedListener(object: TextWatcher {
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
if(p0.toString().trim().isNotEmpty()) {
sendButton.isEnabled = true
sendButton.setBackgroundColor(resources.getColor(R.color.create_account_input_enabled_color))
emailTextLayout.boxStrokeColor = resources.getColor(R.color.create_account_input_enabled_color)
emailTextLayout.hintTextColor = resources.getColorStateList(R.color.create_account_input_enabled_color)
} else {
sendButton.isEnabled = false
sendButton.setBackgroundColor(resources.getColor(R.color.create_account_input_disabled_color))
emailTextLayout.boxStrokeColor = resources.getColor(R.color.create_account_input_disabled_color)
emailTextLayout.hintTextColor = resources.getColorStateList(R.color.create_account_input_disabled_color)
}
}
override fun afterTextChanged(p0: Editable?) {
}
})
sendButton.setOnClickListener {
email = emailTextField.text.toString()
if(isNetworkAvailable()) {
createAccount()
} else {
Toast.makeText(context, R.string.no_internet_toast, Toast.LENGTH_LONG).show()
}
}
}
private fun createAccount() {
val client = OkHttpClient()
val userAgentKey = "User-Agent"
val userAgentValue = "Java 11 HttpClient Bot"
val contentKey = "Content-Type"
val contentValue = "application/x-www-form-urlencoded"
val inviteUrl = "https://welcome.ecloud.global/process_email_invite.php"
val formBody = FormBody.Builder()
.add("email", email)
.add("check", secretCode())
.build()
val request = Request.Builder()
.post(formBody)
.header(userAgentKey, userAgentValue)
.addHeader(contentKey, contentValue)
.url(inviteUrl)
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
e.printStackTrace()
}
override fun onResponse(call: Call, response: Response) {
if (!response.isSuccessful) throw IOException("Unexpected code $response")
val stringCode = response.body()?.string()
activity?.runOnUiThread {
stringCode?.let { getIntCode(it) }?.let { handleErrorCode(it) }
}
}
})
}
private fun secretCode(): String {
val md: MessageDigest = MessageDigest.getInstance("MD5")
return BigInteger(1, md.digest((email + key).toByteArray())).toString(16).padStart(32, '0')
}
private fun getIntCode(stringCode: String): Int {
val trimedResult: String = stringCode.trim()
val errorCode = trimedResult.substring(
trimedResult.lastIndexOf(":") + 1,
trimedResult.length - 1
)
return errorCode.toInt()
}
private fun handleErrorCode(intCode: Int) {
when(intCode) {
100 -> {
requireFragmentManager().beginTransaction()
.replace(R.id.content, InviteSuccessfulFragment())
.commit()
}
200 -> {
activity?.let {
MaterialAlertDialogBuilder(it, R.style.CustomAlertDialogStyle)
.setIcon(R.drawable.ic_error)
.setTitle(R.string.user_exists_dialog_title)
.setMessage(R.string.user_exists_dialog_message)
.setPositiveButton(R.string.skip_button) { _,_ ->
activity?.setResult(Activity.RESULT_OK, Intent())
activity?.finish()
}
.setNegativeButton(R.string.login_button) { _,_ ->
try {
val accountManager = AccountManager.get(it)
accountManager.addAccount(
"e.foundation.webdav.eelo",
null,
null,
null,
it,
AccountManagerCallback<Bundle?> {
activity?.setResult(Activity.RESULT_OK, Intent())
activity?.finish()
},
null
)
} catch (e: Exception) {
}
}
.show()
}
}
300 -> {
activity?.let {
val emailField = it.findViewById<TextInputLayout>(R.id.emailId_layout)
emailField.error = getString(R.string.emailid_error)
}
}
else -> {
activity?.let {
MaterialAlertDialogBuilder(it, R.style.CustomAlertDialogStyle)
.setIcon(R.drawable.ic_error)
.setTitle(R.string.error_dialog_title)
.setMessage(R.string.error_dialog_message)
.setPositiveButton(R.string.skip_button) { _,_ ->
activity?.setResult(Activity.RESULT_OK, Intent())
activity?.finish()
}
.setNegativeButton(R.string.try_again_button) { _,_ ->
createAccount()
}
.show()
}
}
}
}
private fun isNetworkAvailable(): Boolean {
val connectivityManager = activity?.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val activeNetworkInfo = connectivityManager.activeNetworkInfo
return activeNetworkInfo != null && activeNetworkInfo.isConnectedOrConnecting
}
}
\ No newline at end of file
<!--
~ Copyright © ECORP SAS 2022.
~ All rights reserved. This program and the accompanying materials
~ are made available under the terms of the GNU Public License v3.0
~ which accompanies this distribution, and is available at
~ http://www.gnu.org/licenses/gpl.html
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#99000000"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm1,15h-2v-2h2v2zm0,-4h-2V7h2v6z"/>
</vector>
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright © ECORP SAS 2022.
~ All rights reserved. This program and the accompanying materials
~ are made available under the terms of the GNU Public License v3.0
~ which accompanies this distribution, and is available at
~ http://www.gnu.org/licenses/gpl.html
-->
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.setup.CreateAccountActivity">
<FrameLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright © ECORP SAS 2022.
~ All rights reserved. This program and the accompanying materials
~ are made available under the terms of the GNU Public License v3.0
~ which accompanies this distribution, and is available at
~ http://www.gnu.org/licenses/gpl.html
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white">
<LinearLayout
android:id="@+id/successful_box"
android:background="#f6f6f6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:orientation="horizontal"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_done"
app:tint="#4EA719"
android:layout_marginStart="24dp"
android:layout_marginTop="28dp"
android:layout_marginBottom="28dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@*android:color/bright_foreground_holo_light"
android:textSize="13sp"
android:layout_marginStart="17dp"
android:layout_marginEnd="24dp"
android:layout_marginTop="24dp"
android:layout_marginBottom="24dp"
android:text="@string/invitation_sent"/>
</LinearLayout>
<TextView
android:id="@+id/instructions_heading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="48dp"
android:layout_marginStart="24dp"
android:textSize="20sp"
android:textColor="@*android:color/bright_foreground_holo_light"
android:text="@string/instructions_heading"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/successful_box"/>
<TextView
android:id="@+id/instructions"
android:textSize="16sp"
android:textColor="@color/instructions_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/instructions_heading" />
<Button
android:id="@+id/sign_in"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
app:backgroundTint="@*android:color/accent_material_light"
android:textColor="@android:color/white"
android:text="@string/sign_in_button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/instructions"
app:layout_constraintBottom_toBottomOf="parent"/>
<Button
android:id="@+id/sign_in_later"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/setup_later_button"
android:textColor="@*android:color/accent_material_light"
style="@style/Widget.MaterialComponents.Button.TextButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/sign_in"/>
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright © ECORP SAS 2022.
~ All rights reserved. This program and the accompanying materials
~ are made available under the terms of the GNU Public License v3.0
~ which accompanies this distribution, and is available at
~ http://www.gnu.org/licenses/gpl.html
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/heading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="25dp"
android:paddingStart="24dp"
android:paddingEnd="24dp"
android:textSize="16sp"
android:textColor="@*android:color/bright_foreground_holo_light"
android:text="@string/send_invite_heading"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/emailId_layout"
style="@style/edittext_holder_style"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="17dp"
android:paddingEnd="17dp"
android:layout_marginTop="25dp"
android:theme="@style/edittext_holder_theme"
app:hintTextColor="@color/create_account_input_disabled_color"
app:errorEnabled="true"
app:boxStrokeColor="@color/create_account_input_disabled_color"
app:layout_constraintStart_toStartOf="@id/heading"
app:layout_constraintTop_toBottomOf="@id/heading">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/emailId"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/email_hint"
android:inputType="textEmailAddress"
android:textColor="@color/primaryTextColor"/>
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/send_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:backgroundTint="@color/create_account_button_disabled_color"
android:enabled="false"
android:text="@string/send_button"
android:textColor="@android:color/white"
android:layout_marginTop="17dp"
android:layout_marginEnd="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/emailId_layout" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<!--
~ Copyright © ECORP SAS 2022.
~ All rights reserved. This program and the accompanying materials
~ are made available under the terms of the GNU Public License v3.0
~ which accompanies this distribution, and is available at
~ http://www.gnu.org/licenses/gpl.html
-->
<resources>
<color name="create_account_button_enabled_color">@*android:color/accent_material_light</color>
<color name="create_account_button_disabled_color">#61000000</color>
<color name="create_account_input_enabled_color">@*android:color/accent_material_light</color>
<color name="create_account_input_disabled_color">#61000000</color>
<color name="instructions_text">#BD000000</color>