Compare commits
24 Commits
experiment
...
led-activa
Author | SHA1 | Date | |
---|---|---|---|
|
ec3b6b58cc | ||
|
c60ec32e05 | ||
|
e1895caa9d | ||
|
ac26c0d721 | ||
|
ca0fd6a529 | ||
|
41da29f669 | ||
|
3f24489138 | ||
|
188f3290cf | ||
|
da3dfd5d2e | ||
|
a9b5c8f02b | ||
|
acb0dbec05 | ||
|
7f2686166e | ||
|
2c88dd4b03 | ||
|
de2d474294 | ||
|
4d477cfa5d | ||
|
b6c712860d | ||
|
8507e868d8 | ||
|
cca4b70b90 | ||
|
7f5aea94ec | ||
|
033b79cba1 | ||
|
1cdc679c70 | ||
|
9fb10281cb | ||
|
a02d372b90 | ||
|
ac76c7d369 |
45
Makefile
Normal file
@ -0,0 +1,45 @@
|
||||
|
||||
ROUND_ICONS=app/src/main/res/mipmap-mdpi/ic_launcher_round.png \
|
||||
app/src/main/res/mipmap-hdpi/ic_launcher_round.png \
|
||||
app/src/main/res/mipmap-xhdpi/ic_launcher_round.png \
|
||||
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
|
||||
|
||||
RECT_ICONS=app/src/main/res/mipmap-mdpi/ic_launcher.png \
|
||||
app/src/main/res/mipmap-hdpi/ic_launcher.png \
|
||||
app/src/main/res/mipmap-xhdpi/ic_launcher.png \
|
||||
app/src/main/res/mipmap-xxhdpi/ic_launcher.png
|
||||
|
||||
ICONS=$(ROUND_ICONS) $(RECT_ICONS)
|
||||
RECT_BASE=app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
|
||||
ROUND_BASE=app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
|
||||
BASE_ICONS=$(RECT_BASE) $(ROUND_BASE)
|
||||
|
||||
.PHONY: all
|
||||
|
||||
all: $(ICONS)
|
||||
|
||||
app/src/main/res/mipmap-mdpi/ic_launcher_round.png: $(ROUND_BASE)
|
||||
convert $< -resize 48x48 $@
|
||||
|
||||
app/src/main/res/mipmap-hdpi/ic_launcher_round.png: $(ROUND_BASE)
|
||||
convert $< -resize 72x72 $@
|
||||
|
||||
app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: $(ROUND_BASE)
|
||||
convert $< -resize 96x96 $@
|
||||
|
||||
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: $(ROUND_BASE)
|
||||
convert $< -resize 144x144 $@
|
||||
|
||||
app/src/main/res/mipmap-mdpi/ic_launcher.png: $(RECT_BASE)
|
||||
convert $< -resize 48x48 $@
|
||||
|
||||
app/src/main/res/mipmap-hdpi/ic_launcher.png: $(RECT_BASE)
|
||||
convert $< -resize 72x72 $@
|
||||
|
||||
app/src/main/res/mipmap-xhdpi/ic_launcher.png: $(RECT_BASE)
|
||||
convert $< -resize 96x96 $@
|
||||
|
||||
app/src/main/res/mipmap-xxhdpi/ic_launcher.png: $(RECT_BASE)
|
||||
convert $< -resize 144x144 $@
|
||||
|
||||
|
@ -1,15 +1,17 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion 25
|
||||
compileSdkVersion 29
|
||||
buildToolsVersion "27.0.1"
|
||||
defaultConfig {
|
||||
applicationId "com.codigoparallevar.minicards"
|
||||
minSdkVersion 15
|
||||
targetSdkVersion 25
|
||||
targetSdkVersion 29
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
@ -17,18 +19,31 @@ android {
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = 1.8
|
||||
targetCompatibility = 1.8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
|
||||
implementation 'androidx.annotation:annotation:1.0.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
|
||||
androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', {
|
||||
exclude group: 'com.android.support', module: 'support-annotations'
|
||||
})
|
||||
implementation 'com.android.support:appcompat-v7:25.4.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.0.0'
|
||||
testImplementation 'junit:junit:4.12'
|
||||
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
|
||||
implementation 'com.android.support:design:25.4.0'
|
||||
compile 'com.getbase:floatingactionbutton:1.10.1'
|
||||
compile 'com.larswerkman:HoloColorPicker:1.5'
|
||||
implementation 'com.android.support:cardview-v7:25.4.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
implementation 'com.google.android.material:material:1.0.0'
|
||||
implementation 'com.getbase:floatingactionbutton:1.10.1'
|
||||
implementation 'com.larswerkman:HoloColorPicker:1.5'
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation 'com.google.code.gson:gson:2.8.6'
|
||||
|
||||
implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.7.2'
|
||||
}
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
package com.codigoparallevar.minicards;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
@ -3,11 +3,14 @@
|
||||
package="com.codigoparallevar.minicards">
|
||||
|
||||
<!-- OpenGL ES 2.0 -->
|
||||
|
||||
<uses-feature
|
||||
android:glEsVersion="0x00020000"
|
||||
android:required="true" />
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <!-- For Programaker bridge -->
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
@ -15,9 +18,15 @@
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
<service
|
||||
android:name=".bridge.ProgramakerBridgeService"
|
||||
android:enabled="true"
|
||||
android:exported="true" />
|
||||
|
||||
<activity android:name=".CardActivity">
|
||||
<intent-filter>
|
||||
<action android:name="com.codigoparallevar.minicards.CARD" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
@ -26,10 +35,12 @@
|
||||
android:theme="@style/AppTheme.NoActionBar">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="com.codigoparallevar.minicards.DECK" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
@ -4,27 +4,29 @@ import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.codigoparallevar.minicards.motion.MotionMode;
|
||||
import com.codigoparallevar.minicards.types.Part;
|
||||
import com.codigoparallevar.minicards.types.PartConnection;
|
||||
import com.codigoparallevar.minicards.types.PartGrid;
|
||||
import com.codigoparallevar.minicards.types.Position;
|
||||
import com.codigoparallevar.minicards.types.Selectable;
|
||||
import com.codigoparallevar.minicards.types.Tuple2;
|
||||
import com.codigoparallevar.minicards.types.Tuple4;
|
||||
import com.codigoparallevar.minicards.types.connectors.input.AnyInputConnector;
|
||||
import com.codigoparallevar.minicards.types.connectors.input.BooleanInputConnector;
|
||||
import com.codigoparallevar.minicards.types.connectors.input.InputConnector;
|
||||
import com.codigoparallevar.minicards.types.connectors.input.SignalInputConnector;
|
||||
import com.codigoparallevar.minicards.types.connectors.input.StringInputConnector;
|
||||
import com.codigoparallevar.minicards.types.connectors.output.OutputConnector;
|
||||
import com.codigoparallevar.minicards.types.functional.Tuple2;
|
||||
import com.codigoparallevar.minicards.types.functional.Tuple4;
|
||||
import com.programaker.api.ProgramakerApi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
@ -32,7 +34,7 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
class CanvasView extends View implements PartGrid {
|
||||
public class CanvasView extends View implements PartGrid {
|
||||
|
||||
@NonNull
|
||||
List<Part> parts = new ArrayList<>();
|
||||
@ -51,6 +53,7 @@ class CanvasView extends View implements PartGrid {
|
||||
|
||||
@NonNull
|
||||
private String name = "default";
|
||||
private ProgramakerApi api = null;
|
||||
private final static float touchTimeForLongTouchInMillis = 500;
|
||||
private boolean _isDragging = false;
|
||||
private CardActivity parentActivity = null;
|
||||
@ -142,7 +145,7 @@ class CanvasView extends View implements PartGrid {
|
||||
part.draw(scrolledCanvas, _devMode);
|
||||
}
|
||||
|
||||
Log.d("Render time", System.currentTimeMillis() - renderStartTime + "ms");
|
||||
// Log.d("Render time", System.currentTimeMillis() - renderStartTime + "ms");
|
||||
}
|
||||
|
||||
private void drawBackground(ScrolledCanvas canvas) {
|
||||
@ -236,6 +239,7 @@ class CanvasView extends View implements PartGrid {
|
||||
break;
|
||||
}
|
||||
|
||||
Log.d("CanvasView", "Moving part="+selectedPart);
|
||||
if (selectedPart == null){
|
||||
int xMovement = _mouseDownPoint.item1 - xInScreen;
|
||||
int yMovement = _mouseDownPoint.item2 - yInScreen;
|
||||
@ -246,7 +250,7 @@ class CanvasView extends View implements PartGrid {
|
||||
_mouseDownPoint = new Tuple2(xInScreen, yInScreen);
|
||||
}
|
||||
else {
|
||||
Log.i("Canvas", "X: " + xInScreen + " Y: " + yInScreen
|
||||
Log.d("Canvas", "X: " + xInScreen + " Y: " + yInScreen
|
||||
+ " in drop zone " + _dropToRemoveZone + " : "
|
||||
+ inDropZone(xInScreen, yInScreen));
|
||||
|
||||
@ -323,15 +327,22 @@ class CanvasView extends View implements PartGrid {
|
||||
for (int i = parts.size() - 1; i >= 0; i--){
|
||||
final Part part = parts.get(i);
|
||||
// First try with output connectors
|
||||
for (OutputConnector outputConnector : part.getOutputConnectors()){
|
||||
if (outputConnector.containsPoint(x, y)){
|
||||
return outputConnector;
|
||||
List<OutputConnector> outputConnectors = part.getOutputConnectors();
|
||||
if (outputConnectors != null) {
|
||||
for (OutputConnector outputConnector : outputConnectors) {
|
||||
if (outputConnector.containsPoint(x, y)) {
|
||||
return outputConnector;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Then with input ones
|
||||
for (InputConnector inputConnector : part.getInputConnectors()){
|
||||
if (inputConnector.containsPoint(x, y)){
|
||||
return inputConnector;
|
||||
List<InputConnector> inputConnectors = part.getInputConnectors();
|
||||
if (inputConnectors != null) {
|
||||
for (InputConnector inputConnector : inputConnectors) {
|
||||
if (inputConnector.containsPoint(x, y)) {
|
||||
return inputConnector;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -339,6 +350,15 @@ class CanvasView extends View implements PartGrid {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProgramakerApi getApi() {
|
||||
if (api == null) {
|
||||
api = new ProgramakerApi();
|
||||
api.setToken(new ConfigManager(this.getContext()).getToken());
|
||||
}
|
||||
return api;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public SignalInputConnector getSignalInputConnectorOn(int x, int y) {
|
||||
|
@ -2,15 +2,27 @@ package com.codigoparallevar.minicards;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.codigoparallevar.minicards.toolbox.PartsHolder;
|
||||
import com.codigoparallevar.minicards.types.functional.Tuple2;
|
||||
import com.codigoparallevar.minicards.types.functional.Tuple3;
|
||||
import com.codigoparallevar.minicards.ui_helpers.GetAsync;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.programaker.api.ProgramakerApi;
|
||||
import com.programaker.api.data.ProgramakerBridgeInfo;
|
||||
import com.programaker.api.data.api_results.ProgramakerBridgeCustomBlockResult;
|
||||
import com.programaker.api.data.api_results.ProgramakerGetCustomBlocksResult;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
public class CardActivity extends AppCompatActivity {
|
||||
|
||||
@ -18,7 +30,7 @@ public class CardActivity extends AppCompatActivity {
|
||||
public static final String CARD_PATH_KEY = "CARD_PATH";
|
||||
public static final String VISUALIZATION_MODE_KEY = "VISUALIZATION_MODE";
|
||||
public static final String DEVELOPER_VISUALIZATION_MODE = "DEVELOPER_VISUALIZATION_MODE";
|
||||
|
||||
public static final String USER_VISUALIZATION_MODE = "USER_VISUALIZATION_MODE";
|
||||
|
||||
CanvasView canvasView;
|
||||
com.getbase.floatingactionbutton.AddFloatingActionButton AddPartButton;
|
||||
@ -34,11 +46,35 @@ public class CardActivity extends AppCompatActivity {
|
||||
boolean devMode = false;
|
||||
FloatingActionButton removePartFab;
|
||||
private PartsHolder partsHolder;
|
||||
private ProgramakerApi ProgramakerApi;
|
||||
private ConfigManager Config;
|
||||
private ProgramakerGetCustomBlocksResult CustomBlocks = null;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle 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);
|
||||
}
|
||||
|
||||
partsHolder = new PartsHolder(this);
|
||||
|
||||
new GetAsync<Tuple2<List<ProgramakerBridgeInfo>, List<ProgramakerBridgeCustomBlockResult>>>()
|
||||
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
|
||||
new Tuple3<>(() ->
|
||||
new Tuple2<>(
|
||||
CardActivity.this.ProgramakerApi.fetchConnectedBridges(),
|
||||
CardActivity.this.ProgramakerApi.fetchCustomBlocks()),
|
||||
result -> {
|
||||
partsHolder.addCustomBlocks(result.item1, result.item2);
|
||||
Log.d("CARDActivity", "custom blocks: " + result.toString());
|
||||
},
|
||||
exception -> Log.e("CARDActivity", "error retrieving custom blocks: " + exception.toString())));
|
||||
|
||||
// Hide action bar
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
@ -52,7 +88,6 @@ public class CardActivity extends AppCompatActivity {
|
||||
canvasView.setParentActivity(this);
|
||||
|
||||
// Initialize auxiliary elements
|
||||
partsHolder = new PartsHolder(this);
|
||||
|
||||
removePartFab = (FloatingActionButton) findViewById(R.id.remove_part_fab);
|
||||
canvasView.setDropZone(
|
||||
@ -97,8 +132,7 @@ public class CardActivity extends AppCompatActivity {
|
||||
ShowDeckFromDevModeButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent i = new Intent(DeckPreviewActivity.INTENT);
|
||||
CardActivity.this.startActivity(i);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
@ -107,8 +141,7 @@ public class CardActivity extends AppCompatActivity {
|
||||
ShowDeckFromUserModeButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent i = new Intent(DeckPreviewActivity.INTENT);
|
||||
CardActivity.this.startActivity(i);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
@ -196,12 +229,12 @@ public class CardActivity extends AppCompatActivity {
|
||||
if (canvasView.isDragging()){
|
||||
devFabMenu.setVisibility(View.GONE);
|
||||
userFabMenu.setVisibility(View.GONE);
|
||||
removePartFab.setVisibility(View.VISIBLE);
|
||||
((View)removePartFab).setVisibility(View.VISIBLE);
|
||||
Log.d("Main", "Changing visibility!");
|
||||
}
|
||||
else {
|
||||
this.setDevMode(devMode);
|
||||
removePartFab.setVisibility(View.GONE);
|
||||
((View)removePartFab).setVisibility(View.GONE);
|
||||
Log.d("Main", "Now changing visibility!");
|
||||
}
|
||||
canvasView.setDropZone(
|
||||
@ -211,8 +244,16 @@ public class CardActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
public static void openCard(Context context, CardFile cardfile, String visualizationMode) {
|
||||
openCardByPath(context, cardfile.getPath(), visualizationMode);
|
||||
}
|
||||
|
||||
public static void openCard(Context context, PreviewCard cardfile, String visualizationMode) {
|
||||
openCardByPath(context, cardfile.getPath(), visualizationMode);
|
||||
}
|
||||
|
||||
private static void openCardByPath(Context context, String path, String visualizationMode) {
|
||||
Intent i = new Intent(INTENT);
|
||||
i.putExtra(CARD_PATH_KEY, cardfile.getPath());
|
||||
i.putExtra(CARD_PATH_KEY, path);
|
||||
if (visualizationMode != null) {
|
||||
i.putExtra(VISUALIZATION_MODE_KEY, visualizationMode);
|
||||
}
|
||||
|
@ -4,9 +4,11 @@ import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.codigoparallevar.minicards.parts.ProgramakerCustomBlockPart;
|
||||
import com.codigoparallevar.minicards.parts.buttons.RoundButton;
|
||||
import com.codigoparallevar.minicards.parts.logic.Ticker;
|
||||
import com.codigoparallevar.minicards.parts.logic.Toggle;
|
||||
@ -16,7 +18,7 @@ import com.codigoparallevar.minicards.parts.strings.ConvertToString;
|
||||
import com.codigoparallevar.minicards.types.Part;
|
||||
import com.codigoparallevar.minicards.types.PartConnection;
|
||||
import com.codigoparallevar.minicards.types.PartGrid;
|
||||
import com.codigoparallevar.minicards.types.Tuple2;
|
||||
import com.codigoparallevar.minicards.types.functional.Tuple2;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
@ -34,7 +36,7 @@ import java.util.UUID;
|
||||
|
||||
public class CardFile {
|
||||
|
||||
static final int DEFAULT_BACKGROUND_COLOR = Color.parseColor("#044563");
|
||||
static final int DEFAULT_BACKGROUND_COLOR = Color.parseColor("#4485A3");
|
||||
static final String PATH_SEPARATOR = "/";
|
||||
@NonNull
|
||||
private final String cardsDirectory;
|
||||
@ -284,6 +286,13 @@ public class CardFile {
|
||||
|
||||
return convertToStringInfo;
|
||||
}
|
||||
else if (type.equals(ProgramakerCustomBlockPart.class.getName())){
|
||||
Tuple2<Part, List<PartConnection>> customBlockPartInfo = ProgramakerCustomBlockPart.deserialize(
|
||||
grid,
|
||||
jsonObject.getJSONObject("_data"));
|
||||
|
||||
return customBlockPartInfo;
|
||||
}
|
||||
else {
|
||||
throw new JSONException("Expected known class, found " + type);
|
||||
}
|
||||
|
@ -4,9 +4,9 @@ import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.CardView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.cardview.widget.CardView;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@ -17,6 +17,8 @@ import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
class CardPreviewArrayAdapter extends ArrayAdapter<PreviewCard> {
|
||||
@ -41,16 +43,18 @@ class CardPreviewArrayAdapter extends ArrayAdapter<PreviewCard> {
|
||||
final View row = inflater.inflate(R.layout.card_preview, parent, false);
|
||||
final PreviewCard card = this.cards[position];
|
||||
|
||||
row.setOnClickListener(new View.OnClickListener() {
|
||||
TextView nameView = row.findViewById(R.id.card_preview_name);
|
||||
final CardView cardView = row.findViewById(R.id.card_preview_card);
|
||||
final FloatingActionButton settingsButton = row.findViewById(R.id.card_preview_settings_button);
|
||||
|
||||
cardView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent i = new Intent(CardActivity.INTENT);
|
||||
i.putExtra(CardActivity.CARD_PATH_KEY, card.getPath());
|
||||
CardPreviewArrayAdapter.this.getContext().startActivity(i);
|
||||
CardActivity.openCard(getContext(), card, CardActivity.DEVELOPER_VISUALIZATION_MODE);
|
||||
}
|
||||
});
|
||||
|
||||
final ImageView settingsButton = (ImageView) row.findViewById(R.id.card_preview_settings_button);
|
||||
|
||||
settingsButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
@ -58,19 +62,8 @@ class CardPreviewArrayAdapter extends ArrayAdapter<PreviewCard> {
|
||||
}
|
||||
});
|
||||
|
||||
CardView cardView = (CardView) row.findViewById(R.id.card_preview_card);
|
||||
TextView nameView = (TextView) row.findViewById(R.id.card_preview_name);
|
||||
|
||||
int backgroundColor = card.getColor();
|
||||
cardView.setBackgroundColor(backgroundColor);
|
||||
nameView.setText(card.getName());
|
||||
if (backgroundColor == CardFile.DEFAULT_BACKGROUND_COLOR){
|
||||
nameView.setTextColor(0xFFF0F0F0);
|
||||
}
|
||||
else {
|
||||
nameView.setTextColor(0xFFFFFF ^ backgroundColor);
|
||||
}
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
@ -80,7 +73,7 @@ class CardPreviewArrayAdapter extends ArrayAdapter<PreviewCard> {
|
||||
final View openCardOptions = (LayoutInflater.from(getContext())
|
||||
.inflate(R.layout.card_settings_dialog, null));
|
||||
|
||||
final EditText cardNameEditText = (EditText) openCardOptions.findViewById(R.id.card_setting_name_edit_text);
|
||||
final EditText cardNameEditText = openCardOptions.findViewById(R.id.card_setting_name_edit_text);
|
||||
cardNameEditText.setText(card.getName());
|
||||
|
||||
builder.setTitle("Card settings")
|
||||
@ -115,7 +108,7 @@ class CardPreviewArrayAdapter extends ArrayAdapter<PreviewCard> {
|
||||
|
||||
final Dialog dialog = builder.create();
|
||||
|
||||
final TextView deleteCardLink = (TextView) openCardOptions.findViewById(R.id.card_setting_delete_card);
|
||||
final TextView deleteCardLink = openCardOptions.findViewById(R.id.card_setting_delete_card);
|
||||
deleteCardLink.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
|
@ -0,0 +1,66 @@
|
||||
package com.codigoparallevar.minicards;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import static android.content.Context.MODE_PRIVATE;
|
||||
|
||||
public class ConfigManager {
|
||||
private final Context context;
|
||||
private static final String PREFERENCES_NAME = "MINICARDS_PREFERENCES";
|
||||
private static final String TOKEN_KEY = "PROGRAMAKER_API_TOKEN";
|
||||
private static final String BRIDGE_ID_KEY = "PROGRAMAKER_BRIDGE_ID";
|
||||
private static final String BRIDGE_CONNECTION_ID_KEY = "PROGRAMAKER_BRIDGE_CONNECTION_ID";
|
||||
|
||||
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.apply();
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
SharedPreferences preferences = this.context.getSharedPreferences(PREFERENCES_NAME, MODE_PRIVATE);
|
||||
return preferences.getString(TOKEN_KEY, null);
|
||||
}
|
||||
|
||||
public void removeBridgeId() {
|
||||
SharedPreferences preferences = this.context.getSharedPreferences(PREFERENCES_NAME, MODE_PRIVATE);
|
||||
SharedPreferences.Editor edit = preferences.edit();
|
||||
|
||||
if (preferences.contains(BRIDGE_ID_KEY)) {
|
||||
edit.remove(BRIDGE_ID_KEY).apply();
|
||||
}
|
||||
}
|
||||
|
||||
public void setBridgeId(String bridgeId) {
|
||||
SharedPreferences preferences = this.context.getSharedPreferences(PREFERENCES_NAME, MODE_PRIVATE);
|
||||
SharedPreferences.Editor edit = preferences.edit();
|
||||
|
||||
edit.putString(BRIDGE_ID_KEY, bridgeId);
|
||||
edit.commit();
|
||||
}
|
||||
|
||||
public String getBridgeId() {
|
||||
SharedPreferences preferences = this.context.getSharedPreferences(PREFERENCES_NAME, MODE_PRIVATE);
|
||||
return preferences.getString(BRIDGE_ID_KEY, null);
|
||||
}
|
||||
|
||||
public void setBridgeConnectionId(String connectionId) {
|
||||
SharedPreferences preferences = this.context.getSharedPreferences(PREFERENCES_NAME, MODE_PRIVATE);
|
||||
SharedPreferences.Editor edit = preferences.edit();
|
||||
|
||||
edit.putString(BRIDGE_CONNECTION_ID_KEY, connectionId);
|
||||
edit.commit();
|
||||
}
|
||||
|
||||
public String getBridgeConnectionId() {
|
||||
SharedPreferences preferences = this.context.getSharedPreferences(PREFERENCES_NAME, MODE_PRIVATE);
|
||||
return preferences.getString(BRIDGE_CONNECTION_ID_KEY, null);
|
||||
}
|
||||
}
|
@ -2,36 +2,158 @@ package com.codigoparallevar.minicards;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import com.codigoparallevar.minicards.bridge.ProgramakerBridgeService;
|
||||
import com.codigoparallevar.minicards.types.functional.Consumer;
|
||||
import com.codigoparallevar.minicards.types.functional.Tuple2;
|
||||
import com.codigoparallevar.minicards.types.functional.Tuple3;
|
||||
import com.codigoparallevar.minicards.ui_helpers.GetAsync;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.programaker.api.ProgramakerApi;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
public class DeckPreviewActivity extends ReloadableAppCompatActivity {
|
||||
|
||||
public static final String INTENT = "com.codigoparallevar.minicards.DECK";
|
||||
private static final String LogTag = "DeckPreview";
|
||||
|
||||
private ListView listView;
|
||||
private CardPreviewArrayAdapter cardArrayAdapter;
|
||||
private ProgramakerApi ProgramakerApi;
|
||||
private ConfigManager Config;
|
||||
|
||||
protected void openLoginDialog(View view) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
|
||||
final View loginDialog = (LayoutInflater.from(this)
|
||||
.inflate(R.layout.login_dialog_view, null));
|
||||
|
||||
final EditText loginUsernameText = loginDialog.findViewById(R.id.login_username_text);
|
||||
final EditText loginPasswordText = loginDialog.findViewById(R.id.login_password_text);
|
||||
final Button loginButton = loginDialog.findViewById(R.id.login_dialog_login_button);
|
||||
final Button cancelButton = loginDialog.findViewById(R.id.login_dialog_cancel_button);
|
||||
final TextView messageLabel = loginDialog.findViewById(R.id.login_message_label);
|
||||
|
||||
builder.setTitle("Login").setView(loginDialog);
|
||||
final Dialog dialog = builder.create();
|
||||
dialog.show();
|
||||
|
||||
cancelButton.setOnClickListener(new View.OnClickListener(){
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
dialog.cancel();
|
||||
}
|
||||
});
|
||||
|
||||
final TextWatcher watcher = (new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
if ((messageLabel.getVisibility() != View.VISIBLE) &&
|
||||
(loginUsernameText.getText().length() > 0) &&
|
||||
(loginPasswordText.getText().length() > 0)) {
|
||||
loginButton.setEnabled(true);
|
||||
} else {
|
||||
loginButton.setEnabled(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
loginButton.setEnabled(false);
|
||||
loginUsernameText.addTextChangedListener(watcher);
|
||||
loginPasswordText.addTextChangedListener(watcher);
|
||||
|
||||
loginButton.setOnClickListener(new View.OnClickListener(){
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
messageLabel.setVisibility(View.VISIBLE);
|
||||
messageLabel.setText(R.string.loading);
|
||||
new CheckLogin().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
|
||||
new Tuple3<>(
|
||||
loginUsernameText.getText().toString(),
|
||||
loginPasswordText.getText().toString(),
|
||||
token -> {
|
||||
if (token == null) {
|
||||
messageLabel.setText(R.string.invalid_user_pass);
|
||||
} else {
|
||||
DeckPreviewActivity.this.Config.setToken(token);
|
||||
DeckPreviewActivity.this.ProgramakerApi.setToken(token);
|
||||
final Button loginToProgramakerButton = findViewById(R.id.login_in_programaker_button);
|
||||
|
||||
loginToProgramakerButton.setVisibility(View.GONE);
|
||||
// Re-check... just in case
|
||||
checkNeededLoginButton(
|
||||
DeckPreviewActivity.this.ProgramakerApi,
|
||||
loginToProgramakerButton);
|
||||
dialog.cancel();
|
||||
}
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static class CheckLogin extends AsyncTask<Tuple3<String, String, Consumer<String>>,
|
||||
Void, Tuple2<String, Consumer<String>>>{
|
||||
@Override
|
||||
protected Tuple2<String, Consumer<String>> doInBackground(Tuple3<String, String, Consumer<String>>... tuples) {
|
||||
ProgramakerApi api = new ProgramakerApi();
|
||||
String token = null;
|
||||
try {
|
||||
token = api.login(tuples[0]._x, tuples[0]._y);
|
||||
}
|
||||
catch (Exception e) {
|
||||
Log.e("Login to PrograMaker", e.toString(), e);
|
||||
}
|
||||
return new Tuple2<>(token, tuples[0]._z);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Tuple2<String, Consumer<String>> result) {
|
||||
try {
|
||||
result.item2.apply(result.item1);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
Log.e(LogTag, "Error on login UI update", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
this.Config = new ConfigManager(this);
|
||||
this.ProgramakerApi = new ProgramakerApi();
|
||||
|
||||
setContentView(R.layout.activity_deck_preview);
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.create_new_card_fab);
|
||||
FloatingActionButton fab = findViewById(R.id.create_new_card_fab);
|
||||
fab.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
@ -39,7 +161,43 @@ public class DeckPreviewActivity extends ReloadableAppCompatActivity {
|
||||
}
|
||||
});
|
||||
|
||||
listView = (ListView) findViewById(R.id.card_deck_list);
|
||||
final Button loginButton = findViewById(R.id.login_in_programaker_button);
|
||||
loginButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
DeckPreviewActivity.this.openLoginDialog(v);
|
||||
}
|
||||
});
|
||||
|
||||
listView = findViewById(R.id.card_deck_list);
|
||||
String token = this.Config.getToken();
|
||||
if (token == null) {
|
||||
loginButton.setVisibility(View.VISIBLE);
|
||||
}
|
||||
else {
|
||||
|
||||
this.ProgramakerApi.setToken(token);
|
||||
loginButton.setVisibility(View.GONE);
|
||||
// Double check that is not needed, token might have been deleted
|
||||
checkNeededLoginButton(this.ProgramakerApi, loginButton);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkNeededLoginButton(ProgramakerApi api, Button loginButton) {
|
||||
new GetAsync<Boolean>().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
|
||||
new Tuple3<>(
|
||||
() -> api.check(),
|
||||
result -> {
|
||||
if (!result) {
|
||||
loginButton.setVisibility(View.VISIBLE);
|
||||
DeckPreviewActivity.this.Config.removeBridgeId();
|
||||
} else {
|
||||
Intent intent = new Intent(this, ProgramakerBridgeService.class);
|
||||
startService(intent);
|
||||
}
|
||||
},
|
||||
ex -> Log.e(LogTag, "Error checking API:" + ex, ex)
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -61,7 +219,7 @@ public class DeckPreviewActivity extends ReloadableAppCompatActivity {
|
||||
final View openCardOptions = (LayoutInflater.from(this)
|
||||
.inflate(R.layout.create_new_card_view, null));
|
||||
|
||||
final EditText cardNameEditText = (EditText) openCardOptions.findViewById(R.id.card_name_edit_text);
|
||||
final EditText cardNameEditText = openCardOptions.findViewById(R.id.card_name_edit_text);
|
||||
|
||||
builder.setTitle("Create a new card")
|
||||
.setView(openCardOptions)
|
||||
|
@ -2,7 +2,7 @@ package com.codigoparallevar.minicards;
|
||||
|
||||
import com.codigoparallevar.minicards.types.Part;
|
||||
import com.codigoparallevar.minicards.types.PartGrid;
|
||||
import com.codigoparallevar.minicards.types.Tuple2;
|
||||
import com.codigoparallevar.minicards.types.functional.Tuple2;
|
||||
|
||||
public abstract class PartInstantiator {
|
||||
protected abstract Part instantiate(PartGrid grid, Tuple2<Integer, Integer> center);
|
||||
|
@ -1,67 +0,0 @@
|
||||
package com.codigoparallevar.minicards;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.util.Log;
|
||||
|
||||
import com.codigoparallevar.minicards.parts.buttons.RoundButton;
|
||||
import com.codigoparallevar.minicards.parts.logic.Ticker;
|
||||
import com.codigoparallevar.minicards.parts.logic.Toggle;
|
||||
import com.codigoparallevar.minicards.parts.samples.ColorBox;
|
||||
import com.codigoparallevar.minicards.parts.strings.ConvertToString;
|
||||
import com.codigoparallevar.minicards.types.Part;
|
||||
import com.codigoparallevar.minicards.types.Tuple2;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
class PartsHolder {
|
||||
private final Context context;
|
||||
|
||||
private final static List<Tuple2<String, PartInstantiator>> BuiltInParts =
|
||||
new Vector<Tuple2<String, PartInstantiator>>(){{
|
||||
add(new Tuple2<>("Round button", RoundButton.getInstantiator()));
|
||||
add(new Tuple2<>("Ticker", Ticker.getInstantiator()));
|
||||
add(new Tuple2<>("Red/Green box", ColorBox.getInstantiator()));
|
||||
add(new Tuple2<>("Toggle", Toggle.getInstantiator()));
|
||||
add(new Tuple2<>("ToString", ConvertToString.getInstantiator()));
|
||||
}};
|
||||
|
||||
public PartsHolder(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public void openAddPartModal(final CanvasView canvasView) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
builder.setTitle("Choose part type")
|
||||
.setItems(getPartTypes(), new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if ((which >= 0) && (which < BuiltInParts.size())){
|
||||
Log.d("Minicards partsHolder",
|
||||
"Spawning " + BuiltInParts.get(which).item1);
|
||||
PartInstantiator instantiator = BuiltInParts.get(which).item2;
|
||||
PartsHolder.this.runInstantiator(instantiator, canvasView);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Dialog dialog = builder.create();
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
private void runInstantiator(PartInstantiator instantiator, CanvasView canvasView) {
|
||||
Part part = instantiator.build(canvasView);
|
||||
canvasView.addPart(part);
|
||||
}
|
||||
|
||||
private static String[] getPartTypes() {
|
||||
String[] partTypes = new String[BuiltInParts.size()];
|
||||
for (int i = 0; i < BuiltInParts.size(); i++){
|
||||
partTypes[i] = BuiltInParts.get(i).item1;
|
||||
}
|
||||
|
||||
return partTypes;
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
package com.codigoparallevar.minicards;
|
||||
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
abstract class ReloadableAppCompatActivity extends AppCompatActivity {
|
||||
public abstract void reload();
|
||||
|
@ -4,9 +4,9 @@ import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Rect;
|
||||
import android.support.annotation.NonNull;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.codigoparallevar.minicards.types.Tuple2;
|
||||
import com.codigoparallevar.minicards.types.functional.Tuple2;
|
||||
|
||||
public class ScrolledCanvas {
|
||||
private final Canvas canvas;
|
||||
|
@ -2,10 +2,11 @@ package com.codigoparallevar.minicards;
|
||||
|
||||
import com.codigoparallevar.minicards.types.PartGrid;
|
||||
import com.codigoparallevar.minicards.types.Selectable;
|
||||
import com.codigoparallevar.minicards.types.Tuple2;
|
||||
import com.codigoparallevar.minicards.types.functional.Tuple2;
|
||||
import com.codigoparallevar.minicards.types.connectors.input.BooleanInputConnector;
|
||||
import com.codigoparallevar.minicards.types.connectors.input.SignalInputConnector;
|
||||
import com.codigoparallevar.minicards.types.connectors.input.StringInputConnector;
|
||||
import com.programaker.api.ProgramakerApi;
|
||||
|
||||
class StubPartGrid implements PartGrid {
|
||||
@Override
|
||||
@ -13,6 +14,11 @@ class StubPartGrid implements PartGrid {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProgramakerApi getApi() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SignalInputConnector getSignalInputConnectorOn(int x, int y) {
|
||||
return null;
|
||||
|
@ -0,0 +1,51 @@
|
||||
package com.codigoparallevar.minicards.bridge;
|
||||
|
||||
import android.content.Context;
|
||||
import android.provider.Settings;
|
||||
|
||||
import com.codigoparallevar.minicards.ConfigManager;
|
||||
import com.codigoparallevar.minicards.bridge.blocks.DefaultAndroidBlocks;
|
||||
import com.programaker.bridge.ProgramakerBridge;
|
||||
import com.programaker.bridge.ProgramakerBridgeConfiguration;
|
||||
|
||||
public class ProgramakerAndroidBridge {
|
||||
private static final String LogTag = "PM Android Bridge";
|
||||
private ProgramakerBridge bridgeRunner = null;
|
||||
|
||||
// Static
|
||||
public static ProgramakerAndroidBridge configure(Context ctx, String userId, String bridgeId) {
|
||||
return new ProgramakerAndroidBridge(ctx, userId, bridgeId);
|
||||
}
|
||||
|
||||
public static String GetBridgeName(Context ctx) {
|
||||
String deviceName = Settings.Secure.getString(ctx.getContentResolver(), "bluetooth_name");
|
||||
String serviceName = "MiniCards on " + deviceName;
|
||||
return serviceName;
|
||||
}
|
||||
|
||||
// Builder
|
||||
private final Context ctx;
|
||||
private final String userId;
|
||||
private final String bridgeId;
|
||||
|
||||
private ProgramakerAndroidBridge(Context ctx, String userId, String bridgeId) {
|
||||
this.ctx = ctx;
|
||||
this.userId = userId;
|
||||
this.bridgeId = bridgeId;
|
||||
}
|
||||
|
||||
public void start(Runnable onReady, Runnable onComplete) {
|
||||
ProgramakerBridgeConfiguration configuration = new ProgramakerBridgeConfiguration(
|
||||
ProgramakerAndroidBridge.GetBridgeName(this.ctx),
|
||||
new ConfigManager(this.ctx),
|
||||
DefaultAndroidBlocks.GetBuilder(this.ctx).Build()
|
||||
);
|
||||
|
||||
this.bridgeRunner = new ProgramakerBridge(this.bridgeId, this.userId, configuration, onReady, onComplete);
|
||||
this.bridgeRunner.run();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
bridgeRunner.stop();
|
||||
}
|
||||
}
|
@ -0,0 +1,213 @@
|
||||
package com.codigoparallevar.minicards.bridge;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.IBinder;
|
||||
import android.os.Process;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
import com.codigoparallevar.minicards.ConfigManager;
|
||||
import com.codigoparallevar.minicards.R;
|
||||
import com.codigoparallevar.minicards.types.functional.Tuple2;
|
||||
import com.codigoparallevar.minicards.ui_helpers.DoAsync;
|
||||
import com.programaker.api.ProgramakerApi;
|
||||
|
||||
public class ProgramakerBridgeService extends Service {
|
||||
public static final String BridgeUserNotificationChannel = "PROGRAMAKER_BRIDGE_USER_NOTIFICATION";
|
||||
public static final CharSequence BridgeUserNotificationChannelName = "User notifications";
|
||||
public static String BridgeUserLedsNotificationChannel = "PROGRAMAKER_BRIDGE_USER_LEDS_NOTIFICATION";
|
||||
public static CharSequence BridgeUserLedsNotificationChannelName = "User LEDs";
|
||||
|
||||
private static String BridgeStatusNotificationChannel = "PROGRAMAKER_BRIDGE_STATUS_NOTIFICATION";
|
||||
private static CharSequence BridgeStatusNotificationChannelName = "Programaker bridge status";
|
||||
|
||||
private static final int BridgeStatusNotificationId = 9999;
|
||||
public static final String COMMAND_PROGRAMAKER_BRIDGE_STOP = "com.programaker.bridge.commands.stop";
|
||||
public static final String COMMAND_PROGRAMAKER_BRIDGE_START = "com.programaker.bridge.commands.start";
|
||||
|
||||
private static final long WAIT_TIME_BEFORE_RESTART_MILLIS = 10000; // 10s
|
||||
private ProgramakerAndroidBridge bridge = null;
|
||||
private static final String LogTag = "PM BridgeService";
|
||||
private boolean stopped = false;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
setBridgeStatusNotification(getString(R.string.bridge_service_not_started), true);
|
||||
}
|
||||
|
||||
private void setBridgeStatusNotification(String title, boolean stopped) {
|
||||
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
assert notificationManager != null;
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
||||
NotificationChannel channel = new NotificationChannel(
|
||||
ProgramakerBridgeService.BridgeStatusNotificationChannel,
|
||||
ProgramakerBridgeService.BridgeStatusNotificationChannelName,
|
||||
NotificationManager.IMPORTANCE_DEFAULT);
|
||||
notificationManager.createNotificationChannel(channel);
|
||||
}
|
||||
|
||||
Intent stopIntent = new Intent(this, ProgramakerBridgeService.class);
|
||||
stopIntent.setAction(COMMAND_PROGRAMAKER_BRIDGE_STOP);
|
||||
PendingIntent stopPendingIntent =
|
||||
PendingIntent.getService(this, 0, stopIntent, 0);
|
||||
|
||||
Intent startIntent = new Intent(this, ProgramakerBridgeService.class);
|
||||
startIntent.setAction(COMMAND_PROGRAMAKER_BRIDGE_START);
|
||||
PendingIntent startPendingIntent =
|
||||
PendingIntent.getService(this, 0, startIntent, 0);
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat
|
||||
.Builder(this, ProgramakerBridgeService.BridgeStatusNotificationChannel)
|
||||
.setContentTitle(title)
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT);
|
||||
|
||||
|
||||
if (stopped) {
|
||||
builder.addAction(R.drawable.ic_start, getString(R.string.start_bridge), startPendingIntent)
|
||||
.setSmallIcon(R.drawable.ic_vector_stopped_icon);
|
||||
}
|
||||
else {
|
||||
builder.addAction(R.drawable.ic_cancel, getString(R.string.stop_bridge), stopPendingIntent)
|
||||
.setSmallIcon(R.drawable.ic_vector_icon);
|
||||
}
|
||||
|
||||
Notification notification = builder.build();
|
||||
|
||||
if (stopped) {
|
||||
notificationManager.notify(BridgeStatusNotificationId, notification);
|
||||
}
|
||||
else {
|
||||
this.startForeground(BridgeStatusNotificationId, notification);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
String action = intent.getAction();
|
||||
if (action == null) {
|
||||
action = "";
|
||||
}
|
||||
|
||||
if (action.equals(COMMAND_PROGRAMAKER_BRIDGE_STOP)) {
|
||||
this.stopSelf();
|
||||
}
|
||||
else {
|
||||
connectBridge();
|
||||
}
|
||||
|
||||
// If we get killed, after returning from here, restart
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
private void connectBridge() {
|
||||
stopped = false;
|
||||
ConfigManager config = new ConfigManager(this);
|
||||
|
||||
String token = config.getToken();
|
||||
if (token == null) {
|
||||
Toast.makeText(this, "Cannot start bridge", Toast.LENGTH_SHORT).show();
|
||||
Log.e(LogTag, "Cannot start bridge: Token is null");
|
||||
this.stopSelf();
|
||||
}
|
||||
|
||||
if (ProgramakerBridgeService.this.bridge != null) {
|
||||
// Toast.makeText(this, "Bridge already started", Toast.LENGTH_SHORT).show();
|
||||
Log.w(LogTag, "Bridge already started (not null)");
|
||||
}
|
||||
|
||||
// Start up the thread running the service. Note that we create a
|
||||
// separate thread because the service normally runs in the process's
|
||||
// main thread, which we don't want to block. We also make it
|
||||
// background priority so CPU-intensive work doesn't disrupt our UI.
|
||||
Thread thread = new Thread(() -> {
|
||||
try {
|
||||
ProgramakerApi api = new ProgramakerApi();
|
||||
api.setToken(token);
|
||||
String userId = api.getUserId();
|
||||
|
||||
String bridgeIdCheck = config.getBridgeId();
|
||||
if (bridgeIdCheck == null) {
|
||||
bridgeIdCheck = api.createBridge(ProgramakerAndroidBridge.GetBridgeName(this));
|
||||
config.setBridgeId(bridgeIdCheck);
|
||||
}
|
||||
final String bridgeId = bridgeIdCheck;
|
||||
|
||||
ProgramakerBridgeService.this.bridge = ProgramakerAndroidBridge.configure(
|
||||
this,
|
||||
userId,
|
||||
bridgeId);
|
||||
ProgramakerBridgeService.this.bridge.start(
|
||||
() -> { // On ready
|
||||
setBridgeStatusNotification(getString(R.string.bridge_service_online), false);
|
||||
if (config.getBridgeConnectionId() == null) {
|
||||
new DoAsync().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
|
||||
new Tuple2<>(
|
||||
() -> {
|
||||
boolean established = api.establishConnection(bridgeId);
|
||||
if (!established) {
|
||||
Log.e(LogTag, "Error establishing connection to bridge");
|
||||
}
|
||||
},
|
||||
ex -> {
|
||||
Log.e(LogTag, "Error establishing bridge connection: " + ex, ex);
|
||||
ProgramakerBridgeService.this.stopSelf();
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
() -> { // On completed
|
||||
ProgramakerBridgeService.this.bridge = null;
|
||||
onBridgeFailedAfterConnected();
|
||||
});
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
Log.e(LogTag, "Error on bridge", ex);
|
||||
ProgramakerBridgeService.this.bridge = null;
|
||||
}
|
||||
}, "ServiceStartArguments");
|
||||
thread.setPriority(Process.THREAD_PRIORITY_BACKGROUND);
|
||||
|
||||
setBridgeStatusNotification(getString(R.string.bridge_service_starting), false);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
private void onBridgeFailedAfterConnected() {
|
||||
if (!stopped) {
|
||||
Log.e(LogTag, "Bridge stopped after connected. Waiting 10s then restarting");
|
||||
setBridgeStatusNotification(getString(R.string.bridge_service_failed_restarting), false);
|
||||
try {
|
||||
Thread.sleep(WAIT_TIME_BEFORE_RESTART_MILLIS);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
connectBridge();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
// We don't provide binding, so return null
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
stopped = true;
|
||||
ProgramakerAndroidBridge bridge = this.bridge;
|
||||
if (bridge != null) {
|
||||
bridge.stop();
|
||||
}
|
||||
setBridgeStatusNotification(getString(R.string.bridge_service_failed_stopping), true);
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package com.codigoparallevar.minicards.bridge.blocks;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
class BlockArgumentDefinition {
|
||||
private final static String LogTag = "PM BlockArgument";
|
||||
private final Type type;
|
||||
private final String defaultValue;
|
||||
|
||||
public enum Type {
|
||||
STRING,
|
||||
INT,
|
||||
FLOAT,
|
||||
BOOL,
|
||||
}
|
||||
|
||||
BlockArgumentDefinition(BlockArgumentDefinition.Type type, String defaultValue) {
|
||||
this.type = type;
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public JSONObject serialize() throws JSONException {
|
||||
JSONObject obj = new JSONObject();
|
||||
|
||||
obj.put("type", BlockArgumentDefinition.TypeToString(type));
|
||||
obj.put("default", this.defaultValue);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
private static String TypeToString(Type type) {
|
||||
switch (type) {
|
||||
case STRING:
|
||||
return "string";
|
||||
case INT:
|
||||
return "integer";
|
||||
case FLOAT:
|
||||
return "float";
|
||||
case BOOL:
|
||||
return "bool";
|
||||
default:
|
||||
Log.e(LogTag, "Unknown type: " + type);
|
||||
return "string";
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package com.codigoparallevar.minicards.bridge.blocks;
|
||||
|
||||
import com.codigoparallevar.minicards.types.functional.Consumer;
|
||||
import com.programaker.bridge.ProgramakerBridgeConfigurationBlock;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class BridgeBlockListBuilder {
|
||||
private final LinkedList<ProgramakerBridgeConfigurationBlock> blockList;
|
||||
|
||||
public BridgeBlockListBuilder() {
|
||||
this.blockList = new LinkedList<>();
|
||||
}
|
||||
|
||||
|
||||
public List<ProgramakerBridgeConfigurationBlock> Build() {
|
||||
return blockList;
|
||||
}
|
||||
|
||||
public BridgeBlockListBuilder addOperation(String id, String message,
|
||||
List<BlockArgumentDefinition> arguments,
|
||||
Consumer<List<? extends Object>> operation) {
|
||||
return this.addOperation(new OperationBlockDefinition(id, message, arguments, operation));
|
||||
}
|
||||
|
||||
private BridgeBlockListBuilder addOperation(ProgramakerBridgeConfigurationBlock block) {
|
||||
this.blockList.add(block);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
@ -0,0 +1,138 @@
|
||||
package com.codigoparallevar.minicards.bridge.blocks;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
import com.codigoparallevar.minicards.R;
|
||||
import com.codigoparallevar.minicards.bridge.ProgramakerBridgeService;
|
||||
import com.codigoparallevar.minicards.types.functional.Box;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class DefaultAndroidBlocks {
|
||||
|
||||
public static BridgeBlockListBuilder GetBuilder(Context ctx) {
|
||||
// System services
|
||||
String notifServiceId = Context.NOTIFICATION_SERVICE;
|
||||
NotificationManager notificationManager = (NotificationManager) ctx.getSystemService(notifServiceId);
|
||||
assert notificationManager != null;
|
||||
|
||||
Random notificationRandom = new Random();
|
||||
int ledsNotificationId = notificationRandom.nextInt();
|
||||
Box<Integer> currentLedArg = new Box<>(0x00000000);
|
||||
|
||||
NotificationChannel channelPreparation = null;
|
||||
NotificationChannel ledsChannelPreparation = null;
|
||||
|
||||
String notificationChannelId = ProgramakerBridgeService.BridgeUserNotificationChannel;
|
||||
String ledsChannelId = ProgramakerBridgeService.BridgeUserLedsNotificationChannel;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
channelPreparation = new NotificationChannel(
|
||||
ProgramakerBridgeService.BridgeUserNotificationChannel,
|
||||
ProgramakerBridgeService.BridgeUserNotificationChannelName,
|
||||
NotificationManager.IMPORTANCE_DEFAULT);
|
||||
notificationManager.createNotificationChannel(channelPreparation);
|
||||
|
||||
ledsChannelPreparation = new NotificationChannel(
|
||||
ledsChannelId,
|
||||
ProgramakerBridgeService.BridgeUserLedsNotificationChannelName,
|
||||
NotificationManager.IMPORTANCE_DEFAULT);
|
||||
|
||||
ledsChannelPreparation.enableLights(true);
|
||||
|
||||
notificationManager.createNotificationChannel(ledsChannelPreparation);
|
||||
}
|
||||
|
||||
final NotificationChannel notificationChannel = channelPreparation;
|
||||
final NotificationChannel ledsChannel = ledsChannelPreparation;
|
||||
return new BridgeBlockListBuilder()
|
||||
// Notifications
|
||||
.addOperation(
|
||||
"notifications_new",
|
||||
"Create notification. Title: %1, text: %2",
|
||||
new LinkedList<BlockArgumentDefinition>() {{
|
||||
add(new BlockArgumentDefinition(BlockArgumentDefinition.Type.STRING, "My notification"));
|
||||
add(new BlockArgumentDefinition(BlockArgumentDefinition.Type.STRING, "Sample description"));
|
||||
}},
|
||||
(List<?> params) -> {
|
||||
if (params.size() != 2) {
|
||||
throw new Exception("Expected two (2) arguments, found " + params.size());
|
||||
}
|
||||
String title = params.get(0).toString();
|
||||
String description = params.get(1).toString();
|
||||
|
||||
Notification notif = new NotificationCompat
|
||||
.Builder(ctx, ProgramakerBridgeService.BridgeUserNotificationChannel)
|
||||
.setContentTitle(title)
|
||||
.setContentText(description)
|
||||
.setSmallIcon(R.drawable.ic_center_focus_weak_black) // TODO: Change icon
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
.build();
|
||||
|
||||
int notificationId = notificationRandom.nextInt();
|
||||
notificationManager.notify(notificationId, notif);
|
||||
}
|
||||
)
|
||||
.addOperation(
|
||||
"notifications_clear",
|
||||
"Clear notifications",
|
||||
Collections.emptyList(),
|
||||
(List<?> params) -> {
|
||||
notificationManager.cancelAll();
|
||||
}
|
||||
)
|
||||
// Leds
|
||||
.addOperation(
|
||||
"leds_turn_on",
|
||||
"Set leds to (r:%1,g:%2,b:%3)",
|
||||
new LinkedList<BlockArgumentDefinition>() {{
|
||||
add(new BlockArgumentDefinition(BlockArgumentDefinition.Type.INT, "255"));
|
||||
add(new BlockArgumentDefinition(BlockArgumentDefinition.Type.INT, "255"));
|
||||
add(new BlockArgumentDefinition(BlockArgumentDefinition.Type.INT, "255"));
|
||||
}},
|
||||
params -> {
|
||||
if (params.size() != 3) {
|
||||
throw new Exception("Expected three (3) arguments, found " + params.size());
|
||||
}
|
||||
String rStr = params.get(0).toString();
|
||||
String gStr = params.get(1).toString();
|
||||
String bStr = params.get(2).toString();
|
||||
|
||||
int r = Math.min(255, Math.max(0, Integer.parseInt(rStr)));
|
||||
int g = Math.min(255, Math.max(0, Integer.parseInt(gStr)));
|
||||
int b = Math.min(255, Math.max(0, Integer.parseInt(bStr)));
|
||||
|
||||
int ledColor = 0x7f * (1<<24) + r * (1 << 16) + g * (1 << 8) + b;
|
||||
|
||||
Notification notif = new NotificationCompat
|
||||
.Builder(ctx, ledsChannelId)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.setContentTitle(ctx.getString(R.string.leds_notification))
|
||||
.setSmallIcon(R.drawable.ic_center_focus_weak_black) // TODO: Change icon
|
||||
.setLights(Color.RED, 0, 0)
|
||||
.build();
|
||||
|
||||
notif.ledARGB = Color.RED;
|
||||
notif.ledOnMS = 300;
|
||||
notif.ledOffMS = 300;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
ledsChannel.setLightColor(Color.RED);
|
||||
}
|
||||
|
||||
notificationManager.notify(ledsNotificationId, notif);
|
||||
}
|
||||
)
|
||||
;
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
package com.codigoparallevar.minicards.bridge.blocks;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.codigoparallevar.minicards.types.functional.Consumer;
|
||||
import com.programaker.bridge.ProgramakerBridgeConfigurationBlock;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
class OperationBlockDefinition implements ProgramakerBridgeConfigurationBlock {
|
||||
private final static String LogTag = "PM OpBlockDefinition";
|
||||
|
||||
private final String id;
|
||||
private final String message;
|
||||
private final List<BlockArgumentDefinition> args;
|
||||
private final Consumer<List<?>> operation;
|
||||
|
||||
public OperationBlockDefinition(String id, String message, List<BlockArgumentDefinition> args, Consumer<List<? extends Object>> operation) {
|
||||
this.id = id;
|
||||
this.message = message;
|
||||
this.args = args;
|
||||
this.operation = operation;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public JSONObject serialize() {
|
||||
JSONObject obj = new JSONObject();
|
||||
|
||||
try {
|
||||
JSONArray arguments = new JSONArray();
|
||||
for (BlockArgumentDefinition arg : this.args) {
|
||||
arguments.put(arg.serialize());
|
||||
}
|
||||
|
||||
obj.put("id", this.id);
|
||||
obj.put("function_name", this.id);
|
||||
obj.put("message", this.message);
|
||||
obj.put("block_type", "operation");
|
||||
obj.put("block_result_type", JSONObject.NULL);
|
||||
obj.put("arguments", arguments);
|
||||
obj.put("save_to", JSONObject.NULL);
|
||||
} catch (JSONException ex) {
|
||||
Log.e(LogTag, "Error serializing block definition: " + ex, ex);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getFunctionName() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void call(@NotNull List<?> arguments) throws Exception {
|
||||
this.operation.apply(arguments);
|
||||
}
|
||||
}
|
@ -0,0 +1,609 @@
|
||||
package com.codigoparallevar.minicards.parts;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.os.AsyncTask;
|
||||
import android.util.Log;
|
||||
|
||||
import com.codigoparallevar.minicards.ScrolledCanvas;
|
||||
import com.codigoparallevar.minicards.parts.connectors.AnyRoundInputConnector;
|
||||
import com.codigoparallevar.minicards.parts.connectors.ConnectorTypeInfo;
|
||||
import com.codigoparallevar.minicards.parts.connectors.RoundOutputConnector;
|
||||
import com.codigoparallevar.minicards.types.Moveable;
|
||||
import com.codigoparallevar.minicards.types.Part;
|
||||
import com.codigoparallevar.minicards.types.PartConnection;
|
||||
import com.codigoparallevar.minicards.types.PartGrid;
|
||||
import com.codigoparallevar.minicards.types.connectors.input.InputConnector;
|
||||
import com.codigoparallevar.minicards.types.connectors.output.OutputConnector;
|
||||
import com.codigoparallevar.minicards.types.functional.Tuple2;
|
||||
import com.codigoparallevar.minicards.types.wireData.Signal;
|
||||
import com.codigoparallevar.minicards.types.wireData.WireDataType;
|
||||
import com.codigoparallevar.minicards.ui_helpers.DoAsync;
|
||||
import com.programaker.api.ProgramakerApi;
|
||||
import com.programaker.api.data.ProgramakerCustomBlock;
|
||||
import com.programaker.api.data.ProgramakerCustomBlockArgument;
|
||||
import com.programaker.api.data.ProgramakerCustomBlockSaveTo;
|
||||
import com.programaker.api.data.ProgramakerFunctionCallResult;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ProgramakerCustomBlockPart implements Part {
|
||||
private static final int WIDTH_PADDING = 50;
|
||||
private static final int HEIGHT_PADDING = 50;
|
||||
private static final int IO_RADIUS = 50;
|
||||
private static final int IO_PADDING = 20;
|
||||
private static final String LogTag = "PM Custom block part";
|
||||
|
||||
private List<Tuple2<ConnectorTypeInfo, AnyRoundInputConnector>> inputConnectors = null;
|
||||
private List<Tuple2<ConnectorTypeInfo, RoundOutputConnector>> outputConnectors = null;
|
||||
|
||||
private final PartGrid _partGrid;
|
||||
private final String _id;
|
||||
private final ProgramakerCustomBlock _block;
|
||||
|
||||
private int _left;
|
||||
private int _top;
|
||||
private int width = 100;
|
||||
private int height = 100;
|
||||
private String token = null;
|
||||
private Object[] lastValues;
|
||||
private boolean active = true;
|
||||
private Tuple2<ConnectorTypeInfo, RoundOutputConnector> saveToOutput;
|
||||
private RoundOutputConnector pulseOutput;
|
||||
|
||||
|
||||
public ProgramakerCustomBlockPart(String id, PartGrid grid, Tuple2<Integer, Integer> center, ProgramakerCustomBlock block) {
|
||||
this._id = id;
|
||||
this._partGrid = grid;
|
||||
this._block = block;
|
||||
|
||||
this.updateWidthHeight();
|
||||
|
||||
this._left = center.item1 - width / 2;
|
||||
this._top = center.item2 - height / 2;
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
private ProgramakerCustomBlockPart(String id, PartGrid grid, int left, int top, ProgramakerCustomBlock block) {
|
||||
this._id = id;
|
||||
this._partGrid = grid;
|
||||
this._block = block;
|
||||
|
||||
this.updateWidthHeight();
|
||||
|
||||
this._left = left;
|
||||
this._top = top;
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
public ProgramakerCustomBlockPart(PartGrid grid, Tuple2<Integer, Integer> center, ProgramakerCustomBlock block) {
|
||||
this(UUID.randomUUID().toString(), grid, center, block);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int get_left() {
|
||||
return _left;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int get_right() {
|
||||
return _left + width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int get_top() {
|
||||
return _top;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int get_bottom() {
|
||||
return _top + height;
|
||||
}
|
||||
|
||||
private Paint getTextPaint() {
|
||||
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
p.setColor(Color.BLACK);
|
||||
p.setTextSize(50);
|
||||
return p;
|
||||
}
|
||||
|
||||
private void updateWidthHeight() {
|
||||
Paint p = getTextPaint();
|
||||
String message = getMessage();
|
||||
Rect bounds = new Rect();
|
||||
p.getTextBounds(message, 0, message.length(), bounds);
|
||||
|
||||
this.height = bounds.height() + HEIGHT_PADDING * 2;
|
||||
this.width = bounds.width() + WIDTH_PADDING * 2;
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
this.updatePorts();
|
||||
lastValues = new Object[this.inputConnectors.size()];
|
||||
}
|
||||
|
||||
private void updatePorts() {
|
||||
final String type = this._block.getBlock_type() == null ? "" : this._block.getBlock_type();
|
||||
final boolean has_pulse_input = type.equals("operation");
|
||||
final boolean has_pulse_output = has_pulse_input || type.equals("trigger");
|
||||
final boolean hasImplicitOutput = type.equals("getter");
|
||||
|
||||
final List<Tuple2<ConnectorTypeInfo, AnyRoundInputConnector>> inputs = new LinkedList<>();
|
||||
final List<Tuple2<ConnectorTypeInfo, RoundOutputConnector>> outputs = new LinkedList<>();
|
||||
|
||||
RoundOutputConnector pulseOutput = null;
|
||||
|
||||
// Add pulses
|
||||
if (has_pulse_input) {
|
||||
inputs.add(new Tuple2<>(new ConnectorTypeInfo(ConnectorTypeInfo.Type.PULSE),
|
||||
new AnyRoundInputConnector(this, 0, 0,
|
||||
IO_RADIUS))
|
||||
);
|
||||
}
|
||||
if (has_pulse_output) {
|
||||
pulseOutput = new RoundOutputConnector(this, this._partGrid, 0, 0,
|
||||
IO_RADIUS);
|
||||
outputs.add(new Tuple2<>(new ConnectorTypeInfo(ConnectorTypeInfo.Type.PULSE),
|
||||
pulseOutput)
|
||||
);
|
||||
}
|
||||
|
||||
// Add block IO
|
||||
ProgramakerCustomBlockArgument savedTo = null;
|
||||
|
||||
if (_block.getArguments() != null) {
|
||||
ProgramakerCustomBlockSaveTo saveTo = _block.getSave_to();
|
||||
int skip = -1;
|
||||
|
||||
if (saveTo != null) {
|
||||
if (saveTo.getType() != null
|
||||
&& saveTo.getType().equals("argument")) {
|
||||
skip = saveTo.getIndex();
|
||||
}
|
||||
else {
|
||||
Log.w(LogTag, "Unknown save-to type=" + saveTo.getType());
|
||||
}
|
||||
}
|
||||
|
||||
int index = -1;
|
||||
for (ProgramakerCustomBlockArgument arg : _block.getArguments()) {
|
||||
index++;
|
||||
if (skip == index) {
|
||||
savedTo = arg;
|
||||
}
|
||||
else {
|
||||
inputs.add(new Tuple2<>(ConnectorTypeInfo.FromTypeName(arg.getType()),
|
||||
new AnyRoundInputConnector(this, 0, 0, IO_RADIUS)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Tuple2<ConnectorTypeInfo, RoundOutputConnector> saveToOutput = null;
|
||||
|
||||
if (savedTo != null) {
|
||||
saveToOutput = new Tuple2<>(ConnectorTypeInfo.FromTypeName(savedTo.getType()),
|
||||
new RoundOutputConnector(this, this._partGrid, 0, 0, IO_RADIUS));
|
||||
outputs.add(saveToOutput);
|
||||
}
|
||||
|
||||
if (hasImplicitOutput) {
|
||||
outputs.add(new Tuple2<>(ConnectorTypeInfo.FromTypeName(_block.getBlock_result_type()),
|
||||
new RoundOutputConnector(this, this._partGrid, 0, 0, IO_RADIUS)));
|
||||
}
|
||||
|
||||
// Tie everything
|
||||
inputConnectors = inputs;
|
||||
outputConnectors = outputs;
|
||||
this.saveToOutput = saveToOutput;
|
||||
this.pulseOutput = pulseOutput;
|
||||
|
||||
this.updatePortPositions();
|
||||
}
|
||||
|
||||
private void updatePortPositions() {
|
||||
{
|
||||
// Update inputs
|
||||
int y = get_top();
|
||||
int x = get_left() + IO_PADDING;
|
||||
for (Tuple2<ConnectorTypeInfo, AnyRoundInputConnector> entry : inputConnectors) {
|
||||
InputConnector input = entry.item2;
|
||||
int new_x = x + IO_PADDING + IO_RADIUS / 2;
|
||||
input.updatePosition(new_x, y);
|
||||
x = x + IO_RADIUS * 2 + IO_PADDING * 2;
|
||||
}
|
||||
}
|
||||
{
|
||||
// Update outputs
|
||||
int y = get_bottom();
|
||||
int x = get_left() + IO_PADDING;
|
||||
for (Tuple2<ConnectorTypeInfo, RoundOutputConnector> entry : outputConnectors) {
|
||||
OutputConnector output = entry.item2;
|
||||
int new_x = x + IO_PADDING + IO_RADIUS / 2;
|
||||
output.updatePosition(new_x, y);
|
||||
x = x + IO_RADIUS * 2 + IO_PADDING * 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getMessage() {
|
||||
return this._block.getMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void touched() {
|
||||
Log.i(LogTag, "Part touched (block_fun=" + this._block.getFunction_name() + ")");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<InputConnector> getInputConnectors() {
|
||||
List<InputConnector> result = new ArrayList<>(inputConnectors.size());
|
||||
for (Tuple2<ConnectorTypeInfo, AnyRoundInputConnector> entry : inputConnectors) {
|
||||
result.add(entry.item2);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<OutputConnector> getOutputConnectors() {
|
||||
List<OutputConnector> result = new ArrayList<>(outputConnectors.size());
|
||||
for (Tuple2<ConnectorTypeInfo, RoundOutputConnector> entry : outputConnectors) {
|
||||
result.add(entry.item2);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject serialize() throws JSONException {
|
||||
JSONObject serialized = new JSONObject();
|
||||
|
||||
serialized.put("id", _id);
|
||||
serialized.put("left", _left);
|
||||
serialized.put("top", _top);
|
||||
|
||||
JSONObject blockData = _block.serialize();
|
||||
serialized.put("block", blockData);
|
||||
|
||||
serialized.put("on_string_output_connector", serializeConnectionEndpoints());
|
||||
|
||||
return serialized;
|
||||
}
|
||||
|
||||
private JSONArray serializeConnectionEndpoints() {
|
||||
JSONArray serializedData = new JSONArray();
|
||||
|
||||
for (OutputConnector output : getOutputConnectors()) {
|
||||
JSONArray elements = new JSONArray();
|
||||
|
||||
for (Tuple2<String, String> endpoint : (List<Tuple2<String, String>>) output.getConnectionEndpoints()) {
|
||||
elements.put(PartConnection.serializeToJson(endpoint.item1, endpoint.item2));
|
||||
}
|
||||
|
||||
serializedData.put(elements);
|
||||
}
|
||||
return serializedData;
|
||||
}
|
||||
|
||||
public static Tuple2<Part, List<PartConnection>> deserialize(PartGrid grid, JSONObject data) throws JSONException {
|
||||
String id = data.getString("id");
|
||||
int left = data.getInt("left");
|
||||
int top = data.getInt("top");
|
||||
|
||||
JSONObject el = data.getJSONObject("block");
|
||||
ProgramakerCustomBlock block = ProgramakerCustomBlock.deserialize(el);
|
||||
ProgramakerCustomBlockPart part = new ProgramakerCustomBlockPart(id, grid, left, top, block);
|
||||
|
||||
List<PartConnection> connections = new LinkedList<>();
|
||||
|
||||
JSONArray allConnectorOuts = data.optJSONArray("on_string_output_connector");
|
||||
if (allConnectorOuts == null) {
|
||||
allConnectorOuts = new JSONArray();
|
||||
}
|
||||
|
||||
for (int i = 0; i < allConnectorOuts.length(); i++) {
|
||||
JSONArray connectorOuts = allConnectorOuts.getJSONArray(i);
|
||||
Tuple2<ConnectorTypeInfo, RoundOutputConnector> connector = part.outputConnectors.get(i);
|
||||
for (int j = 0; j < connectorOuts.length(); j++) {
|
||||
connections.add(PartConnection.deserialize(
|
||||
connector.item2, connectorOuts.getJSONObject(j)));
|
||||
}
|
||||
}
|
||||
|
||||
return new Tuple2<>(part, connections);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void send(InputConnector inputConnector, WireDataType signal) {
|
||||
// Log.i(LogTag, "Received signal from Input="+inputConnector);
|
||||
ConnectorTypeInfo info = getConnectorInfo(inputConnector);
|
||||
if (info == null) {
|
||||
Log.e(LogTag, "Unknown connector" + inputConnector);
|
||||
return;
|
||||
}
|
||||
|
||||
if (info.get_type() == ConnectorTypeInfo.Type.PULSE) {
|
||||
wrappedRunOperation();
|
||||
}
|
||||
else {
|
||||
int index = getConnectorIndex(inputConnector);
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
this.lastValues[index] = signal.get();
|
||||
}
|
||||
}
|
||||
|
||||
private void wrappedRunOperation() {
|
||||
Log.d(LogTag, "Running block function=" + this._block.getFunction_name());
|
||||
String token = this.prepareStart();
|
||||
|
||||
if (token != null) {
|
||||
try {
|
||||
new DoAsync().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
|
||||
new Tuple2<>(() -> {
|
||||
ProgramakerCustomBlockPart.this.runBlockOperation();
|
||||
|
||||
ProgramakerCustomBlockPart.this.freeBlock(token);
|
||||
}, ex -> {
|
||||
Log.w(LogTag, "Error executing function=" + this._block.getFunction_name()
|
||||
+ "; Error=" + ex, ex);
|
||||
ProgramakerCustomBlockPart.this.freeBlock(token);
|
||||
}));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(LogTag, "Error executing function=" + this._block.getFunction_name()
|
||||
+ "; Error=" + ex, ex);
|
||||
this.freeBlock(token);
|
||||
}
|
||||
} else {
|
||||
// An execution is in progress, so nothing is done
|
||||
}
|
||||
}
|
||||
|
||||
private void runBlockOperation() {
|
||||
if (!this.active) {
|
||||
Log.w(LogTag, "Trying to run inactive block function=" + this._block.getFunction_name());
|
||||
return;
|
||||
}
|
||||
|
||||
ProgramakerApi api = this._partGrid.getApi();
|
||||
List<String> arguments = new LinkedList<>();
|
||||
|
||||
int index = -1;
|
||||
for (Tuple2<ConnectorTypeInfo, AnyRoundInputConnector> entry : inputConnectors) {
|
||||
index++;
|
||||
if (entry.item1.get_type() == ConnectorTypeInfo.Type.PULSE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
arguments.add(lastValues[index].toString()); // TODO: Do proper type formatting
|
||||
}
|
||||
|
||||
ProgramakerFunctionCallResult result = api.callBlock(this._block, arguments);
|
||||
Log.d(LogTag, "Execution result="+result.getResult());
|
||||
|
||||
onExecutionCompleted(result);
|
||||
}
|
||||
|
||||
private void onExecutionCompleted(ProgramakerFunctionCallResult result) {
|
||||
Tuple2<ConnectorTypeInfo, RoundOutputConnector> savedTo = this.saveToOutput;
|
||||
if (savedTo != null) {
|
||||
// TODO: Fix output typing
|
||||
// savedTo.item2.send((WireDataType<Object>) () -> result.getResult());
|
||||
}
|
||||
|
||||
RoundOutputConnector pulseOutput = this.pulseOutput;
|
||||
if (pulseOutput != null) {
|
||||
pulseOutput.send(new Signal());
|
||||
}
|
||||
}
|
||||
|
||||
private void freeBlock(String token) {
|
||||
tryUpdateBlock(token, null);
|
||||
}
|
||||
|
||||
private String prepareStart() {
|
||||
String token = UUID.randomUUID().toString();
|
||||
if (tryUpdateBlock(null, token)) {
|
||||
return token;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private synchronized boolean tryUpdateBlock(String oldVal, String newVal) {
|
||||
if (this.token == oldVal) {
|
||||
this.token = newVal;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String get_id() {
|
||||
return _id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputConnector getConnectorWithId(String inputConnectorId) {
|
||||
if (inputConnectorId.startsWith("index=")) {
|
||||
String[] chunks = inputConnectorId.split("=");
|
||||
if (chunks.length > 1) {
|
||||
Integer index = null;
|
||||
try {
|
||||
index = Integer.parseInt(chunks[1]);
|
||||
}
|
||||
catch (NumberFormatException ex) {
|
||||
Log.e(LogTag, "Error parsing connector id="+inputConnectorId, ex);
|
||||
}
|
||||
|
||||
if (index != null && index < inputConnectors.size()) {
|
||||
return inputConnectors.get(index).item2;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private int getConnectorIndex(InputConnector inputConnector) {
|
||||
int index = 0;
|
||||
for (Tuple2<ConnectorTypeInfo, AnyRoundInputConnector> entry : inputConnectors) {
|
||||
if (entry.item2 == inputConnector) {
|
||||
return index;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConnectorId(InputConnector inputConnector) {
|
||||
int index = getConnectorIndex(inputConnector);
|
||||
if (index < 0 ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return "index=" + index;
|
||||
}
|
||||
|
||||
public ConnectorTypeInfo getConnectorInfo(InputConnector inputConnector) {
|
||||
for (Tuple2<ConnectorTypeInfo, AnyRoundInputConnector> entry : inputConnectors) {
|
||||
if (entry.item2 == inputConnector) {
|
||||
return entry.item1;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resume() {
|
||||
this.active = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pause() {
|
||||
this.active = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(ScrolledCanvas canvas, boolean devMode) {
|
||||
|
||||
updateWidthHeight(); // TODO: Remove after the calculations have stabilized
|
||||
|
||||
if (!devMode) {
|
||||
return; // Logic block, don't show on user-mode
|
||||
}
|
||||
|
||||
drawConnectors(canvas);
|
||||
drawWires(canvas, devMode);
|
||||
|
||||
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
paint.setColor(Color.WHITE);
|
||||
canvas.drawRect(
|
||||
new Rect(get_left(), get_top(),
|
||||
get_right(), get_bottom()),
|
||||
paint);
|
||||
|
||||
Paint textPaint = getTextPaint();
|
||||
canvas.drawText(getMessage(),
|
||||
get_left() + WIDTH_PADDING,
|
||||
get_bottom() - HEIGHT_PADDING,
|
||||
textPaint);
|
||||
}
|
||||
|
||||
private void drawConnectors(ScrolledCanvas canvas) {
|
||||
if (inputConnectors != null) {
|
||||
for (Tuple2<ConnectorTypeInfo, AnyRoundInputConnector> entry : inputConnectors) {
|
||||
Paint outerInputConnectorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
outerInputConnectorPaint.setColor(entry.item1.getOuterColor());
|
||||
|
||||
canvas.drawCircle(
|
||||
entry.item2.getX(),
|
||||
entry.item2.getY(),
|
||||
entry.item2.getRadius(),
|
||||
outerInputConnectorPaint);
|
||||
|
||||
Paint innerInputConnectorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
innerInputConnectorPaint.setColor(entry.item1.getInnerColor());
|
||||
|
||||
canvas.drawCircle(
|
||||
entry.item2.getX(),
|
||||
entry.item2.getY(),
|
||||
entry.item2.getRadius() / 2,
|
||||
innerInputConnectorPaint);
|
||||
}
|
||||
}
|
||||
|
||||
if (outputConnectors != null) {
|
||||
for (Tuple2<ConnectorTypeInfo, RoundOutputConnector> entry : outputConnectors) {
|
||||
Paint outerOutputConnectorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
outerOutputConnectorPaint.setColor(entry.item1.getOuterColor());
|
||||
|
||||
canvas.drawCircle(
|
||||
entry.item2.getX(),
|
||||
entry.item2.getY(),
|
||||
entry.item2.getRadius(),
|
||||
outerOutputConnectorPaint);
|
||||
|
||||
Paint innerOutputConnectorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
innerOutputConnectorPaint.setColor(entry.item1.getInnerColor());
|
||||
|
||||
canvas.drawCircle(
|
||||
entry.item2.getX(),
|
||||
entry.item2.getY(),
|
||||
entry.item2.getRadius() / 2,
|
||||
innerOutputConnectorPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void drawWires(ScrolledCanvas canvas, boolean devMode) {
|
||||
for (OutputConnector outputConnector : getOutputConnectors()){
|
||||
outputConnector.drawWires(canvas, devMode);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveEnd(int x, int y) {
|
||||
_left = x - width / 2;
|
||||
_top = y - height / 2;
|
||||
|
||||
this.updatePortPositions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drop(int x, int y) {
|
||||
moveEnd(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsPoint(int x, int y) {
|
||||
return ((x >= this.get_left()) && (x <= this.get_right())
|
||||
&& (y >= this.get_top()) && (y <= this.get_bottom()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Moveable getMoveable() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unlink() {
|
||||
this.active = false;
|
||||
for (InputConnector input : getInputConnectors()) {
|
||||
input.unlink();
|
||||
}
|
||||
}
|
||||
}
|
@ -7,11 +7,12 @@ import android.util.Log;
|
||||
import com.codigoparallevar.minicards.PartInstantiator;
|
||||
import com.codigoparallevar.minicards.ScrolledCanvas;
|
||||
import com.codigoparallevar.minicards.parts.connectors.RoundOutputConnector;
|
||||
import com.codigoparallevar.minicards.parts.style.CardTheme;
|
||||
import com.codigoparallevar.minicards.types.Moveable;
|
||||
import com.codigoparallevar.minicards.types.Part;
|
||||
import com.codigoparallevar.minicards.types.PartConnection;
|
||||
import com.codigoparallevar.minicards.types.PartGrid;
|
||||
import com.codigoparallevar.minicards.types.Tuple2;
|
||||
import com.codigoparallevar.minicards.types.functional.Tuple2;
|
||||
import com.codigoparallevar.minicards.types.connectors.input.InputConnector;
|
||||
import com.codigoparallevar.minicards.types.connectors.output.OutputConnector;
|
||||
import com.codigoparallevar.minicards.types.wireData.Signal;
|
||||
@ -106,20 +107,27 @@ public class RoundButton implements Part {
|
||||
}
|
||||
|
||||
private void drawConnector(ScrolledCanvas canvas) {
|
||||
Paint connectorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
connectorPaint.setColor(Color.RED);
|
||||
Paint outerConnectorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
outerConnectorPaint.setColor(CardTheme.PULSE_CONNECTOR_COLOR_OUTER);
|
||||
|
||||
canvas.drawCircle(getOutputConnectorCenterX(), getOutputConnectorCenterY(),
|
||||
getOutputConnectRadius(),
|
||||
connectorPaint);
|
||||
outerConnectorPaint);
|
||||
|
||||
Paint innerConnectorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
innerConnectorPaint.setColor(CardTheme.PULSE_CONNECTOR_COLOR_INNER);
|
||||
|
||||
canvas.drawCircle(getOutputConnectorCenterX(), getOutputConnectorCenterY(),
|
||||
getOutputConnectRadius() / 2,
|
||||
innerConnectorPaint);
|
||||
}
|
||||
|
||||
private int getOutputConnectorCenterX() {
|
||||
return _xCenter + _outerRadius;
|
||||
return _xCenter;
|
||||
}
|
||||
|
||||
private int getOutputConnectorCenterY() {
|
||||
return _yCenter;
|
||||
return _yCenter + _outerRadius;
|
||||
}
|
||||
|
||||
private int getOutputConnectRadius() {
|
||||
|
@ -65,6 +65,10 @@ public class AnyRoundInputConnector extends AnyInputConnector {
|
||||
return _yposition;
|
||||
}
|
||||
|
||||
public float getRadius() {
|
||||
return _radius;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(WireDataType signal) {
|
||||
_part.send(this, signal);
|
||||
|
@ -7,7 +7,7 @@ import com.codigoparallevar.minicards.types.Drawable;
|
||||
import com.codigoparallevar.minicards.types.Moveable;
|
||||
import com.codigoparallevar.minicards.types.Part;
|
||||
import com.codigoparallevar.minicards.types.PartGrid;
|
||||
import com.codigoparallevar.minicards.types.Tuple2;
|
||||
import com.codigoparallevar.minicards.types.functional.Tuple2;
|
||||
import com.codigoparallevar.minicards.types.connectors.Wiring.BooleanWire;
|
||||
import com.codigoparallevar.minicards.types.connectors.input.BooleanInputConnector;
|
||||
import com.codigoparallevar.minicards.types.connectors.input.InputConnector;
|
||||
|
@ -0,0 +1,105 @@
|
||||
package com.codigoparallevar.minicards.parts.connectors;
|
||||
|
||||
import com.codigoparallevar.minicards.parts.style.CardTheme;
|
||||
|
||||
public class ConnectorTypeInfo {
|
||||
private final Type _type;
|
||||
|
||||
public static ConnectorTypeInfo FromTypeName(String type) {
|
||||
if (type == null) {
|
||||
return new ConnectorTypeInfo(Type.UNKNOWN);
|
||||
}
|
||||
|
||||
type = type.toLowerCase();
|
||||
if (type.equals("string")) {
|
||||
return new ConnectorTypeInfo(Type.STRING);
|
||||
}
|
||||
else if (type.equals("integer")) {
|
||||
return new ConnectorTypeInfo(Type.INTEGER);
|
||||
}
|
||||
else if (type.equals("float")) {
|
||||
return new ConnectorTypeInfo(Type.FLOAT);
|
||||
}
|
||||
else if (type.equals("boolean")) {
|
||||
return new ConnectorTypeInfo(Type.BOOLEAN);
|
||||
}
|
||||
else if (type.equals("pulse")) {
|
||||
return new ConnectorTypeInfo(Type.PULSE);
|
||||
}
|
||||
else if (type.equals("enum")) {
|
||||
return new ConnectorTypeInfo(Type.ENUM);
|
||||
}
|
||||
else if (type.equals("any")) {
|
||||
return new ConnectorTypeInfo(Type.ANY);
|
||||
}
|
||||
else {
|
||||
return new ConnectorTypeInfo(Type.UNKNOWN);
|
||||
}
|
||||
}
|
||||
|
||||
public int getOuterColor() {
|
||||
switch (_type) {
|
||||
case ANY:
|
||||
return CardTheme.ANY_CONNECTOR_COLOR_OUTER;
|
||||
case ENUM:
|
||||
return CardTheme.ENUM_CONNECTOR_COLOR_OUTER;
|
||||
case FLOAT:
|
||||
return CardTheme.FLOAT_CONNECTOR_COLOR_OUTER;
|
||||
case PULSE:
|
||||
return CardTheme.PULSE_CONNECTOR_COLOR_OUTER;
|
||||
case STRING:
|
||||
return CardTheme.STRING_CONNECTOR_COLOR_OUTER;
|
||||
case BOOLEAN:
|
||||
return CardTheme.BOOLEAN_CONNECTOR_COLOR_OUTER;
|
||||
case INTEGER:
|
||||
return CardTheme.INTEGER_CONNECTOR_COLOR_OUTER;
|
||||
case UNKNOWN:
|
||||
return CardTheme.UNKNOWN_CONNECTOR_COLOR_OUTER;
|
||||
default:
|
||||
return CardTheme.UNKNOWN_CONNECTOR_COLOR_OUTER;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public int getInnerColor() {
|
||||
switch (_type) {
|
||||
case ANY:
|
||||
return CardTheme.ANY_CONNECTOR_COLOR_INNER;
|
||||
case ENUM:
|
||||
return CardTheme.ENUM_CONNECTOR_COLOR_INNER;
|
||||
case FLOAT:
|
||||
return CardTheme.FLOAT_CONNECTOR_COLOR_INNER;
|
||||
case PULSE:
|
||||
return CardTheme.PULSE_CONNECTOR_COLOR_INNER;
|
||||
case STRING:
|
||||
return CardTheme.STRING_CONNECTOR_COLOR_INNER;
|
||||
case BOOLEAN:
|
||||
return CardTheme.BOOLEAN_CONNECTOR_COLOR_INNER;
|
||||
case INTEGER:
|
||||
return CardTheme.INTEGER_CONNECTOR_COLOR_INNER;
|
||||
case UNKNOWN:
|
||||
return CardTheme.UNKNOWN_CONNECTOR_COLOR_INNER;
|
||||
default:
|
||||
return CardTheme.UNKNOWN_CONNECTOR_COLOR_INNER;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
PULSE,
|
||||
BOOLEAN,
|
||||
STRING,
|
||||
ANY,
|
||||
ENUM,
|
||||
UNKNOWN,
|
||||
FLOAT,
|
||||
INTEGER,
|
||||
}
|
||||
|
||||
public ConnectorTypeInfo(ConnectorTypeInfo.Type type) {
|
||||
this._type = type;
|
||||
}
|
||||
|
||||
public Type get_type() {
|
||||
return _type;
|
||||
}
|
||||
}
|
@ -65,12 +65,15 @@ public class RoundInputConnector implements SignalInputConnector {
|
||||
return _yposition;
|
||||
}
|
||||
|
||||
public float getRadius() {
|
||||
return _radius;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(Signal signal) {
|
||||
_part.send(this, signal);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void getAttachment(Wire<Signal, SignalInputConnector> wire) {
|
||||
_attachments.add(wire);
|
||||
|
@ -7,7 +7,7 @@ import com.codigoparallevar.minicards.types.Drawable;
|
||||
import com.codigoparallevar.minicards.types.Moveable;
|
||||
import com.codigoparallevar.minicards.types.Part;
|
||||
import com.codigoparallevar.minicards.types.PartGrid;
|
||||
import com.codigoparallevar.minicards.types.Tuple2;
|
||||
import com.codigoparallevar.minicards.types.functional.Tuple2;
|
||||
import com.codigoparallevar.minicards.types.connectors.Wiring.SignalWire;
|
||||
import com.codigoparallevar.minicards.types.connectors.input.InputConnector;
|
||||
import com.codigoparallevar.minicards.types.connectors.input.SignalInputConnector;
|
||||
@ -155,4 +155,16 @@ public class RoundOutputConnector implements Drawable, SignalOutputConnector {
|
||||
wire.send(signal);
|
||||
}
|
||||
}
|
||||
|
||||
public float getX() {
|
||||
return _centerX;
|
||||
}
|
||||
|
||||
public float getY() {
|
||||
return _centerY;
|
||||
}
|
||||
|
||||
public float getRadius() {
|
||||
return _radius;
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import com.codigoparallevar.minicards.types.Drawable;
|
||||
import com.codigoparallevar.minicards.types.Moveable;
|
||||
import com.codigoparallevar.minicards.types.Part;
|
||||
import com.codigoparallevar.minicards.types.PartGrid;
|
||||
import com.codigoparallevar.minicards.types.Tuple2;
|
||||
import com.codigoparallevar.minicards.types.functional.Tuple2;
|
||||
import com.codigoparallevar.minicards.types.connectors.Wiring.StringWire;
|
||||
import com.codigoparallevar.minicards.types.connectors.input.InputConnector;
|
||||
import com.codigoparallevar.minicards.types.connectors.input.StringInputConnector;
|
||||
|
@ -8,11 +8,12 @@ import android.util.Log;
|
||||
import com.codigoparallevar.minicards.PartInstantiator;
|
||||
import com.codigoparallevar.minicards.ScrolledCanvas;
|
||||
import com.codigoparallevar.minicards.parts.connectors.RoundOutputConnector;
|
||||
import com.codigoparallevar.minicards.parts.style.CardTheme;
|
||||
import com.codigoparallevar.minicards.types.Moveable;
|
||||
import com.codigoparallevar.minicards.types.Part;
|
||||
import com.codigoparallevar.minicards.types.PartConnection;
|
||||
import com.codigoparallevar.minicards.types.PartGrid;
|
||||
import com.codigoparallevar.minicards.types.Tuple2;
|
||||
import com.codigoparallevar.minicards.types.functional.Tuple2;
|
||||
import com.codigoparallevar.minicards.types.connectors.input.InputConnector;
|
||||
import com.codigoparallevar.minicards.types.connectors.output.OutputConnector;
|
||||
import com.codigoparallevar.minicards.types.wireData.Signal;
|
||||
@ -144,12 +145,21 @@ public class Ticker implements Part {
|
||||
}
|
||||
|
||||
private void drawConnector(ScrolledCanvas canvas) {
|
||||
Paint connectorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
connectorPaint.setColor(Color.RED);
|
||||
Paint outerConnectorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
outerConnectorPaint.setColor(CardTheme.PULSE_CONNECTOR_COLOR_OUTER);
|
||||
|
||||
canvas.drawCircle(_right, getOutputConnectorCenterY(),
|
||||
canvas.drawCircle(getOutputConnectorCenterX(),
|
||||
getOutputConnectorCenterY(),
|
||||
getOutputConnectRadius(),
|
||||
connectorPaint);
|
||||
outerConnectorPaint);
|
||||
|
||||
Paint innerConnectorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
innerConnectorPaint.setColor(CardTheme.PULSE_CONNECTOR_COLOR_INNER);
|
||||
|
||||
canvas.drawCircle(getOutputConnectorCenterX(),
|
||||
getOutputConnectorCenterY(),
|
||||
getOutputConnectRadius() / 2,
|
||||
innerConnectorPaint);
|
||||
}
|
||||
|
||||
private void drawWires(ScrolledCanvas canvas, boolean devMode) {
|
||||
@ -289,11 +299,11 @@ public class Ticker implements Part {
|
||||
|
||||
|
||||
private int getOutputConnectorCenterX() {
|
||||
return _right;
|
||||
return (_left + _right) / 2;
|
||||
}
|
||||
|
||||
private int getOutputConnectorCenterY() {
|
||||
return (_top + _bottom) / 2;
|
||||
return _bottom;
|
||||
}
|
||||
|
||||
private int getOutputConnectRadius() {
|
||||
|
@ -9,11 +9,12 @@ import com.codigoparallevar.minicards.PartInstantiator;
|
||||
import com.codigoparallevar.minicards.ScrolledCanvas;
|
||||
import com.codigoparallevar.minicards.parts.connectors.BooleanRoundOutputConnector;
|
||||
import com.codigoparallevar.minicards.parts.connectors.RoundInputConnector;
|
||||
import com.codigoparallevar.minicards.parts.style.CardTheme;
|
||||
import com.codigoparallevar.minicards.types.Moveable;
|
||||
import com.codigoparallevar.minicards.types.Part;
|
||||
import com.codigoparallevar.minicards.types.PartConnection;
|
||||
import com.codigoparallevar.minicards.types.PartGrid;
|
||||
import com.codigoparallevar.minicards.types.Tuple2;
|
||||
import com.codigoparallevar.minicards.types.functional.Tuple2;
|
||||
import com.codigoparallevar.minicards.types.connectors.input.InputConnector;
|
||||
import com.codigoparallevar.minicards.types.connectors.input.SignalInputConnector;
|
||||
import com.codigoparallevar.minicards.types.connectors.output.OutputConnector;
|
||||
@ -128,20 +129,41 @@ public class Toggle implements Part {
|
||||
}
|
||||
|
||||
private void drawConnector(ScrolledCanvas canvas) {
|
||||
Paint inputConnectorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
inputConnectorPaint.setColor(Color.YELLOW);
|
||||
// Input
|
||||
Paint outerInputConnectorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
outerInputConnectorPaint.setColor(CardTheme.PULSE_CONNECTOR_COLOR_OUTER);
|
||||
|
||||
canvas.drawCircle(
|
||||
getInputConnectorCenterX(), getInputConnectorCenterY(),
|
||||
getInputConnectorCenterX(),
|
||||
getInputConnectorCenterY(),
|
||||
getInputConnectRadius(),
|
||||
inputConnectorPaint);
|
||||
outerInputConnectorPaint);
|
||||
|
||||
Paint outputConnectorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
outputConnectorPaint.setColor(Color.RED);
|
||||
Paint innerInputConnectorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
innerInputConnectorPaint.setColor(CardTheme.PULSE_CONNECTOR_COLOR_INNER);
|
||||
|
||||
canvas.drawCircle(_right, getOutputConnectorCenterY(),
|
||||
canvas.drawCircle(
|
||||
getInputConnectorCenterX(),
|
||||
getInputConnectorCenterY(),
|
||||
getInputConnectRadius() / 2,
|
||||
innerInputConnectorPaint);
|
||||
|
||||
// Output
|
||||
Paint outerOutputConnectorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
outerOutputConnectorPaint.setColor(CardTheme.BOOLEAN_CONNECTOR_COLOR_OUTER);
|
||||
|
||||
canvas.drawCircle(getOutputConnectorCenterX(),
|
||||
getOutputConnectorCenterY(),
|
||||
getOutputConnectRadius(),
|
||||
outputConnectorPaint);
|
||||
outerOutputConnectorPaint);
|
||||
|
||||
Paint innerOutputConnectorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
innerOutputConnectorPaint.setColor(CardTheme.BOOLEAN_CONNECTOR_COLOR_INNER);
|
||||
|
||||
canvas.drawCircle(getOutputConnectorCenterX(),
|
||||
getOutputConnectorCenterY(),
|
||||
getOutputConnectRadius() / 2,
|
||||
innerOutputConnectorPaint);
|
||||
}
|
||||
|
||||
private void drawWires(ScrolledCanvas canvas, boolean devMode) {
|
||||
@ -306,7 +328,7 @@ public class Toggle implements Part {
|
||||
}
|
||||
|
||||
public int getInputConnectorCenterX() {
|
||||
return get_left();
|
||||
return (get_left() + get_right()) / 2;
|
||||
}
|
||||
|
||||
private int getInputConnectRadius() {
|
||||
@ -314,15 +336,15 @@ public class Toggle implements Part {
|
||||
}
|
||||
|
||||
public int getInputConnectorCenterY() {
|
||||
return (get_top() + get_bottom()) / 2;
|
||||
return get_top();
|
||||
}
|
||||
|
||||
private int getOutputConnectorCenterX() {
|
||||
return _right;
|
||||
return (get_left() + get_right()) / 2;
|
||||
}
|
||||
|
||||
private int getOutputConnectorCenterY() {
|
||||
return (_top + _bottom) / 2;
|
||||
return get_bottom();
|
||||
}
|
||||
|
||||
private int getOutputConnectRadius() {
|
||||
|
@ -8,10 +8,11 @@ import android.util.Log;
|
||||
import com.codigoparallevar.minicards.PartInstantiator;
|
||||
import com.codigoparallevar.minicards.ScrolledCanvas;
|
||||
import com.codigoparallevar.minicards.parts.connectors.BooleanRoundInputConnector;
|
||||
import com.codigoparallevar.minicards.parts.style.CardTheme;
|
||||
import com.codigoparallevar.minicards.types.Moveable;
|
||||
import com.codigoparallevar.minicards.types.Part;
|
||||
import com.codigoparallevar.minicards.types.PartGrid;
|
||||
import com.codigoparallevar.minicards.types.Tuple2;
|
||||
import com.codigoparallevar.minicards.types.functional.Tuple2;
|
||||
import com.codigoparallevar.minicards.types.connectors.input.BooleanInputConnector;
|
||||
import com.codigoparallevar.minicards.types.connectors.input.InputConnector;
|
||||
import com.codigoparallevar.minicards.types.connectors.output.OutputConnector;
|
||||
@ -105,13 +106,23 @@ public class ColorBox implements Part {
|
||||
}
|
||||
|
||||
private void drawConnector(ScrolledCanvas canvas) {
|
||||
Paint connectorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
connectorPaint.setColor(Color.YELLOW);
|
||||
Paint outerInputConnectorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
outerInputConnectorPaint.setColor(CardTheme.BOOLEAN_CONNECTOR_COLOR_OUTER);
|
||||
|
||||
canvas.drawCircle(
|
||||
getInputConnectorCenterX(), getInputConnectorCenterY(),
|
||||
getInputConnectorCenterX(),
|
||||
getInputConnectorCenterY(),
|
||||
getInputConnectRadius(),
|
||||
connectorPaint);
|
||||
outerInputConnectorPaint);
|
||||
|
||||
Paint innerInputConnectorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
innerInputConnectorPaint.setColor(CardTheme.BOOLEAN_CONNECTOR_COLOR_INNER);
|
||||
|
||||
canvas.drawCircle(
|
||||
getInputConnectorCenterX(),
|
||||
getInputConnectorCenterY(),
|
||||
getInputConnectRadius() / 2,
|
||||
innerInputConnectorPaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -238,7 +249,7 @@ public class ColorBox implements Part {
|
||||
}
|
||||
|
||||
public int getInputConnectorCenterX() {
|
||||
return get_left();
|
||||
return (get_left() + get_right()) / 2;
|
||||
}
|
||||
|
||||
private int getInputConnectRadius() {
|
||||
@ -246,7 +257,7 @@ public class ColorBox implements Part {
|
||||
}
|
||||
|
||||
public int getInputConnectorCenterY() {
|
||||
return (get_top() + get_bottom()) / 2;
|
||||
return get_top();
|
||||
}
|
||||
|
||||
public static PartInstantiator getInstantiator() {
|
||||
|
@ -3,21 +3,23 @@ package com.codigoparallevar.minicards.parts.strings;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.codigoparallevar.minicards.PartInstantiator;
|
||||
import com.codigoparallevar.minicards.ScrolledCanvas;
|
||||
import com.codigoparallevar.minicards.parts.connectors.AnyRoundInputConnector;
|
||||
import com.codigoparallevar.minicards.parts.connectors.StringRoundOutputConnector;
|
||||
import com.codigoparallevar.minicards.parts.style.CardTheme;
|
||||
import com.codigoparallevar.minicards.types.Moveable;
|
||||
import com.codigoparallevar.minicards.types.Part;
|
||||
import com.codigoparallevar.minicards.types.PartConnection;
|
||||
import com.codigoparallevar.minicards.types.PartGrid;
|
||||
import com.codigoparallevar.minicards.types.Tuple2;
|
||||
import com.codigoparallevar.minicards.types.connectors.input.AnyInputConnector;
|
||||
import com.codigoparallevar.minicards.types.connectors.input.InputConnector;
|
||||
import com.codigoparallevar.minicards.types.connectors.output.OutputConnector;
|
||||
import com.codigoparallevar.minicards.types.functional.Tuple2;
|
||||
import com.codigoparallevar.minicards.types.wireData.StringSignal;
|
||||
import com.codigoparallevar.minicards.types.wireData.WireDataType;
|
||||
import com.codigoparallevar.minicards.utils.Serializations;
|
||||
@ -110,7 +112,7 @@ public class ConvertToString implements Part {
|
||||
drawWires(canvas, devMode);
|
||||
|
||||
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
paint.setColor(Color.WHITE);
|
||||
paint.setColor(Color.BLACK);
|
||||
canvas.drawRect(
|
||||
new Rect(_left, _top,
|
||||
_right, _bottom),
|
||||
@ -120,27 +122,48 @@ public class ConvertToString implements Part {
|
||||
textPaint.setColor(Color.GREEN);
|
||||
textPaint.setTextSize(100);
|
||||
canvas.drawText(_lastValue,
|
||||
_left,
|
||||
_top - 5,
|
||||
_left + 10,
|
||||
_bottom - 10,
|
||||
textPaint);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawConnector(ScrolledCanvas canvas) {
|
||||
Paint inputConnectorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
inputConnectorPaint.setColor(Color.YELLOW);
|
||||
// Input
|
||||
Paint inputOuterConnectorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
inputOuterConnectorPaint.setColor(CardTheme.ANY_CONNECTOR_COLOR_OUTER);
|
||||
|
||||
canvas.drawCircle(
|
||||
getInputConnectorCenterX(), getInputConnectorCenterY(),
|
||||
getInputConnectorCenterX(),
|
||||
getInputConnectorCenterY(),
|
||||
getInputConnectRadius(),
|
||||
inputConnectorPaint);
|
||||
inputOuterConnectorPaint);
|
||||
|
||||
Paint outputConnectorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
outputConnectorPaint.setColor(Color.RED);
|
||||
Paint inputInnerConnectorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
inputInnerConnectorPaint.setColor(CardTheme.ANY_CONNECTOR_COLOR_INNER);
|
||||
|
||||
canvas.drawCircle(_right, getOutputConnectorCenterY(),
|
||||
canvas.drawCircle(
|
||||
getInputConnectorCenterX(),
|
||||
getInputConnectorCenterY(),
|
||||
getInputConnectRadius() / 2,
|
||||
inputInnerConnectorPaint);
|
||||
|
||||
// Output
|
||||
Paint outputOuterConnectorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
outputOuterConnectorPaint.setColor(CardTheme.STRING_CONNECTOR_COLOR_OUTER);
|
||||
|
||||
canvas.drawCircle(getOutputConnectorCenterX(),
|
||||
getOutputConnectorCenterY(),
|
||||
getOutputConnectRadius(),
|
||||
outputConnectorPaint);
|
||||
outputOuterConnectorPaint);
|
||||
|
||||
Paint outputInnerConnectorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
outputInnerConnectorPaint.setColor(CardTheme.STRING_CONNECTOR_COLOR_INNER);
|
||||
|
||||
canvas.drawCircle(getOutputConnectorCenterX(),
|
||||
getOutputConnectorCenterY(),
|
||||
getOutputConnectRadius() / 2,
|
||||
outputInnerConnectorPaint);
|
||||
}
|
||||
|
||||
private void drawWires(ScrolledCanvas canvas, boolean devMode) {
|
||||
@ -228,7 +251,6 @@ public class ConvertToString implements Part {
|
||||
public void send(InputConnector roundInputConnector, WireDataType signal) {
|
||||
String encoded = "null";
|
||||
Object value = signal.get();
|
||||
|
||||
if (value != null){
|
||||
encoded = value.toString();
|
||||
}
|
||||
@ -315,7 +337,7 @@ public class ConvertToString implements Part {
|
||||
}
|
||||
|
||||
public int getInputConnectorCenterX() {
|
||||
return get_left();
|
||||
return (get_left() + get_right()) / 2;
|
||||
}
|
||||
|
||||
private int getInputConnectRadius() {
|
||||
@ -323,15 +345,15 @@ public class ConvertToString implements Part {
|
||||
}
|
||||
|
||||
public int getInputConnectorCenterY() {
|
||||
return (get_top() + get_bottom()) / 2;
|
||||
return get_top();
|
||||
}
|
||||
|
||||
private int getOutputConnectorCenterX() {
|
||||
return _right;
|
||||
return (get_left() + get_right()) / 2;
|
||||
}
|
||||
|
||||
private int getOutputConnectorCenterY() {
|
||||
return (_top + _bottom) / 2;
|
||||
return get_bottom();
|
||||
}
|
||||
|
||||
private int getOutputConnectRadius() {
|
||||
|
@ -0,0 +1,31 @@
|
||||
package com.codigoparallevar.minicards.parts.style;
|
||||
|
||||
import android.graphics.Color;
|
||||
|
||||
public class CardTheme {
|
||||
public static final int DEFAULT_COLOR_INNER = Color.parseColor("#000000");
|
||||
|
||||
public static final int PULSE_CONNECTOR_COLOR_OUTER = Color.parseColor("#f0c000");
|
||||
public static final int PULSE_CONNECTOR_COLOR_INNER = DEFAULT_COLOR_INNER;
|
||||
|
||||
public static final int BOOLEAN_CONNECTOR_COLOR_OUTER = Color.parseColor("#dd4444");
|
||||
public static final int BOOLEAN_CONNECTOR_COLOR_INNER = DEFAULT_COLOR_INNER;
|
||||
|
||||
public static final int STRING_CONNECTOR_COLOR_OUTER = Color.parseColor("#44dd44");
|
||||
public static final int STRING_CONNECTOR_COLOR_INNER = DEFAULT_COLOR_INNER;
|
||||
|
||||
public static final int ANY_CONNECTOR_COLOR_OUTER = Color.parseColor("#dd44dd");
|
||||
public static final int ANY_CONNECTOR_COLOR_INNER = DEFAULT_COLOR_INNER;
|
||||
|
||||
public static final int ENUM_CONNECTOR_COLOR_OUTER = Color.parseColor("#888888");
|
||||
public static final int ENUM_CONNECTOR_COLOR_INNER = DEFAULT_COLOR_INNER;
|
||||
|
||||
public static final int FLOAT_CONNECTOR_COLOR_OUTER = Color.parseColor("#44dddd");
|
||||
public static final int FLOAT_CONNECTOR_COLOR_INNER = DEFAULT_COLOR_INNER;
|
||||
|
||||
public static final int INTEGER_CONNECTOR_COLOR_OUTER = Color.parseColor("#4444ff");
|
||||
public static final int INTEGER_CONNECTOR_COLOR_INNER = DEFAULT_COLOR_INNER;
|
||||
|
||||
public static final int UNKNOWN_CONNECTOR_COLOR_OUTER = Color.parseColor("#7f7f7f");
|
||||
public static final int UNKNOWN_CONNECTOR_COLOR_INNER = DEFAULT_COLOR_INNER;
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package com.codigoparallevar.minicards.toolbox;
|
||||
|
||||
import com.codigoparallevar.minicards.PartInstantiator;
|
||||
import com.codigoparallevar.minicards.types.functional.Tuple2;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
class PartCategory {
|
||||
private final String _name;
|
||||
private final List<Tuple2<String, PartInstantiator>> _parts;
|
||||
|
||||
public PartCategory(String name, List<Tuple2<String, PartInstantiator>> parts) {
|
||||
this._name = name;
|
||||
this._parts = parts;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return _name;
|
||||
}
|
||||
|
||||
public List<Tuple2<String, PartInstantiator>> getParts() {
|
||||
return this._parts;
|
||||
}
|
||||
}
|
@ -0,0 +1,132 @@
|
||||
package com.codigoparallevar.minicards.toolbox;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import android.util.Log;
|
||||
|
||||
import com.codigoparallevar.minicards.CanvasView;
|
||||
import com.codigoparallevar.minicards.PartInstantiator;
|
||||
import com.codigoparallevar.minicards.parts.buttons.RoundButton;
|
||||
import com.codigoparallevar.minicards.parts.logic.Ticker;
|
||||
import com.codigoparallevar.minicards.parts.logic.Toggle;
|
||||
import com.codigoparallevar.minicards.parts.samples.ColorBox;
|
||||
import com.codigoparallevar.minicards.parts.strings.ConvertToString;
|
||||
import com.codigoparallevar.minicards.types.Part;
|
||||
import com.codigoparallevar.minicards.types.functional.Tuple2;
|
||||
import com.programaker.api.data.ProgramakerBridgeInfo;
|
||||
import com.programaker.api.data.ProgramakerCustomBlock;
|
||||
import com.programaker.api.data.api_results.ProgramakerBridgeCustomBlockResult;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Vector;
|
||||
|
||||
public class PartsHolder {
|
||||
private final Context context;
|
||||
|
||||
private final static List<Tuple2<String, PartInstantiator>> BuiltInParts =
|
||||
new Vector<Tuple2<String, PartInstantiator>>(){{
|
||||
add(new Tuple2<>("Round button", RoundButton.getInstantiator()));
|
||||
add(new Tuple2<>("Ticker", Ticker.getInstantiator()));
|
||||
add(new Tuple2<>("Red/Green box", ColorBox.getInstantiator()));
|
||||
add(new Tuple2<>("Toggle", Toggle.getInstantiator()));
|
||||
add(new Tuple2<>("ToString", ConvertToString.getInstantiator()));
|
||||
}};
|
||||
|
||||
private final static List<PartCategory> Categories = new Vector<PartCategory>() {{
|
||||
add(new PartCategory("Testing", BuiltInParts));
|
||||
}};
|
||||
private Map<String, ProgramakerBridgeInfo> bridgeInfoMap;
|
||||
|
||||
public PartsHolder(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public void openAddPartModal(final CanvasView canvasView) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
Map<String, PartCategory> categoryOptions = getPartCategories();
|
||||
String[] categoryNames = categoryOptions.keySet().toArray(new String[0]);
|
||||
|
||||
builder.setTitle("Choose part category")
|
||||
.setItems(categoryNames, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int whichCategory) {
|
||||
if ((whichCategory >= 0) && (whichCategory < categoryNames.length)){
|
||||
Map<String, PartInstantiator> parts = getPartTypes(categoryOptions.get(categoryNames[whichCategory]));
|
||||
String[] partNames = parts.keySet().toArray(new String[0]);
|
||||
|
||||
builder.setTitle("Choose part type")
|
||||
.setItems(partNames, (dialog1, whichPart) -> {
|
||||
if ((whichPart >= 0) && (whichPart < partNames.length)) {
|
||||
String partName = partNames[whichPart];
|
||||
|
||||
Log.d("Minicards partsHolder",
|
||||
"Spawning " + partName);
|
||||
|
||||
PartInstantiator instantiator = parts.get(partName);
|
||||
PartsHolder.this.runInstantiator(instantiator, canvasView);
|
||||
}
|
||||
});
|
||||
|
||||
Dialog dialog2 = builder.create();
|
||||
dialog2.show();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Dialog dialog = builder.create();
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
private void runInstantiator(PartInstantiator instantiator, CanvasView canvasView) {
|
||||
Part part = instantiator.build(canvasView);
|
||||
canvasView.addPart(part);
|
||||
}
|
||||
|
||||
private static Map<String,PartCategory> getPartCategories() {
|
||||
Map<String, PartCategory> partTypes = new LinkedHashMap<>(Categories.size());
|
||||
for (int i = 0; i < Categories.size(); i++){
|
||||
partTypes.put(Categories.get(i).getName(), Categories.get(i));
|
||||
}
|
||||
|
||||
return partTypes;
|
||||
}
|
||||
|
||||
private static Map<String, PartInstantiator> getPartTypes(PartCategory categoryOption) {
|
||||
HashMap<String, PartInstantiator> partMap = new LinkedHashMap<>();
|
||||
|
||||
for (Tuple2<String, PartInstantiator> part : categoryOption.getParts()) {
|
||||
partMap.put(part.item1, part.item2);
|
||||
}
|
||||
|
||||
return partMap;
|
||||
}
|
||||
|
||||
public void addCustomBlocks(List<ProgramakerBridgeInfo> bridgeInfo, List<ProgramakerBridgeCustomBlockResult> customBlocks) {
|
||||
Map<String, ProgramakerBridgeInfo> bridgeInfoMap = new LinkedHashMap<>();
|
||||
for (ProgramakerBridgeInfo info : bridgeInfo) {
|
||||
bridgeInfoMap.put(info.getId(), info);
|
||||
}
|
||||
|
||||
this.bridgeInfoMap = bridgeInfoMap;
|
||||
|
||||
for (ProgramakerBridgeCustomBlockResult res : customBlocks) {
|
||||
|
||||
List<Tuple2<String, PartInstantiator>> parts = new LinkedList<>();
|
||||
for (ProgramakerCustomBlock block : res.getBlocks()) {
|
||||
parts.add(new Tuple2<>(block.getMessage(), new ProgramakerCustomBlockInstantiator(block)));
|
||||
}
|
||||
|
||||
if (bridgeInfoMap.containsKey(res.getBridge_id())) {
|
||||
Categories.add(new PartCategory(bridgeInfoMap.get(res.getBridge_id()).getName(), parts));
|
||||
}
|
||||
else {
|
||||
Categories.add(new PartCategory("Bridge ID="+res.getBridge_id(), parts));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package com.codigoparallevar.minicards.toolbox;
|
||||
|
||||
import com.codigoparallevar.minicards.PartInstantiator;
|
||||
import com.codigoparallevar.minicards.parts.ProgramakerCustomBlockPart;
|
||||
import com.codigoparallevar.minicards.parts.buttons.RoundButton;
|
||||
import com.codigoparallevar.minicards.types.Part;
|
||||
import com.codigoparallevar.minicards.types.PartGrid;
|
||||
import com.codigoparallevar.minicards.types.functional.Tuple2;
|
||||
import com.programaker.api.data.ProgramakerCustomBlock;
|
||||
|
||||
class ProgramakerCustomBlockInstantiator extends PartInstantiator {
|
||||
private final ProgramakerCustomBlock _block;
|
||||
|
||||
public ProgramakerCustomBlockInstantiator(ProgramakerCustomBlock block) {
|
||||
this._block = block;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Part instantiate(PartGrid grid, Tuple2<Integer, Integer> center) {
|
||||
return new ProgramakerCustomBlockPart(grid, center, this._block);
|
||||
}
|
||||
}
|
@ -38,4 +38,17 @@ public class PartConnection {
|
||||
|
||||
return serialized;
|
||||
}
|
||||
|
||||
public static JSONObject serializeToJson(final String inputConnectorId, final String inputPartId) {
|
||||
|
||||
JSONObject serialized = new JSONObject();
|
||||
try {
|
||||
serialized.put(PartConnection.INPUT_CONNECTOR_ID_KEY, inputConnectorId);
|
||||
serialized.put(PartConnection.INPUT_PART_ID_KEY, inputPartId);
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return serialized;
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,12 @@ package com.codigoparallevar.minicards.types;
|
||||
import com.codigoparallevar.minicards.types.connectors.input.BooleanInputConnector;
|
||||
import com.codigoparallevar.minicards.types.connectors.input.SignalInputConnector;
|
||||
import com.codigoparallevar.minicards.types.connectors.input.StringInputConnector;
|
||||
import com.codigoparallevar.minicards.types.functional.Tuple2;
|
||||
import com.programaker.api.ProgramakerApi;
|
||||
|
||||
public interface PartGrid {
|
||||
Selectable getPartOn(int x, int y);
|
||||
ProgramakerApi getApi();
|
||||
|
||||
SignalInputConnector getSignalInputConnectorOn(int x, int y);
|
||||
BooleanInputConnector getBooleanInputConnectorOn(int x, int y);
|
||||
|
@ -53,13 +53,13 @@ public class Wire<T extends WireDataType, InputConnectorType extends InputConnec
|
||||
|
||||
samplePath.moveTo(_xinit, _yinit);
|
||||
samplePath.cubicTo(
|
||||
_xinit + _pathRunWay, _yinit,
|
||||
_xend - _pathRunWay, _yend,
|
||||
_xinit, _yinit + _pathRunWay,
|
||||
_xend, _yend - _pathRunWay,
|
||||
_xend, _yend);
|
||||
|
||||
Paint pathPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
pathPaint.setColor(Color.GREEN);
|
||||
pathPaint.setStrokeWidth(5.0f);
|
||||
pathPaint.setColor(Color.parseColor("#44FF44"));
|
||||
pathPaint.setStrokeWidth(10.0f);
|
||||
pathPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||
pathPaint.setStyle(Paint.Style.STROKE);
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
package com.codigoparallevar.minicards.types.connectors.input;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.codigoparallevar.minicards.types.Moveable;
|
||||
import com.codigoparallevar.minicards.types.Part;
|
||||
|
@ -3,9 +3,9 @@ package com.codigoparallevar.minicards.types.connectors.output;
|
||||
import com.codigoparallevar.minicards.ScrolledCanvas;
|
||||
import com.codigoparallevar.minicards.types.Dropper;
|
||||
import com.codigoparallevar.minicards.types.Selectable;
|
||||
import com.codigoparallevar.minicards.types.Tuple2;
|
||||
import com.codigoparallevar.minicards.types.connectors.Wiring.Wire;
|
||||
import com.codigoparallevar.minicards.types.connectors.input.InputConnector;
|
||||
import com.codigoparallevar.minicards.types.functional.Tuple2;
|
||||
import com.codigoparallevar.minicards.types.wireData.WireDataType;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -0,0 +1,5 @@
|
||||
package com.codigoparallevar.minicards.types.functional;
|
||||
|
||||
public interface Action {
|
||||
void run() throws InterruptedException;
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package com.codigoparallevar.minicards.types.functional;
|
||||
|
||||
public class Box<T> {
|
||||
private T value;
|
||||
|
||||
public Box(T value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public void set(T value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.codigoparallevar.minicards.types.functional;
|
||||
|
||||
public interface Consumer<T> {
|
||||
void apply(T param) throws Exception;
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.codigoparallevar.minicards.types.functional;
|
||||
|
||||
public interface Producer<T> {
|
||||
T get();
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.codigoparallevar.minicards.types;
|
||||
package com.codigoparallevar.minicards.types.functional;
|
||||
|
||||
public class Tuple2<T1, T2> {
|
||||
public final T1 item1;
|
@ -0,0 +1,19 @@
|
||||
package com.codigoparallevar.minicards.types.functional;
|
||||
|
||||
public class Tuple3<T, T1, T2> {
|
||||
|
||||
public final T _x;
|
||||
public final T1 _y;
|
||||
public final T2 _z;
|
||||
|
||||
public Tuple3(T x, T1 y, T2 z) {
|
||||
_x = x;
|
||||
_y = y;
|
||||
_z = z;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
return "(" + _x + " - " + _y + ", " + _z + ")";
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.codigoparallevar.minicards.types;
|
||||
package com.codigoparallevar.minicards.types.functional;
|
||||
|
||||
public class Tuple4<T, T1, T2, T3> {
|
||||
|
@ -0,0 +1,27 @@
|
||||
package com.codigoparallevar.minicards.ui_helpers;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
import android.util.Log;
|
||||
|
||||
import com.codigoparallevar.minicards.types.functional.Action;
|
||||
import com.codigoparallevar.minicards.types.functional.Consumer;
|
||||
import com.codigoparallevar.minicards.types.functional.Tuple2;
|
||||
|
||||
public class DoAsync extends AsyncTask<Tuple2<Action, Consumer<Throwable>>,
|
||||
Void, Void> {
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Tuple2<Action, Consumer<Throwable>>... data) {
|
||||
try {
|
||||
data[0].item1.run();
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
try {
|
||||
data[0].item2.apply(ex);
|
||||
} catch (Throwable subEx) {
|
||||
Log.e("DoAsync", "Error handling exception", subEx);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
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;
|
||||
import com.codigoparallevar.minicards.types.functional.Tuple3;
|
||||
|
||||
public class GetAsync<T> extends AsyncTask<Tuple3<Producer<T>, Consumer<T>, Consumer<Throwable>>,
|
||||
Void,
|
||||
Tuple2<T, Consumer<T>>> {
|
||||
|
||||
@Override
|
||||
protected Tuple2<T, Consumer<T>> doInBackground(Tuple3<Producer<T>, Consumer<T>, Consumer<Throwable>>... data) {
|
||||
try {
|
||||
T result = data[0]._x.get();
|
||||
return new Tuple2<>(result, data[0]._y);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
try {
|
||||
data[0]._z.apply(ex);
|
||||
}
|
||||
catch (Throwable subEx) {
|
||||
Log.d("GetAsync", "Error handling exception", subEx);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Tuple2<T, Consumer<T>> result) {
|
||||
if (result == null) {
|
||||
return;
|
||||
}
|
||||
else {
|
||||
try {
|
||||
result.item2.apply(result.item1);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
Log.e("GetAsync", "Error on UI thread", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
319
app/src/main/java/com/programaker/api/ProgramakerApi.kt
Normal file
@ -0,0 +1,319 @@
|
||||
package com.programaker.api
|
||||
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.programaker.api.data.ProgramakerBridgeInfo
|
||||
import com.programaker.api.data.ProgramakerCustomBlock
|
||||
import com.programaker.api.data.ProgramakerFunctionCallResult
|
||||
import com.programaker.api.data.api_results.*
|
||||
import com.programaker.api.exceptions.ProgramakerLoginRequiredException
|
||||
import com.programaker.api.exceptions.ProgramakerProtocolException
|
||||
import com.programaker.api.exceptions.TokenNotFoundException
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import java.io.DataOutputStream
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.InputStream
|
||||
import java.io.InputStreamReader
|
||||
import java.lang.reflect.Type
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.net.URLConnection
|
||||
|
||||
|
||||
class ProgramakerApi(private val ApiRoot: String="https://programaker.com/api") {
|
||||
private val LogTag: String = "ProgramakerApi"
|
||||
|
||||
private var userId: String? = null
|
||||
private var userName: String? = null
|
||||
var token: String? = null
|
||||
|
||||
// API
|
||||
fun check(): Boolean {
|
||||
val conn = URL(getCheckUrl()).openConnection() as HttpURLConnection
|
||||
if (conn == null){
|
||||
Log.e(LogTag, "URL Connection not established, set to NULL")
|
||||
return false
|
||||
}
|
||||
try {
|
||||
addAuthHeader(conn)
|
||||
} catch(ex: TokenNotFoundException) {
|
||||
return false
|
||||
}
|
||||
|
||||
val result: ProgramakerCheckResult
|
||||
try {
|
||||
result = parseJson(conn.inputStream, ProgramakerCheckResult::class.java)
|
||||
} catch(ex: JsonParseException) {
|
||||
ex.logError(LogTag)
|
||||
return false
|
||||
} catch (ex: FileNotFoundException) {
|
||||
ex.logError(LogTag)
|
||||
return false
|
||||
}
|
||||
|
||||
if (result.success) {
|
||||
this.userId = result.user_id;
|
||||
this.userName = result.username;
|
||||
}
|
||||
|
||||
return result.success
|
||||
}
|
||||
|
||||
|
||||
fun login(user: String, password: String): String {
|
||||
val conn = URL(getLoginUrl()).openConnection() as HttpURLConnection
|
||||
conn.setRequestProperty("Content-Type", "application/json")
|
||||
|
||||
conn.requestMethod = "POST";
|
||||
conn.doOutput = true;
|
||||
|
||||
val postData = JSONObject()
|
||||
postData.put("username", user)
|
||||
postData.put("password", password)
|
||||
|
||||
val wr = DataOutputStream(conn.outputStream)
|
||||
wr.writeBytes(postData.toString());
|
||||
wr.flush();
|
||||
wr.close();
|
||||
|
||||
val result: ProgramakerLoginResult
|
||||
result = parseJson(conn.inputStream, ProgramakerLoginResult::class.java)
|
||||
return result.token
|
||||
}
|
||||
|
||||
fun fetchCustomBlocks(): List<ProgramakerBridgeCustomBlockResult> {
|
||||
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()
|
||||
}
|
||||
catch (ex: Exception) {
|
||||
Log.e(LogTag, "Unexpected exception: " + ex, ex)
|
||||
throw ProgramakerProtocolException()
|
||||
}
|
||||
return result.result
|
||||
}
|
||||
|
||||
fun fetchConnectedBridges(): List<ProgramakerBridgeInfo> {
|
||||
val conn = URL(getConnectedBridgesUrl()).openConnection() as HttpURLConnection
|
||||
|
||||
addAuthHeader(conn)
|
||||
|
||||
val result: ProgramakerGetBridgeInfoResult
|
||||
|
||||
try {
|
||||
val builder = GsonBuilder()
|
||||
builder.registerTypeAdapter(ProgramakerGetBridgeInfoResult::class.java, ProgramakerGetBridgeInfoTypeAdapter())
|
||||
|
||||
val gson = builder.create()
|
||||
val reader = InputStreamReader(conn.inputStream)
|
||||
|
||||
result = gson.fromJson(reader, ProgramakerGetBridgeInfoResult::class.java)
|
||||
} catch(ex: JsonParseException) {
|
||||
ex.logError(LogTag)
|
||||
throw ProgramakerProtocolException()
|
||||
} catch (ex: FileNotFoundException) {
|
||||
ex.logError(LogTag)
|
||||
throw ProgramakerProtocolException()
|
||||
}
|
||||
return result.result
|
||||
}
|
||||
|
||||
fun callBlock(block: ProgramakerCustomBlock, arguments: List<String>): ProgramakerFunctionCallResult {
|
||||
val conn = URL(getBlockUrl(block)).openConnection() as HttpURLConnection
|
||||
conn.setRequestProperty("Content-Type", "application/json")
|
||||
addAuthHeader(conn)
|
||||
|
||||
conn.requestMethod = "POST";
|
||||
conn.doOutput = true;
|
||||
|
||||
val postData = JSONObject(hashMapOf(
|
||||
"arguments" to JSONArray(arguments)
|
||||
) as Map<*, *>)
|
||||
|
||||
val wr = DataOutputStream(conn.outputStream)
|
||||
wr.writeBytes(postData.toString());
|
||||
wr.flush();
|
||||
wr.close();
|
||||
|
||||
val result: ProgramakerFunctionCallResult
|
||||
result = parseJson(conn.inputStream, ProgramakerFunctionCallResult::class.java)
|
||||
return result
|
||||
}
|
||||
|
||||
fun createBridge(name: String): String {
|
||||
val conn = URL(getCreateBridgeUrl()).openConnection() as HttpURLConnection
|
||||
conn.setRequestProperty("Content-Type", "application/json")
|
||||
addAuthHeader(conn)
|
||||
|
||||
conn.requestMethod = "POST";
|
||||
conn.doOutput = true;
|
||||
|
||||
val postData = JSONObject(hashMapOf(
|
||||
"name" to name
|
||||
) as Map<*, *>)
|
||||
|
||||
val wr = DataOutputStream(conn.outputStream)
|
||||
wr.writeBytes(postData.toString());
|
||||
wr.flush();
|
||||
wr.close();
|
||||
|
||||
val result: ProgramakerCreateBridgeResult
|
||||
result = parseJson(conn.inputStream, ProgramakerCreateBridgeResult::class.java)
|
||||
return result.getBridgeId()
|
||||
}
|
||||
|
||||
fun establishConnection(bridgeId: String): Boolean {
|
||||
// NOTE: This establishes a connection to a bridge as long as it can be done without interaction
|
||||
|
||||
// Prepare connection
|
||||
val prepareConn = URL(getPrepareConnectionUrl(bridgeId)).openConnection() as HttpURLConnection
|
||||
addAuthHeader(prepareConn)
|
||||
|
||||
val prepareResult: ProgramakerPrepareConnectionResult
|
||||
prepareResult = parseJson(prepareConn.inputStream, ProgramakerPrepareConnectionResult::class.java)
|
||||
if (prepareResult.type != "direct") {
|
||||
throw Exception("Expected 'direct' connection type, found '${prepareResult.type}'")
|
||||
}
|
||||
|
||||
// Establish connection
|
||||
val establishConn = URL(getEstablishConnectionUrl(bridgeId)).openConnection() as HttpURLConnection
|
||||
establishConn.setRequestProperty("Content-Type", "application/json")
|
||||
addAuthHeader(establishConn)
|
||||
|
||||
establishConn.requestMethod = "POST";
|
||||
establishConn.doOutput = true;
|
||||
|
||||
val establishWrite = DataOutputStream(establishConn.outputStream)
|
||||
establishWrite.writeBytes(JSONObject().toString());
|
||||
establishWrite.flush();
|
||||
establishWrite.close();
|
||||
|
||||
val establishResult: ProgramakerEstablishDirectConnectionResult
|
||||
establishResult = parseJson(establishConn.inputStream, ProgramakerEstablishDirectConnectionResult::class.java)
|
||||
return establishResult.success
|
||||
}
|
||||
|
||||
// Initialization
|
||||
init {
|
||||
// Disable connection reuse if necessary
|
||||
// HTTP connection reuse which was buggy pre-froyo
|
||||
if (Build.VERSION.SDK.toInt() < Build.VERSION_CODES.FROYO) {
|
||||
System.setProperty("http.keepAlive", "false")
|
||||
}
|
||||
}
|
||||
|
||||
// Private functions
|
||||
private fun getCheckUrl(): String {
|
||||
return "$ApiRoot/v0/sessions/check"
|
||||
}
|
||||
|
||||
private fun getLoginUrl(): String {
|
||||
return "$ApiRoot/v0/sessions/login"
|
||||
}
|
||||
|
||||
private fun getCustomBlocksUrl(): String {
|
||||
this.withUserName()
|
||||
return "$ApiRoot/v0/users/$userName/custom-blocks/"
|
||||
}
|
||||
|
||||
private fun getConnectedBridgesUrl(): String {
|
||||
this.withUserName()
|
||||
return "$ApiRoot/v0/users/$userName/bridges/"
|
||||
}
|
||||
|
||||
private fun getBlockUrl(block: ProgramakerCustomBlock): String {
|
||||
this.withUserId()
|
||||
return "$ApiRoot/v0/users/id/$userId/bridges/id/${block.bridge_id}/functions/${block.function_name}"
|
||||
}
|
||||
|
||||
private fun getCreateBridgeUrl(): String {
|
||||
this.withUserName()
|
||||
return "$ApiRoot/v0/users/$userName/bridges"
|
||||
}
|
||||
|
||||
private fun getPrepareConnectionUrl(bridgeId: String): String {
|
||||
this.withUserName()
|
||||
return "$ApiRoot/v0/users/$userName/services/id/$bridgeId/how-to-enable"
|
||||
}
|
||||
|
||||
private fun getEstablishConnectionUrl(bridgeId: String): String {
|
||||
this.withUserName()
|
||||
return "$ApiRoot/v0/users/$userName/services/id/$bridgeId/register"
|
||||
}
|
||||
|
||||
fun getUserId(): String? {
|
||||
this.withUserId()
|
||||
return userId;
|
||||
}
|
||||
|
||||
private fun withUserName() {
|
||||
if (userName == null) {
|
||||
if (token == null) {
|
||||
throw ProgramakerLoginRequiredException()
|
||||
}
|
||||
|
||||
this.check();
|
||||
if (userName == null) {
|
||||
throw ProgramakerProtocolException()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun withUserId() {
|
||||
if (userId == null) {
|
||||
if (token == null) {
|
||||
throw ProgramakerLoginRequiredException()
|
||||
}
|
||||
|
||||
this.check();
|
||||
if (userId == null) {
|
||||
throw ProgramakerProtocolException()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addAuthHeader(conn: URLConnection) {
|
||||
if (token != null) {
|
||||
conn.setRequestProperty("Authorization", token)
|
||||
}
|
||||
else {
|
||||
throw TokenNotFoundException()
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T>parseJson(content: InputStream, resultClass: Type): T {
|
||||
val reader = InputStreamReader(content)
|
||||
val gson = Gson()
|
||||
return gson.fromJson(reader, resultClass)
|
||||
}
|
||||
}
|
||||
|
||||
private fun FileNotFoundException.logError(tag: String) {
|
||||
Log.e(tag, "Cannot open: ${this.message}")
|
||||
}
|
||||
|
||||
class JsonParseException(private val content: String, private val cls: Type) : Exception() {
|
||||
fun logError(tag: String) {
|
||||
Log.e(tag, "Cannot JSON parse: ${this.content} as ${this.cls}", this)
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.programaker.api.data
|
||||
|
||||
class ProgramakerBridgeInfo (
|
||||
val id: String,
|
||||
val name: String
|
||||
// val is_connected: boolean
|
||||
// val icon: Map<String, Object>
|
||||
) {
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package com.programaker.api.data
|
||||
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
|
||||
class ProgramakerCustomBlock(
|
||||
val block_id: String,
|
||||
val function_name: String,
|
||||
val message: String,
|
||||
val arguments: List<ProgramakerCustomBlockArgument>?,
|
||||
val block_type: String?,
|
||||
val block_result_type: String?,
|
||||
var bridge_id: String?,
|
||||
val save_to: ProgramakerCustomBlockSaveTo?
|
||||
) {
|
||||
fun serialize(): JSONObject {
|
||||
|
||||
val serializedArguments = JSONArray()
|
||||
if (arguments != null) {
|
||||
for (value in arguments) {
|
||||
serializedArguments.put(value.serialize())
|
||||
}
|
||||
}
|
||||
|
||||
var saveToSerialized: JSONObject? = null
|
||||
if (save_to != null) {
|
||||
saveToSerialized = save_to.serialize()
|
||||
}
|
||||
|
||||
val serialized = hashMapOf(
|
||||
"block_id" to block_id,
|
||||
"function_name" to function_name,
|
||||
"message" to message,
|
||||
"arguments" to serializedArguments,
|
||||
"block_type" to block_type,
|
||||
"block_result_type" to block_result_type,
|
||||
"bridge_id" to bridge_id,
|
||||
"save_to" to saveToSerialized
|
||||
);
|
||||
|
||||
return JSONObject(serialized as Map<*, *>)
|
||||
}
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun deserialize(obj: JSONObject): ProgramakerCustomBlock {
|
||||
var block = ProgramakerCustomBlock(
|
||||
obj.getString("block_id"),
|
||||
obj.getString("function_name"),
|
||||
obj.getString("message"),
|
||||
ProgramakerCustomBlockArgument.deserialize(obj.optJSONArray("arguments")),
|
||||
obj.getString("block_type"),
|
||||
obj.getString("block_result_type"),
|
||||
obj.optString("bridge_id"),
|
||||
ProgramakerCustomBlockSaveTo.deserialize(obj.optJSONObject("save_to"))
|
||||
)
|
||||
|
||||
return block
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,57 @@
|
||||
package com.programaker.api.data
|
||||
|
||||
import android.util.Log
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import java.util.*
|
||||
|
||||
class ProgramakerCustomBlockArgument(
|
||||
val type: String?,
|
||||
val default_value: String?,
|
||||
// val class: String,
|
||||
val callback: String?
|
||||
) {
|
||||
fun serialize(): JSONObject {
|
||||
val serialized = hashMapOf<String, String?>(
|
||||
"type" to type,
|
||||
"default_value" to default_value,
|
||||
"callback" to callback
|
||||
)
|
||||
|
||||
return JSONObject(serialized as Map<*, *>)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun deserialize(arguments: JSONArray?): List<ProgramakerCustomBlockArgument> {
|
||||
if (arguments == null) {
|
||||
return listOf();
|
||||
}
|
||||
val results: MutableList<ProgramakerCustomBlockArgument> = LinkedList()
|
||||
|
||||
var i = 0;
|
||||
while (i < arguments.length()) {
|
||||
val obj = arguments.getJSONObject(i)
|
||||
if (obj == null) {
|
||||
Log.e("PMCustomBlockArgument", "Looped into a null value!?")
|
||||
}
|
||||
else {
|
||||
results.add(deserialize(obj))
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
private fun deserialize(arguments: JSONObject): ProgramakerCustomBlockArgument {
|
||||
val type: String? = arguments.optString("type")
|
||||
val default_value: String? = arguments.optString("default_value")
|
||||
val callback: String? = arguments.optString("callback")
|
||||
|
||||
return ProgramakerCustomBlockArgument(type, default_value, callback)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package com.programaker.api.data
|
||||
|
||||
import org.json.JSONObject
|
||||
|
||||
class ProgramakerCustomBlockSaveTo (
|
||||
val type: String,
|
||||
val index: Int
|
||||
) {
|
||||
fun serialize(): JSONObject {
|
||||
return JSONObject(hashMapOf(
|
||||
"type" to type,
|
||||
"index" to index
|
||||
) as Map<*, *>)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun deserialize(save_to: JSONObject?): ProgramakerCustomBlockSaveTo? {
|
||||
if (save_to == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ProgramakerCustomBlockSaveTo(save_to.getString("type"),
|
||||
save_to.getInt("index"))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package com.programaker.api.data
|
||||
|
||||
import java.util.*
|
||||
|
||||
class ProgramakerFunctionCallResult (
|
||||
var success: Boolean,
|
||||
var result: Object
|
||||
) {
|
||||
|
||||
}
|
@ -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>) {
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
package com.programaker.api.data.api_results
|
||||
|
||||
class ProgramakerCheckResult(val success: Boolean, val user_id: String, val username: String) {
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package com.programaker.api.data.api_results
|
||||
|
||||
class ProgramakerCreateBridgeResult (
|
||||
val control_url: String
|
||||
) {
|
||||
fun getBridgeId(): String {
|
||||
val url = control_url.trim('/');
|
||||
if (url.endsWith("communication")) {
|
||||
val no_communication = url.substring(0, url.length - "communication".length);
|
||||
val chunks = no_communication.trim('/').split("/");
|
||||
return chunks[chunks.size - 1]
|
||||
}
|
||||
else {
|
||||
throw IllegalArgumentException("Unknown control_url format")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.programaker.api.data.api_results
|
||||
|
||||
class ProgramakerEstablishDirectConnectionResult(val success: Boolean) {
|
||||
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package com.programaker.api.data.api_results
|
||||
|
||||
import com.programaker.api.data.ProgramakerBridgeInfo
|
||||
|
||||
internal class ProgramakerGetBridgeInfoResult(val result: List<ProgramakerBridgeInfo>) {
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package com.programaker.api.data.api_results
|
||||
|
||||
import com.google.gson.*
|
||||
import com.programaker.api.data.ProgramakerBridgeInfo
|
||||
import com.programaker.api.data.ProgramakerCustomBlock
|
||||
import java.lang.reflect.Type
|
||||
import java.util.*
|
||||
|
||||
internal class ProgramakerGetBridgeInfoTypeAdapter : JsonSerializer<ProgramakerGetBridgeInfoResult?>, JsonDeserializer<ProgramakerGetBridgeInfoResult> {
|
||||
var gson = Gson()
|
||||
override fun serialize(customBlock: ProgramakerGetBridgeInfoResult?, typeOfT: Type, context: JsonSerializationContext): JsonElement {
|
||||
throw NotImplementedError()
|
||||
}
|
||||
|
||||
@Throws(JsonParseException::class)
|
||||
override fun deserialize(element: JsonElement, typeOfT: Type, context: JsonDeserializationContext): ProgramakerGetBridgeInfoResult {
|
||||
val array = element.asJsonArray
|
||||
val bridges: MutableList<ProgramakerBridgeInfo> = LinkedList()
|
||||
|
||||
for (value in array) {
|
||||
|
||||
val bridge = gson.fromJson(value, ProgramakerBridgeInfo::class.java)
|
||||
bridges.add(bridge)
|
||||
}
|
||||
|
||||
return ProgramakerGetBridgeInfoResult(bridges)
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
package com.programaker.api.data.api_results
|
||||
|
||||
internal class ProgramakerGetCustomBlocksResult(val result: List<ProgramakerBridgeCustomBlockResult>) {
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
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.*
|
||||
|
||||
internal 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)
|
||||
block.bridge_id = bridgeId
|
||||
blocks.add(block)
|
||||
}
|
||||
|
||||
val bridge = ProgramakerBridgeCustomBlockResult(bridgeId, blocks)
|
||||
|
||||
bridges.add(bridge)
|
||||
}
|
||||
|
||||
return ProgramakerGetCustomBlocksResult(bridges)
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.programaker.api.data.api_results
|
||||
|
||||
class ProgramakerLoginResult(val user_id: String, val token: String, val success: Boolean) {
|
||||
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.programaker.api.data.api_results
|
||||
|
||||
class ProgramakerPrepareConnectionResult(val type: String) {
|
||||
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.programaker.api.exceptions
|
||||
|
||||
open class ProgramakerConfigurationException : Exception() {
|
||||
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.programaker.api.exceptions
|
||||
|
||||
class ProgramakerLoginRequiredException : Throwable() {
|
||||
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.programaker.api.exceptions
|
||||
|
||||
class ProgramakerNetworkException : Throwable() {
|
||||
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package com.programaker.api.exceptions
|
||||
|
||||
import java.lang.Exception
|
||||
|
||||
class ProgramakerProtocolException() : Exception() {
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package com.programaker.api.exceptions
|
||||
|
||||
import com.programaker.api.exceptions.ProgramakerConfigurationException
|
||||
|
||||
class TokenNotFoundException : ProgramakerConfigurationException() {
|
||||
}
|
176
app/src/main/java/com/programaker/bridge/ProgramakerBridge.kt
Normal file
@ -0,0 +1,176 @@
|
||||
package com.programaker.bridge
|
||||
|
||||
import android.util.Log
|
||||
import com.google.gson.Gson
|
||||
import okhttp3.*
|
||||
import okio.ByteString
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import java.nio.charset.Charset
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
||||
class ProgramakerBridge(
|
||||
private val bridge_id: String,
|
||||
private val user_id: String,
|
||||
private val config: ProgramakerBridgeConfiguration,
|
||||
private val onReady: Runnable,
|
||||
private val onComplete: Runnable
|
||||
) : WebSocketListener() {
|
||||
private var webSocket: WebSocket? = null
|
||||
private val utf8: Charset = Charset.forName("UTF-8")
|
||||
private val gson = Gson()
|
||||
|
||||
private val PING_PERIOD_MILLIS: Long = 15000
|
||||
private val apiRoot: String = "wss://programaker.com/api"
|
||||
private val LogTag = "ProgramakerBridge"
|
||||
|
||||
// Connection establishment
|
||||
fun run() {
|
||||
val client: OkHttpClient = OkHttpClient.Builder()
|
||||
.pingInterval(PING_PERIOD_MILLIS, TimeUnit.MILLISECONDS)
|
||||
.readTimeout(0, TimeUnit.MILLISECONDS)
|
||||
.build()
|
||||
|
||||
val request: Request = Request.Builder()
|
||||
.url(getBridgeControlUrl())
|
||||
.build()
|
||||
client.newWebSocket(request, this)
|
||||
|
||||
// Trigger shutdown of the dispatcher's executor so this process can exit cleanly.
|
||||
client.dispatcher.executorService.shutdown()
|
||||
}
|
||||
|
||||
private fun getBridgeControlUrl(): String {
|
||||
return "$apiRoot/v0/users/id/$user_id/bridges/id/$bridge_id/communication"
|
||||
}
|
||||
|
||||
// Websocket management
|
||||
override fun onOpen(webSocket: WebSocket, response: Response) {
|
||||
this.webSocket = webSocket
|
||||
webSocket.send(config.serialize())
|
||||
onReady.run()
|
||||
}
|
||||
|
||||
override fun onMessage(webSocket: WebSocket, bytes: ByteString) {
|
||||
onMessage(webSocket, bytes.string(utf8))
|
||||
}
|
||||
|
||||
override fun onMessage(webSocket: WebSocket, text: String) {
|
||||
Log.d(LogTag, "Message: $text")
|
||||
val json = gson.fromJson(text, HashMap::class.java)
|
||||
if (json == null) {
|
||||
this.onFailure(webSocket, JSONException("Error decoding: $text"), null)
|
||||
}
|
||||
else {
|
||||
try {
|
||||
this.onCommand(webSocket, json)
|
||||
}
|
||||
catch (ex: Throwable ) {
|
||||
Log.e(LogTag, "Error on command handling: $ex", ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
|
||||
webSocket.close(1000, null)
|
||||
Log.i(LogTag, "Closing bridge socket {code=$code, reason=$reason}")
|
||||
onComplete.run()
|
||||
}
|
||||
|
||||
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
|
||||
Log.e(LogTag, "Error: $t", t)
|
||||
webSocket.close(1000, null)
|
||||
}
|
||||
|
||||
// Protocol handling
|
||||
private fun onCommand(webSocket: WebSocket, json: Map<*, *>) {
|
||||
val type = json.get("type") as String
|
||||
val messageId = json.get("message_id") as String
|
||||
val value = json.get("value") as Map<*, *>
|
||||
var userId: String? = null
|
||||
var extraData: Map<*, *>? = null
|
||||
if (json.containsKey("user_id")) {
|
||||
userId = json.get("user_id") as String
|
||||
}
|
||||
if (json.containsKey("extra_data")) {
|
||||
extraData = json.get("extra_data") as Map<*, *>
|
||||
}
|
||||
|
||||
when (type) {
|
||||
"GET_HOW_TO_SERVICE_REGISTRATION" -> handleGetServiceRegistrationInfo(webSocket, value, messageId, userId, extraData)
|
||||
"REGISTRATION" -> handlePerformRegistration(webSocket, value, messageId, userId, extraData)
|
||||
"FUNCTION_CALL" -> handleFunctionCall(webSocket, value, messageId, userId, extraData)
|
||||
else ->
|
||||
{
|
||||
Log.w(LogTag, "Unknown command type: $type")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleFunctionCall(webSocket: WebSocket, value: Map<*, *>, messageId: String, userId: String?, extraData: Map<*, *>?) {
|
||||
val functionName = value.get("function_name") as String
|
||||
val arguments = value.get("arguments") as List<*>
|
||||
|
||||
try {
|
||||
config.callFunction(functionName, arguments)
|
||||
|
||||
val result = JSONObject.NULL
|
||||
webSocket.send(
|
||||
JSONObject(
|
||||
hashMapOf(
|
||||
"message_id" to messageId,
|
||||
"success" to true,
|
||||
"result" to result
|
||||
) as Map<*, *>
|
||||
).toString()
|
||||
)
|
||||
}
|
||||
catch (ex: Throwable) {
|
||||
Log.w(LogTag, "Error on bridge call to $functionName", ex)
|
||||
webSocket.send(
|
||||
JSONObject(
|
||||
hashMapOf(
|
||||
"message_id" to messageId,
|
||||
"success" to false
|
||||
) as Map<*, *>
|
||||
).toString()
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun handleGetServiceRegistrationInfo(webSocket: WebSocket, value: Map<*, *>, messageId: String?, userId: String?, extraData: Map<*, *>?) {
|
||||
// Registration is automatic
|
||||
webSocket.send(
|
||||
JSONObject(
|
||||
hashMapOf(
|
||||
"message_id" to messageId,
|
||||
"success" to true,
|
||||
"result" to null
|
||||
) as Map<*, *>
|
||||
).toString()
|
||||
)
|
||||
}
|
||||
|
||||
private fun handlePerformRegistration(webSocket: WebSocket, value: Map<*, *>, messageId: String, userId: String?, extraData: Map<*, *>?) {
|
||||
// Registration is automatic
|
||||
if (userId == null) {
|
||||
throw Exception("No connection ID received")
|
||||
}
|
||||
|
||||
webSocket.send(
|
||||
JSONObject(
|
||||
hashMapOf(
|
||||
"message_id" to messageId,
|
||||
"success" to true
|
||||
) as Map<*, *>
|
||||
).toString()
|
||||
)
|
||||
this.config.configManager.bridgeConnectionId = userId
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
webSocket?.close(1000, null)
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package com.programaker.bridge
|
||||
|
||||
import com.codigoparallevar.minicards.ConfigManager
|
||||
import org.json.JSONObject
|
||||
|
||||
class ProgramakerBridgeConfiguration(
|
||||
val serviceName: String,
|
||||
val configManager: ConfigManager,
|
||||
val blocks: List<ProgramakerBridgeConfigurationBlock>
|
||||
// val is_public: boolean, // No reason for this use case
|
||||
// val registration: ???, // No reason for this use case
|
||||
// val allow_multiple_connections: boolean // No reason for this use case
|
||||
// val icon: ???, // Not supported yet // TODO: Add support
|
||||
) {
|
||||
|
||||
fun serialize(): String {
|
||||
var serializedBlocks = listOf<JSONObject>()
|
||||
if (blocks != null) {
|
||||
serializedBlocks = blocks.map { it.serialize() }
|
||||
}
|
||||
|
||||
val config = JSONObject(hashMapOf(
|
||||
"service_name" to serviceName,
|
||||
"is_public" to false,
|
||||
"registration" to null,
|
||||
"allow_multiple_connections" to null,
|
||||
"icon" to null,
|
||||
"blocks" to serializedBlocks
|
||||
) as Map<*, *>)
|
||||
|
||||
val wrapper = JSONObject(hashMapOf(
|
||||
"type" to "CONFIGURATION",
|
||||
"value" to config
|
||||
) as Map<*, *>)
|
||||
|
||||
return wrapper.toString()
|
||||
}
|
||||
|
||||
fun callFunction(functionName: String, arguments: List<*>) {
|
||||
for (block in blocks) {
|
||||
if (block.getFunctionName() == functionName) {
|
||||
return block.call(arguments)
|
||||
}
|
||||
}
|
||||
|
||||
throw IllegalArgumentException("Bridge function (name=$functionName) not found")
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package com.programaker.bridge
|
||||
|
||||
import org.json.JSONObject
|
||||
|
||||
interface ProgramakerBridgeConfigurationBlock {
|
||||
fun serialize() : JSONObject;
|
||||
fun getFunctionName(): String;
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun call(arguments: List<*>)
|
||||
}
|
5
app/src/main/res/drawable/ic_cancel.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:alpha="0.99" android:height="24dp"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000" android:pathData="M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2zM17,15.59L15.59,17 12,13.41 8.41,17 7,15.59 10.59,12 7,8.41 8.41,7 12,10.59 15.59,7 17,8.41 13.41,12 17,15.59z"/>
|
||||
</vector>
|
5
app/src/main/res/drawable/ic_exit_to_app.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:alpha="0.99" android:height="24dp"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF7F7F7F" android:pathData="M10.09,15.59L11.5,17l5,-5 -5,-5 -1.41,1.41L12.67,11H3v2h9.67l-2.58,2.59zM19,3H5c-1.11,0 -2,0.9 -2,2v4h2V5h14v14H5v-4H3v4c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2z"/>
|
||||
</vector>
|
5
app/src/main/res/drawable/ic_start.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:alpha="0.99" android:autoMirrored="true"
|
||||
android:height="24dp" android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM10,16.5v-9l6,4.5 -6,4.5z"/>
|
||||
</vector>
|
12
app/src/main/res/drawable/ic_vector_icon.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<vector android:alpha="0.99" android:autoMirrored="true"
|
||||
android:height="328.6dp" android:viewportHeight="328.6"
|
||||
android:viewportWidth="322" android:width="322dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M90.79,152.763 L287.19,286.563 173.29,25.493c0,0 -12.3,-23.91 -26,0.37 -13.6,24.3 -56.5,126.9 -56.5,126.9z"
|
||||
android:strokeAlpha="1" android:strokeColor="#000000"
|
||||
android:strokeLineCap="butt" android:strokeLineJoin="miter" android:strokeWidth="30.2362"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M32.25,290.863 L132.09,230.763 74.8,191.163Z"
|
||||
android:strokeAlpha="1" android:strokeColor="#000000"
|
||||
android:strokeLineCap="butt" android:strokeLineJoin="miter" android:strokeWidth="30.2362"/>
|
||||
</vector>
|
16
app/src/main/res/drawable/ic_vector_stopped_icon.xml
Normal file
@ -0,0 +1,16 @@
|
||||
<vector android:alpha="0.99" android:autoMirrored="true"
|
||||
android:height="328.6dp" android:viewportHeight="328.6"
|
||||
android:viewportWidth="322" android:width="322dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M90.79,152.763 L287.19,286.563 173.29,25.493c0,0 -12.3,-23.91 -26,0.37 -13.6,24.3 -56.5,126.9 -56.5,126.9z"
|
||||
android:strokeAlpha="1" android:strokeColor="#000000"
|
||||
android:strokeLineCap="butt" android:strokeLineJoin="miter" android:strokeWidth="30.2362"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M32.25,290.863 L132.09,230.763 74.8,191.163Z"
|
||||
android:strokeAlpha="1" android:strokeColor="#000000"
|
||||
android:strokeLineCap="butt" android:strokeLineJoin="miter" android:strokeWidth="30.2362"/>
|
||||
<path android:fillColor="#000000"
|
||||
android:pathData="M-14.73,211.691l338.429,-119.798l12.181,33.237l-338.429,119.798z"
|
||||
android:strokeColor="#ff00ff" android:strokeLineCap="square"
|
||||
android:strokeLineJoin="miter" android:strokeWidth="0"/>
|
||||
</vector>
|
@ -1,28 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<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="com.codigoparallevar.minicards.DeckPreviewActivity">
|
||||
|
||||
<android.support.design.widget.AppBarLayout
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/AppTheme.AppBarOverlay">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
app:popupTheme="@style/AppTheme.PopupOverlay" />
|
||||
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<include layout="@layout/content_deck_preview" />
|
||||
|
||||
<android.support.design.widget.FloatingActionButton
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/create_new_card_fab"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@ -31,4 +31,4 @@
|
||||
android:theme="@style/AppTheme"
|
||||
app:srcCompat="@drawable/ic_add_black" />
|
||||
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.design.widget.CoordinatorLayout
|
||||
<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"
|
||||
@ -26,11 +26,11 @@
|
||||
|
||||
<com.codigoparallevar.minicards.CanvasView
|
||||
android:id="@+id/canvasView"
|
||||
android:layout_width="383dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:layout_editor_absoluteX="1dp" />
|
||||
|
||||
<android.support.design.widget.FloatingActionButton
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/remove_part_fab"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@ -63,7 +63,8 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
fab:fab_colorNormal="@color/white"
|
||||
fab:fab_title="Show deck"
|
||||
app:fab_icon="@drawable/ic_exit_to_app"
|
||||
app:fab_title="@string/go_back"
|
||||
fab:fab_colorPressed="@color/white_pressed"/>
|
||||
|
||||
<com.getbase.floatingactionbutton.FloatingActionButton
|
||||
@ -115,9 +116,10 @@
|
||||
android:id="@+id/show_deck_from_dev_mode_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:fab_icon="@drawable/ic_exit_to_app"
|
||||
app:fab_title="@string/go_back"
|
||||
fab:fab_colorNormal="@color/white"
|
||||
fab:fab_title="Show deck"
|
||||
fab:fab_colorPressed="@color/white_pressed"/>
|
||||
fab:fab_colorPressed="@color/white_pressed" />
|
||||
|
||||
<com.getbase.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/set_user_mode_button"
|
||||
@ -129,4 +131,4 @@
|
||||
|
||||
</com.getbase.floatingactionbutton.FloatingActionsMenu>
|
||||
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
@ -1,58 +1,76 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.v7.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:card_view="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<android.support.v7.widget.CardView
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/card_preview_card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:layout_marginTop="5dp"
|
||||
|
||||
android:paddingBottom="5dp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginLeft="4dp"
|
||||
android:layout_marginTop="1dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_marginRight="4dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:clickable="true"
|
||||
android:focusable="auto"
|
||||
android:foreground="?android:attr/selectableItemBackground"
|
||||
android:paddingLeft="15dp"
|
||||
android:paddingRight="15dp"
|
||||
android:paddingTop="5dp"
|
||||
card_view:cardCornerRadius="5dp"
|
||||
card_view:cardPreventCornerOverlap="true"
|
||||
android:paddingRight="15dp"
|
||||
android:paddingBottom="5dp"
|
||||
card_view:cardCornerRadius="2dp"
|
||||
card_view:cardElevation="4dp"
|
||||
card_view:cardUseCompatPadding="true"
|
||||
tools:layout_editor_absoluteX="8dp"
|
||||
tools:layout_editor_absoluteY="3dp">
|
||||
card_view:layout_constraintBottom_toBottomOf="parent"
|
||||
card_view:layout_constraintEnd_toEndOf="parent"
|
||||
card_view:layout_constraintStart_toStartOf="parent"
|
||||
card_view:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<RelativeLayout
|
||||
android:minHeight="100dp"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="100dp"
|
||||
android:height="100dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/card_preview_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginLeft="20dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="TextView"
|
||||
android:textSize="25dp"
|
||||
tools:layout_editor_absoluteX="8dp"
|
||||
tools:layout_editor_absoluteY="3dp" />
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_marginRight="20dp"
|
||||
android:layout_marginBottom="30dp"
|
||||
android:text="@string/placeholder_text"
|
||||
android:textColor="?android:attr/colorForeground"
|
||||
android:textSize="25sp"
|
||||
card_view:layout_constraintBottom_toBottomOf="parent"
|
||||
card_view:layout_constraintEnd_toEndOf="parent"
|
||||
card_view:layout_constraintStart_toStartOf="parent"
|
||||
card_view:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/card_preview_settings_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:alpha="1"
|
||||
android:clickable="true"
|
||||
android:src="@drawable/ic_settings_gray" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
</android.support.v7.widget.CardView>
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/card_preview_settings_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
</android.support.v7.widget.LinearLayoutCompat>
|
||||
android:layout_marginBottom="4dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
card_view:elevation="4dp"
|
||||
card_view:hoveredFocusedTranslationZ="4dp"
|
||||
card_view:layout_constraintBottom_toBottomOf="parent"
|
||||
card_view:layout_constraintEnd_toEndOf="parent"
|
||||
card_view:srcCompat="@drawable/ic_settings_black" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout 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"
|
||||
@ -8,12 +8,24 @@
|
||||
tools:context="com.codigoparallevar.minicards.DeckPreviewActivity"
|
||||
tools:showIn="@layout/activity_deck_preview">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatButton
|
||||
android:id="@+id/login_in_programaker_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="#FFFFFF"
|
||||
android:text="@string/login_in_programaker"
|
||||
android:textAllCaps="false"
|
||||
app:backgroundTint="@color/colorAccent"
|
||||
app:layout_constraintBottom_toTopOf="@id/card_deck_list"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ListView
|
||||
android:id="@+id/card_deck_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:divider="@null"
|
||||
android:dividerHeight="2dp"
|
||||
android:paddingTop="5dp"
|
||||
android:dividerHeight="2dp" />
|
||||
app:layout_constraintTop_toBottomOf="@id/login_in_programaker_button" />
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
111
app/src/main/res/layout/login_dialog_view.xml
Normal file
@ -0,0 +1,111 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout 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="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_centerHorizontal="true">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/login_username_label"
|
||||
android:layout_width="81dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Username"
|
||||
android:layout_alignBaseline="@id/login_username_text"
|
||||
/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/login_username_text"
|
||||
android:layout_width="225dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="20dp"
|
||||
android:layout_toRightOf="@id/login_username_label"
|
||||
android:ems="10"
|
||||
android:text="" />
|
||||
</RelativeLayout>
|
||||
<RelativeLayout
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/login_password_label"
|
||||
android:layout_width="81dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Password"
|
||||
android:layout_alignBaseline="@id/login_password_text"
|
||||
/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/login_password_text"
|
||||
android:layout_width="225dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="20dp"
|
||||
android:layout_toRightOf="@id/login_password_label"
|
||||
android:ems="10"
|
||||
android:inputType="textPassword"
|
||||
android:text="" />
|
||||
</RelativeLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<Button
|
||||
android:id="@+id/login_dialog_login_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:background="@color/colorAccent"
|
||||
android:text="@string/action_login_short"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/login_dialog_cancel_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:text="@android:string/cancel"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/login_dialog_login_button"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/login_message_label"
|
||||
android:layout_width="81dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</LinearLayout>
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 27 KiB |
@ -1,3 +1,6 @@
|
||||
<resources>
|
||||
<dimen name="fab_margin">16dp</dimen>
|
||||
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||
</resources>
|
||||
|
@ -1,4 +1,29 @@
|
||||
<resources>
|
||||
<string name="app_name">mini-cards</string>
|
||||
<string name="title_activity_deck_preview">DeckPreviewActivity</string>
|
||||
<string name="login_in_programaker">Login in PrograMaker</string>
|
||||
<string name="title_activity_login">Sign in</string>
|
||||
<string name="prompt_email">Email</string>
|
||||
<string name="prompt_password">Password</string>
|
||||
<string name="action_sign_in">Sign in or register</string>
|
||||
<string name="action_sign_in_short">Sign in</string>
|
||||
<string name="welcome">"Welcome !"</string>
|
||||
<string name="invalid_username">Not a valid username</string>
|
||||
<string name="invalid_password">Password must be >5 characters</string>
|
||||
<string name="login_failed">"Login failed"</string>
|
||||
<string name="login_username_label">Username</string>
|
||||
<string name="action_login_short">Login</string>
|
||||
<string name="loading">Loading...</string>
|
||||
<string name="invalid_user_pass">Invalid username/password</string>
|
||||
<string name="placeholder_text">Placeholder text</string>
|
||||
<string name="back_to_deck">Back to card Deck</string>
|
||||
<string name="go_back">Go back</string>
|
||||
<string name="bridge_service_not_started">Programaker bridge not started</string>
|
||||
<string name="bridge_service_starting">Programaker bridge starting</string>
|
||||
<string name="bridge_service_online">Programaker bridge online</string>
|
||||
<string name="bridge_service_failed_restarting">Programaker bridge failed, restarting...</string>
|
||||
<string name="bridge_service_failed_stopping">Programaker bridge stopped</string>
|
||||
<string name="stop_bridge">Stop bridge</string>
|
||||
<string name="start_bridge">Start bridge</string>
|
||||
<string name="leds_notification">LED activated</string>
|
||||
</resources>
|
||||
|
BIN
asset-src/ic_launcher.xcf
Normal file
BIN
asset-src/ic_launcher_round.xcf
Normal file
72
asset-src/ic_vector_icon.svg
Normal file
@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="svg2693"
|
||||
width="322"
|
||||
height="328.6"
|
||||
viewBox="0 0 322 328.6"
|
||||
sodipodi:docname="ic_vector_icon.svg"
|
||||
inkscape:version="1.0 (4035a4fb49, 2020-05-01)">
|
||||
<metadata
|
||||
id="metadata2699">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs2697" />
|
||||
<sodipodi:namedview
|
||||
inkscape:document-rotation="0"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1364"
|
||||
inkscape:window-height="746"
|
||||
id="namedview2695"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.7865"
|
||||
inkscape:cx="222.3"
|
||||
inkscape:cy="190.6"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="20"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="g2701"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0" />
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
inkscape:label="Image"
|
||||
id="g2701"
|
||||
transform="translate(-14.41,-8.837)">
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:30.2362;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 105.2,161.6 301.6,295.4 187.7,34.33 c 0,0 -12.3,-23.91 -26,0.37 -13.6,24.3 -56.5,126.9 -56.5,126.9 z"
|
||||
id="path3270"
|
||||
sodipodi:nodetypes="cccsc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:30.2362;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 46.66,299.7 146.5,239.6 89.21,200 Z"
|
||||
id="path3312"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.3 KiB |