Add support for fetching custom blocks.

This commit is contained in:
Sergio Martínez Portela 2020-05-20 12:07:24 +02:00
parent cca4b70b90
commit 8507e868d8
20 changed files with 269 additions and 52 deletions

View File

@ -2,15 +2,27 @@ package com.codigoparallevar.minicards;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import com.codigoparallevar.minicards.types.functional.Consumer;
import com.codigoparallevar.minicards.types.functional.Producer;
import com.codigoparallevar.minicards.types.functional.Tuple2;
import com.codigoparallevar.minicards.ui_helpers.GetAsync;
import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.programaker.api.ProgramakerApi;
import com.programaker.api.data.ProgramakerCustomBlock;
import com.programaker.api.data.api_results.ProgramakerGetCustomBlocksResult;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; import android.util.Log;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import java.io.IOException; import java.io.IOException;
import java.util.List;
public class CardActivity extends AppCompatActivity { public class CardActivity extends AppCompatActivity {
@ -19,7 +31,6 @@ public class CardActivity extends AppCompatActivity {
public static final String VISUALIZATION_MODE_KEY = "VISUALIZATION_MODE"; public static final String VISUALIZATION_MODE_KEY = "VISUALIZATION_MODE";
public static final String DEVELOPER_VISUALIZATION_MODE = "DEVELOPER_VISUALIZATION_MODE"; public static final String DEVELOPER_VISUALIZATION_MODE = "DEVELOPER_VISUALIZATION_MODE";
CanvasView canvasView; CanvasView canvasView;
com.getbase.floatingactionbutton.AddFloatingActionButton AddPartButton; com.getbase.floatingactionbutton.AddFloatingActionButton AddPartButton;
com.getbase.floatingactionbutton.FloatingActionButton SetDevModeButton; com.getbase.floatingactionbutton.FloatingActionButton SetDevModeButton;
@ -34,11 +45,39 @@ public class CardActivity extends AppCompatActivity {
boolean devMode = false; boolean devMode = false;
FloatingActionButton removePartFab; FloatingActionButton removePartFab;
private PartsHolder partsHolder; private PartsHolder partsHolder;
private ProgramakerApi ProgramakerApi;
private ConfigManager Config;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
this.Config = new ConfigManager(this);
this.ProgramakerApi = new ProgramakerApi();
String token = this.Config.getToken();
if (token != null) {
this.ProgramakerApi.setToken(token);
}
new GetAsync<ProgramakerGetCustomBlocksResult>().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
new Tuple2<>(new Producer<ProgramakerGetCustomBlocksResult>() {
@Override
public ProgramakerGetCustomBlocksResult get() {
return CardActivity.this.ProgramakerApi.fetchCustomBlocks();
}
}, new Consumer<ProgramakerGetCustomBlocksResult>() {
@Override
public void apply(ProgramakerGetCustomBlocksResult result) {
if (result == null) {
Log.e("CARDActivity", "error retrieving custom blocks");
}
else {
Log.d("CARDActivity", "custom blocks: " + result.toString());
}
}
}));
// Hide action bar // Hide action bar
ActionBar actionBar = getSupportActionBar(); ActionBar actionBar = getSupportActionBar();
if (actionBar != null) { if (actionBar != null) {

View File

@ -0,0 +1,35 @@
package com.codigoparallevar.minicards;
import android.content.Context;
import android.content.SharedPreferences;
import android.view.View;
import static android.content.Context.MODE_PRIVATE;
public class ConfigManager {
private final Context context;
private static final String TOKEN_KEY = "PROGRAMAKER_API_TOKEN";
private static final String PREFERENCES_NAME = "MINICARDS_PREFERENCES";
public ConfigManager(Context ctx) {
this.context = ctx;
}
public void setToken(String token) {
SharedPreferences preferences = this.context.getSharedPreferences(PREFERENCES_NAME, MODE_PRIVATE);
SharedPreferences.Editor edit = preferences.edit();
edit.putString(TOKEN_KEY, token);
edit.commit();
}
public String getToken() {
SharedPreferences preferences = this.context.getSharedPreferences(PREFERENCES_NAME, MODE_PRIVATE);
if (!preferences.contains(TOKEN_KEY)) {
return null;
}
else {
return preferences.getString(TOKEN_KEY, null);
}
}
}

View File

@ -2,11 +2,10 @@ package com.codigoparallevar.minicards;
import android.app.Dialog; import android.app.Dialog;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import com.codigoparallevar.minicards.types.functional.Function; import com.codigoparallevar.minicards.types.functional.Consumer;
import com.codigoparallevar.minicards.types.functional.Tuple2; import com.codigoparallevar.minicards.types.functional.Tuple2;
import com.codigoparallevar.minicards.types.functional.Tuple3; import com.codigoparallevar.minicards.types.functional.Tuple3;
import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.floatingactionbutton.FloatingActionButton;
@ -32,13 +31,11 @@ import java.util.Vector;
import com.programaker.api.ProgramakerApi; import com.programaker.api.ProgramakerApi;
public class DeckPreviewActivity extends ReloadableAppCompatActivity { public class DeckPreviewActivity extends ReloadableAppCompatActivity {
private static final String TOKEN_KEY = "PROGRAMAKER_API_TOKEN";
private static final String PREFERENCES_NAME = "MINICARDS_PREFERENCES";
public static final String INTENT = "com.codigoparallevar.minicards.DECK"; public static final String INTENT = "com.codigoparallevar.minicards.DECK";
private ListView listView; private ListView listView;
private CardPreviewArrayAdapter cardArrayAdapter; private CardPreviewArrayAdapter cardArrayAdapter;
private ProgramakerApi ProgramakerApi = new ProgramakerApi(); private ProgramakerApi ProgramakerApi;
private ConfigManager Config;
protected void openLoginDialog(View view) { protected void openLoginDialog(View view) {
AlertDialog.Builder builder = new AlertDialog.Builder(this); AlertDialog.Builder builder = new AlertDialog.Builder(this);
@ -95,12 +92,13 @@ public class DeckPreviewActivity extends ReloadableAppCompatActivity {
new Tuple3<>( new Tuple3<>(
loginUsernameText.getText().toString(), loginUsernameText.getText().toString(),
loginPasswordText.getText().toString(), loginPasswordText.getText().toString(),
new Function<String>() { new Consumer<String>() {
public void apply(String token) { public void apply(String token) {
if (token == null) { if (token == null) {
messageLabel.setText(R.string.invalid_user_pass); messageLabel.setText(R.string.invalid_user_pass);
} else { } else {
DeckPreviewActivity.this.setToken(token); DeckPreviewActivity.this.Config.setToken(token);
DeckPreviewActivity.this.ProgramakerApi.setToken(token);
final Button loginToProgramakerButton = findViewById(R.id.login_in_programaker_button); final Button loginToProgramakerButton = findViewById(R.id.login_in_programaker_button);
loginToProgramakerButton.setVisibility(View.GONE); loginToProgramakerButton.setVisibility(View.GONE);
@ -116,30 +114,10 @@ public class DeckPreviewActivity extends ReloadableAppCompatActivity {
}); });
} }
private void setToken(String token) { static class CheckLogin extends AsyncTask<Tuple3<String, String, Consumer<String>>,
SharedPreferences preferences = getSharedPreferences(PREFERENCES_NAME, MODE_PRIVATE); Void, Tuple2<String, Consumer<String>>>{
SharedPreferences.Editor edit = preferences.edit();
edit.putString(TOKEN_KEY, token);
edit.commit();
this.ProgramakerApi.setToken(token);
}
private String getToken() {
SharedPreferences preferences = getSharedPreferences(PREFERENCES_NAME, MODE_PRIVATE);
if (!preferences.contains(TOKEN_KEY)) {
return null;
}
else {
return preferences.getString(TOKEN_KEY, null);
}
}
static class CheckLogin extends AsyncTask<Tuple3<String, String, Function<String>>,
Void, Tuple2<String, Function<String>>>{
@Override @Override
protected Tuple2<String, Function<String>> doInBackground(Tuple3<String, String, Function<String>>... tuples) { protected Tuple2<String, Consumer<String>> doInBackground(Tuple3<String, String, Consumer<String>>... tuples) {
ProgramakerApi api = new ProgramakerApi(); ProgramakerApi api = new ProgramakerApi();
boolean logged = false; boolean logged = false;
String token = null; String token = null;
@ -149,11 +127,11 @@ public class DeckPreviewActivity extends ReloadableAppCompatActivity {
catch (Exception e) { catch (Exception e) {
Log.e("Login to PrograMaker", e.toString()); Log.e("Login to PrograMaker", e.toString());
} }
return new Tuple2<String, Function<String>>(token, tuples[0]._z); return new Tuple2<String, Consumer<String>>(token, tuples[0]._z);
} }
@Override @Override
protected void onPostExecute(Tuple2<String, Function<String>> result) { protected void onPostExecute(Tuple2<String, Consumer<String>> result) {
result.item2.apply(result.item1); result.item2.apply(result.item1);
} }
} }
@ -175,6 +153,10 @@ public class DeckPreviewActivity extends ReloadableAppCompatActivity {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
this.Config = new ConfigManager(this);
this.ProgramakerApi = new ProgramakerApi();
setContentView(R.layout.activity_deck_preview); setContentView(R.layout.activity_deck_preview);
Toolbar toolbar = findViewById(R.id.toolbar); Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
@ -196,17 +178,16 @@ public class DeckPreviewActivity extends ReloadableAppCompatActivity {
}); });
listView = findViewById(R.id.card_deck_list); listView = findViewById(R.id.card_deck_list);
String token = this.Config.getToken();
String token = getToken();
if (token == null) { if (token == null) {
loginButton.setVisibility(View.VISIBLE); loginButton.setVisibility(View.VISIBLE);
} }
else { else {
loginButton.setVisibility(View.GONE);
this.ProgramakerApi.setToken(token); this.ProgramakerApi.setToken(token);
// Double check that is not needed loginButton.setVisibility(View.GONE);
// Double check that is not needed, token might have been deleted
new CheckNeededLoginButton().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, new CheckNeededLoginButton().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
new Tuple2<>(this.ProgramakerApi, loginButton)); new Tuple2<>(this.ProgramakerApi, loginButton));
} }
} }

View File

@ -1,5 +1,5 @@
package com.codigoparallevar.minicards.types.functional; package com.codigoparallevar.minicards.types.functional;
public abstract class Function<T> { public abstract class Consumer<T> {
public abstract void apply(T param); public abstract void apply(T param);
} }

View File

@ -0,0 +1,5 @@
package com.codigoparallevar.minicards.types.functional;
public abstract class Producer<T> {
public abstract T get();
}

View File

@ -0,0 +1,29 @@
package com.codigoparallevar.minicards.ui_helpers;
import android.os.AsyncTask;
import android.util.Log;
import com.codigoparallevar.minicards.types.functional.Consumer;
import com.codigoparallevar.minicards.types.functional.Producer;
import com.codigoparallevar.minicards.types.functional.Tuple2;
public class GetAsync<T> extends AsyncTask<Tuple2<Producer<T>, Consumer<T>>,
Void,
Tuple2<T, Consumer<T>>> {
@Override
protected Tuple2<T, Consumer<T>> doInBackground(Tuple2<Producer<T>, Consumer<T>>... data) {
try {
return new Tuple2<>(data[0].item1.get(), data[0].item2);
}
catch (Exception ex) {
Log.e("GetAsync", ex.toString());
return new Tuple2<>(null, data[0].item2);
}
}
@Override
protected void onPostExecute(Tuple2<T, Consumer<T>> result) {
result.item2.apply(result.item1);
}
}

View File

@ -3,6 +3,15 @@ package com.programaker.api
import android.os.Build import android.os.Build
import android.util.Log import android.util.Log
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.programaker.api.data.ProgramakerCustomBlock
import com.programaker.api.data.api_results.ProgramakerCheckResult
import com.programaker.api.data.api_results.ProgramakerGetCustomBlocksResult
import com.programaker.api.data.api_results.ProgramakerGetCustomBlocksResultTypeAdapter
import com.programaker.api.data.api_results.ProgramakerLoginResult
import com.programaker.api.exceptions.ProgramakerLoginRequiredException
import com.programaker.api.exceptions.ProgramakerProtocolException
import com.programaker.api.exceptions.TokenNotFoundException
import org.json.JSONObject import org.json.JSONObject
import java.io.DataOutputStream import java.io.DataOutputStream
import java.io.FileNotFoundException import java.io.FileNotFoundException
@ -16,6 +25,9 @@ import java.net.URLConnection
class ProgramakerApi(private val ApiRoot: String="https://programaker.com/api") { class ProgramakerApi(private val ApiRoot: String="https://programaker.com/api") {
private val LogTag: String = "ProgramakerApi" private val LogTag: String = "ProgramakerApi"
private var userId: String? = null
private var userName: String? = null
var token: String? = null var token: String? = null
// API // API
@ -41,9 +53,16 @@ class ProgramakerApi(private val ApiRoot: String="https://programaker.com/api")
ex.logError(LogTag) ex.logError(LogTag)
return false return false
} }
if (result.success) {
this.userId = result.user_id;
this.userName = result.username;
}
return result.success return result.success
} }
fun login(user: String, password: String): String { fun login(user: String, password: String): String {
val conn = URL(getLoginUrl()).openConnection() as HttpURLConnection val conn = URL(getLoginUrl()).openConnection() as HttpURLConnection
conn.setRequestProperty("Content-Type", "application/json") conn.setRequestProperty("Content-Type", "application/json")
@ -65,6 +84,31 @@ class ProgramakerApi(private val ApiRoot: String="https://programaker.com/api")
return result.token return result.token
} }
fun fetchCustomBlocks(): ProgramakerGetCustomBlocksResult {
val conn = URL(getCustomBlocksUrl()).openConnection() as HttpURLConnection
addAuthHeader(conn)
val result: ProgramakerGetCustomBlocksResult
try {
val builder = GsonBuilder()
builder.registerTypeAdapter(ProgramakerGetCustomBlocksResult::class.java, ProgramakerGetCustomBlocksResultTypeAdapter())
val gson = builder.create()
val reader = InputStreamReader(conn.inputStream)
result = gson.fromJson(reader, ProgramakerGetCustomBlocksResult::class.java)
} catch(ex: JsonParseException) {
ex.logError(LogTag)
throw ProgramakerProtocolException()
} catch (ex: FileNotFoundException) {
ex.logError(LogTag)
throw ProgramakerProtocolException()
}
return result
}
// Initialization // Initialization
init { init {
// Disable connection reuse if necessary // Disable connection reuse if necessary
@ -83,6 +127,21 @@ class ProgramakerApi(private val ApiRoot: String="https://programaker.com/api")
return "$ApiRoot/v0/sessions/login" return "$ApiRoot/v0/sessions/login"
} }
private fun getCustomBlocksUrl(): String {
if (userName == null) {
if (token == null) {
throw ProgramakerLoginRequiredException()
}
this.check();
if (userName == null) {
throw ProgramakerProtocolException()
}
}
return "$ApiRoot/v0/users/$userName/custom-blocks/"
}
private fun addAuthHeader(conn: URLConnection) { private fun addAuthHeader(conn: URLConnection) {
if (token != null) { if (token != null) {
conn.setRequestProperty("Authorization", token) conn.setRequestProperty("Authorization", token)

View File

@ -1,5 +0,0 @@
package com.programaker.api
class ProgramakerCheckResult(val success: Boolean) {
}

View File

@ -1,4 +0,0 @@
package com.programaker.api
class TokenNotFoundException : ProgramakerConfigurationException() {
}

View File

@ -0,0 +1,14 @@
package com.programaker.api.data
class ProgramakerCustomBlock(
val id: String,
val service_port_id: String,
val block_id: String,
val block_type: String,
val block_result_type: String,
val function_name: String,
val message: String
// val arguments: BlockArgument[],
// val save_to: undefined | string,
) {
}

View File

@ -0,0 +1,6 @@
package com.programaker.api.data.api_results
import com.programaker.api.data.ProgramakerCustomBlock
class ProgramakerBridgeCustomBlockResult(val bridge_id: String, val blocks: List<ProgramakerCustomBlock>) {
}

View File

@ -0,0 +1,4 @@
package com.programaker.api.data.api_results
class ProgramakerCheckResult(val success: Boolean, val user_id: String, val username: String) {
}

View File

@ -0,0 +1,4 @@
package com.programaker.api.data.api_results
class ProgramakerGetCustomBlocksResult(val result: List<ProgramakerBridgeCustomBlockResult>) {
}

View File

@ -0,0 +1,34 @@
package com.programaker.api.data.api_results
import com.google.gson.*
import com.programaker.api.data.ProgramakerCustomBlock
import java.lang.reflect.Type
import java.util.*
class ProgramakerGetCustomBlocksResultTypeAdapter : JsonSerializer<ProgramakerGetCustomBlocksResult?>, JsonDeserializer<ProgramakerGetCustomBlocksResult> {
var gson = Gson()
override fun serialize(customBlock: ProgramakerGetCustomBlocksResult?, typeOfT: Type, context: JsonSerializationContext): JsonElement {
throw NotImplementedError()
}
@Throws(JsonParseException::class)
override fun deserialize(element: JsonElement, typeOfT: Type, context: JsonDeserializationContext): ProgramakerGetCustomBlocksResult {
val json = element.asJsonObject
val bridges: MutableList<ProgramakerBridgeCustomBlockResult> = LinkedList()
for ((bridgeId, value1) in json.entrySet()) {
val blocks: MutableList<ProgramakerCustomBlock> = LinkedList()
for (value in value1.asJsonArray) {
val block = gson.fromJson(value, ProgramakerCustomBlock::class.java)
blocks.add(block)
}
val bridge = ProgramakerBridgeCustomBlockResult(bridgeId, blocks)
bridges.add(bridge)
}
return ProgramakerGetCustomBlocksResult(bridges)
}
}

View File

@ -1,4 +1,4 @@
package com.programaker.api package com.programaker.api.data.api_results
class ProgramakerLoginResult(val user_id: String, val token: String, val success: Boolean) { class ProgramakerLoginResult(val user_id: String, val token: String, val success: Boolean) {

View File

@ -1,4 +1,4 @@
package com.programaker.api package com.programaker.api.exceptions
open class ProgramakerConfigurationException : Exception() { open class ProgramakerConfigurationException : Exception() {

View File

@ -0,0 +1,5 @@
package com.programaker.api.exceptions
class ProgramakerLoginRequiredException : Throwable() {
}

View File

@ -1,4 +1,4 @@
package com.programaker.api package com.programaker.api.exceptions
class ProgramakerNetworkException : Throwable() { class ProgramakerNetworkException : Throwable() {

View File

@ -0,0 +1,5 @@
package com.programaker.api.exceptions
class ProgramakerProtocolException : Throwable() {
}

View File

@ -0,0 +1,6 @@
package com.programaker.api.exceptions
import com.programaker.api.exceptions.ProgramakerConfigurationException
class TokenNotFoundException : ProgramakerConfigurationException() {
}