Added method to create Account using the Agorise's faucet, and also making use of Retrofit's suggested ServiceGenerator.
This commit is contained in:
parent
860947b351
commit
78909498c0
9 changed files with 301 additions and 9 deletions
|
@ -72,9 +72,12 @@ dependencies {
|
|||
implementation "com.jakewharton.rxbinding3:rxbinding:$rx_bindings_version"
|
||||
implementation "com.jakewharton.rxbinding3:rxbinding-material:$rx_bindings_version" // Material Components widgets
|
||||
implementation "com.jakewharton.rxbinding3:rxbinding-appcompat:$rx_bindings_version" // AndroidX appcompat widgets
|
||||
// Retrofit
|
||||
// Retrofit & OkHttp
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
|
||||
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
|
||||
implementation 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
|
||||
implementation 'com.squareup.okhttp3:okhttp:3.12.0'
|
||||
implementation 'com.squareup.okhttp3:logging-interceptor:3.5.0'
|
||||
//Firebase
|
||||
implementation 'com.google.firebase:firebase-core:16.0.6'
|
||||
implementation 'com.google.firebase:firebase-crash:16.2.1'
|
||||
|
|
|
@ -8,6 +8,7 @@ import android.view.ViewGroup
|
|||
import androidx.navigation.fragment.findNavController
|
||||
import com.jakewharton.rxbinding3.widget.textChanges
|
||||
import cy.agorise.bitsybitshareswallet.R
|
||||
import cy.agorise.bitsybitshareswallet.network.FaucetService
|
||||
import cy.agorise.bitsybitshareswallet.utils.Constants
|
||||
import cy.agorise.bitsybitshareswallet.utils.containsDigits
|
||||
import cy.agorise.bitsybitshareswallet.utils.containsVowels
|
||||
|
@ -21,12 +22,20 @@ import cy.agorise.graphenej.models.JsonRpcResponse
|
|||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import kotlinx.android.synthetic.main.fragment_create_account.*
|
||||
import org.bitcoinj.core.ECKey
|
||||
import retrofit2.Callback
|
||||
import java.io.BufferedReader
|
||||
import java.io.IOException
|
||||
import java.io.InputStreamReader
|
||||
import java.util.concurrent.TimeUnit
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import cy.agorise.bitsybitshareswallet.models.FaucetRequest
|
||||
import cy.agorise.bitsybitshareswallet.models.FaucetResponse
|
||||
import cy.agorise.bitsybitshareswallet.network.ServiceGenerator
|
||||
import retrofit2.Call
|
||||
import retrofit2.Response
|
||||
|
||||
class CreateAccountFragment : ConnectedFragment() {
|
||||
|
||||
class CreateAccountFragment : BaseAccountFragment() {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "CreateAccountFragment"
|
||||
|
@ -34,10 +43,12 @@ class CreateAccountFragment : ConnectedFragment() {
|
|||
private const val BRAINKEY_FILE = "brainkeydict.txt"
|
||||
private const val MIN_ACCOUNT_NAME_LENGTH = 8
|
||||
|
||||
private const val RESPONSE_GET_ACCOUNT_BY_NAME = 1
|
||||
// Used when trying to validate that the account name is available
|
||||
private const val RESPONSE_GET_ACCOUNT_BY_NAME_VALIDATION = 1
|
||||
// Used when trying to obtain the info of the newly created account
|
||||
private const val RESPONSE_GET_ACCOUNT_BY_NAME_CREATED = 2
|
||||
}
|
||||
|
||||
private lateinit var mBrainKey: BrainKey
|
||||
private lateinit var mAddress: String
|
||||
|
||||
/** Variables used to store the validation status of the form fields */
|
||||
|
@ -87,6 +98,7 @@ class CreateAccountFragment : ConnectedFragment() {
|
|||
btnCancel.setOnClickListener { findNavController().navigateUp() }
|
||||
|
||||
btnCreate.isEnabled = false
|
||||
btnCreate.setOnClickListener { createAccount() }
|
||||
|
||||
// Generating BrainKey
|
||||
generateKeys()
|
||||
|
@ -104,7 +116,7 @@ class CreateAccountFragment : ConnectedFragment() {
|
|||
val id = mNetworkService?.sendMessage(GetAccountByName(accountName), GetAccountByName.REQUIRED_API)
|
||||
|
||||
if (id != null)
|
||||
responseMap[id] = RESPONSE_GET_ACCOUNT_BY_NAME
|
||||
responseMap[id] = RESPONSE_GET_ACCOUNT_BY_NAME_VALIDATION
|
||||
}
|
||||
|
||||
enableDisableCreateButton()
|
||||
|
@ -157,7 +169,8 @@ class CreateAccountFragment : ConnectedFragment() {
|
|||
if (responseMap.containsKey(response.id)) {
|
||||
val responseType = responseMap[response.id]
|
||||
when (responseType) {
|
||||
RESPONSE_GET_ACCOUNT_BY_NAME -> handleAccountName(response.result)
|
||||
RESPONSE_GET_ACCOUNT_BY_NAME_VALIDATION -> handleAccountNameValidation(response.result)
|
||||
RESPONSE_GET_ACCOUNT_BY_NAME_CREATED -> handleAccountNameCreated(response.result)
|
||||
}
|
||||
responseMap.remove(response.id)
|
||||
}
|
||||
|
@ -171,7 +184,7 @@ class CreateAccountFragment : ConnectedFragment() {
|
|||
* Handles the response from the NetworkService's GetAccountByName call to decide if the user's suggested
|
||||
* account is available or not.
|
||||
*/
|
||||
private fun handleAccountName(result: Any?) {
|
||||
private fun handleAccountNameValidation(result: Any?) {
|
||||
if (result is AccountProperties) {
|
||||
tilAccountName.helperText = ""
|
||||
tilAccountName.error = getString(R.string.error__account_not_available)
|
||||
|
@ -185,6 +198,71 @@ class CreateAccountFragment : ConnectedFragment() {
|
|||
enableDisableCreateButton()
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the response from the NetworkService's GetAccountByName call and stores the information of the newly
|
||||
* created account if the result is successful, shows a toast error otherwise
|
||||
*/
|
||||
private fun handleAccountNameCreated(result: Any?) {
|
||||
if (result is AccountProperties) {
|
||||
onAccountSelected(result, tietPin.text.toString())
|
||||
} else {
|
||||
context?.toast(getString(R.string.error__created_account_not_found))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the account-creation request to the faucet server.
|
||||
* Only account name and public address is sent here.
|
||||
*/
|
||||
private fun createAccount() {
|
||||
val accountName = tietAccountName.text.toString()
|
||||
val faucetRequest = FaucetRequest(accountName, mAddress, Constants.FAUCET_REFERRER)
|
||||
|
||||
val sg = ServiceGenerator(Constants.FAUCET_URL)
|
||||
val faucetService = sg.getService(FaucetService::class.java)
|
||||
|
||||
val call = faucetService.registerPrivateAccount(faucetRequest)
|
||||
|
||||
// Execute the call asynchronously. Get a positive or negative callback.
|
||||
call.enqueue(object : Callback<FaucetResponse> {
|
||||
override fun onResponse(call: Call<FaucetResponse>, response: Response<FaucetResponse>) {
|
||||
// The network call was a success and we got a response
|
||||
getCreatedAccountInfo(response.body())
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<FaucetResponse>, t: Throwable) {
|
||||
// the network call was a failure
|
||||
MaterialDialog(context!!)
|
||||
.title(R.string.title_error)
|
||||
.message(cy.agorise.bitsybitshareswallet.R.string.error__faucet)
|
||||
.negativeButton(android.R.string.ok)
|
||||
.show()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun getCreatedAccountInfo(faucetResponse: FaucetResponse?) {
|
||||
if (faucetResponse?.account != null) {
|
||||
val id = mNetworkService?.sendMessage(GetAccountByName(faucetResponse.account?.name),
|
||||
GetAccountByName.REQUIRED_API)
|
||||
|
||||
if (id != null)
|
||||
responseMap[id] = RESPONSE_GET_ACCOUNT_BY_NAME_CREATED
|
||||
} else {
|
||||
Log.d(TAG, "Private account creation failed ")
|
||||
val content = if (faucetResponse?.error?.base?.size ?: 0 > 0) {
|
||||
getString(R.string.error__faucet_template, faucetResponse?.error?.base?.get(0))
|
||||
} else {
|
||||
getString(R.string.error__faucet_template, "None")
|
||||
}
|
||||
|
||||
MaterialDialog(context!!)
|
||||
.title(R.string.title_error)
|
||||
.message(text = content)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that generates a fresh key that will be controlling the newly created account.
|
||||
*/
|
||||
|
@ -197,11 +275,11 @@ class CreateAccountFragment : ConnectedFragment() {
|
|||
|
||||
val brainKeySuggestion = BrainKey.suggest(dictionary)
|
||||
mBrainKey = BrainKey(brainKeySuggestion, 0)
|
||||
val address = Address(ECKey.fromPublicOnly(mBrainKey.privateKey.pubKey))
|
||||
val address = Address(ECKey.fromPublicOnly(mBrainKey?.privateKey?.pubKey))
|
||||
Log.d(TAG, "brain key: $brainKeySuggestion")
|
||||
Log.d(TAG, "address would be: " + address.toString())
|
||||
mAddress = address.toString()
|
||||
tvBrainKey.text = mBrainKey.brainKey
|
||||
tvBrainKey.text = mBrainKey?.brainKey
|
||||
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, "IOException while trying to generate key. Msg: " + e.message)
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
package cy.agorise.bitsybitshareswallet.models;
|
||||
|
||||
/**
|
||||
* Class used to deserialize a the "account" object contained in the faucet response to the
|
||||
* {@link cy.agorise.bitsybitshareswallet.network.FaucetService#registerPrivateAccount(FaucetRequest)} API call.
|
||||
*/
|
||||
|
||||
public class FaucetAccount {
|
||||
public String name;
|
||||
public String owner_key;
|
||||
public String active_key;
|
||||
public String memo_key;
|
||||
public String referrer;
|
||||
public String refcode;
|
||||
|
||||
public FaucetAccount(String accountName, String address, String referrer){
|
||||
this.name = accountName;
|
||||
this.owner_key = address;
|
||||
this.active_key = address;
|
||||
this.memo_key = address;
|
||||
this.refcode = referrer;
|
||||
this.referrer = referrer;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getOwnerKey() {
|
||||
return owner_key;
|
||||
}
|
||||
|
||||
public void setOwnerKey(String owner_key) {
|
||||
this.owner_key = owner_key;
|
||||
}
|
||||
|
||||
public String getActiveKey() {
|
||||
return active_key;
|
||||
}
|
||||
|
||||
public void setActiveKey(String active_key) {
|
||||
this.active_key = active_key;
|
||||
}
|
||||
|
||||
public String getMemoKey() {
|
||||
return memo_key;
|
||||
}
|
||||
|
||||
public void setMemoKey(String memo_key) {
|
||||
this.memo_key = memo_key;
|
||||
}
|
||||
|
||||
public String getRefcode() {
|
||||
return refcode;
|
||||
}
|
||||
|
||||
public void setRefcode(String refcode) {
|
||||
this.refcode = refcode;
|
||||
}
|
||||
|
||||
public String getReferrer() {
|
||||
return referrer;
|
||||
}
|
||||
|
||||
public void setReferrer(String referrer) {
|
||||
this.referrer = referrer;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package cy.agorise.bitsybitshareswallet.models;
|
||||
|
||||
/**
|
||||
* Class used to encapsulate a faucet account creation request
|
||||
*/
|
||||
|
||||
public class FaucetRequest {
|
||||
private FaucetAccount account;
|
||||
|
||||
public FaucetRequest(String accountName, String address, String referrer){
|
||||
account = new FaucetAccount(accountName, address, referrer);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package cy.agorise.bitsybitshareswallet.models;
|
||||
|
||||
public class FaucetResponse {
|
||||
public FaucetAccount account;
|
||||
public Error error;
|
||||
|
||||
public class Error {
|
||||
public String[] base;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package cy.agorise.bitsybitshareswallet.network
|
||||
|
||||
import cy.agorise.bitsybitshareswallet.models.FaucetRequest
|
||||
import cy.agorise.bitsybitshareswallet.models.FaucetResponse
|
||||
import okhttp3.ResponseBody
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Headers
|
||||
import retrofit2.http.POST
|
||||
|
||||
/**
|
||||
* Interface to the faucet service. The faucet is used in order to register new BitShares accounts.
|
||||
*/
|
||||
interface FaucetService {
|
||||
|
||||
@GET("/")
|
||||
fun checkStatus(): Call<ResponseBody>
|
||||
|
||||
@Headers("Content-Type: application/json")
|
||||
@POST("/api/v1/accounts")
|
||||
fun registerPrivateAccount(@Body faucetRequest: FaucetRequest): Call<FaucetResponse>
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
package cy.agorise.bitsybitshareswallet.network;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.logging.HttpLoggingInterceptor;
|
||||
import retrofit2.Retrofit;
|
||||
import retrofit2.converter.gson.GsonConverterFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ServiceGenerator{
|
||||
public static String API_BASE_URL;
|
||||
private static HttpLoggingInterceptor logging;
|
||||
private static OkHttpClient.Builder httpClient;
|
||||
private static Retrofit.Builder builder;
|
||||
|
||||
private static HashMap<Class<?>, Object> Services;
|
||||
|
||||
public ServiceGenerator(String apiBaseUrl) {
|
||||
API_BASE_URL= apiBaseUrl;
|
||||
logging = new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY);
|
||||
httpClient = new OkHttpClient.Builder().addInterceptor(logging);
|
||||
builder = new Retrofit.Builder()
|
||||
.baseUrl(API_BASE_URL)
|
||||
.addConverterFactory(GsonConverterFactory.create(getGson()))
|
||||
.addCallAdapterFactory(RxJava2CallAdapterFactory.create());
|
||||
Services = new HashMap<Class<?>, Object>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Customizes the Gson instance with specific de-serialization logic
|
||||
*/
|
||||
private Gson getGson(){
|
||||
GsonBuilder builder = new GsonBuilder();
|
||||
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
public void setCallbackExecutor(Executor executor){
|
||||
builder.callbackExecutor(executor);
|
||||
}
|
||||
|
||||
public static <T> void setService(Class<T> klass, T thing) {
|
||||
Services.put(klass, thing);
|
||||
}
|
||||
|
||||
public <T> T getService(Class<T> serviceClass) {
|
||||
|
||||
T service = serviceClass.cast(Services.get(serviceClass));
|
||||
if (service == null) {
|
||||
service = createService(serviceClass);
|
||||
setService(serviceClass, service);
|
||||
}
|
||||
return service;
|
||||
}
|
||||
|
||||
public static <S> S createService(Class<S> serviceClass) {
|
||||
|
||||
httpClient.interceptors().add(new Interceptor() {
|
||||
@Override
|
||||
public okhttp3.Response intercept(Interceptor.Chain chain) throws IOException {
|
||||
okhttp3.Request original = chain.request();
|
||||
// Request customization: add request headers
|
||||
okhttp3.Request.Builder requestBuilder = original.newBuilder().method(original.method(), original.body());
|
||||
|
||||
okhttp3.Request request = requestBuilder.build();
|
||||
return chain.proceed(request);
|
||||
}
|
||||
});
|
||||
httpClient.readTimeout(5, TimeUnit.MINUTES);
|
||||
httpClient.connectTimeout(5, TimeUnit.MINUTES);
|
||||
OkHttpClient client = httpClient.build();
|
||||
Retrofit retrofit = builder.client(client).build();
|
||||
return retrofit.create(serviceClass);
|
||||
}
|
||||
}
|
|
@ -14,6 +14,12 @@ object Constants {
|
|||
/** The minimum required length for a PIN number */
|
||||
const val MIN_PIN_LENGTH = 6
|
||||
|
||||
/** Name of the account passed to the faucet as the referrer */
|
||||
const val FAUCET_REFERRER = "agorise"
|
||||
|
||||
/** Faucet URL used to create new accounts */
|
||||
const val FAUCET_URL = "https://faucet.palmpay.io"
|
||||
|
||||
/** The user selected encrypted PIN */
|
||||
const val KEY_ENCRYPTED_PIN = "key_encrypted_pin"
|
||||
|
||||
|
|
|
@ -32,6 +32,10 @@
|
|||
<string name="text__verifying_account_availability">Verifying account availability…</string>
|
||||
<string name="error__account_not_available">Account not available</string>
|
||||
<string name="text__account_is_available">Account is available</string>
|
||||
<string name="title_error">Error</string>
|
||||
<string name="error__faucet">The server returned an error. This might be due to a limitation purposefully set in place to disallow frequent requests coming from the same IP address in a short time lapse. Please wait 5 minutes and try again, or switch to a different network, for example from wifi to cell.</string>
|
||||
<string name="error__faucet_template">The faucet returned an error. Msg: %1$s</string>
|
||||
<string name="error__created_account_not_found">The app could not retrieve information about the newly created account</string>
|
||||
|
||||
<!-- Home -->
|
||||
<string name="title_transactions">Transactions</string>
|
||||
|
|
Loading…
Reference in a new issue