package com.codigoparallevar.minicards; import android.app.Dialog; import android.content.DialogInterface; import android.os.AsyncTask; import android.os.Bundle; 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.ProgramakerAndroidBridge; 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.DoAsync; 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; private ProgramakerAndroidBridge bridge = null; 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>, Void, Tuple2>>{ @Override protected Tuple2> doInBackground(Tuple3>... 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> result) { result.item2.apply(result.item1); } } @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 = findViewById(R.id.toolbar); setSupportActionBar(toolbar); FloatingActionButton fab = findViewById(R.id.create_new_card_fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { createOpenCardDialog(); } }); 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().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, new Tuple3<>( () -> api.check(), result -> { if (!result) { loginButton.setVisibility(View.VISIBLE); DeckPreviewActivity.this.Config.removeBridgeId(); } else { String bridgeId = DeckPreviewActivity.this.Config.getBridgeId(); if (bridgeId == null) { new GetAsync().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, new Tuple3<>( () -> api.createBridge(ProgramakerAndroidBridge.GetBridgeName(this)), newBridgeId -> { DeckPreviewActivity.this.Config.setBridgeId(newBridgeId); this.bridge = ProgramakerAndroidBridge.configure(this, api.getUserId(), newBridgeId); new DoAsync().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, new Tuple2<>( () -> this.bridge.start(), ex -> Log.e(LogTag, "Error on bridge: " + ex, ex) )); }, ex -> Log.e(LogTag, "Error creating bridge: " + ex, ex) )); } else { this.bridge = ProgramakerAndroidBridge.configure(this, api.getUserId(), bridgeId); new DoAsync().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, new Tuple2<>( () -> this.bridge.start(), ex -> Log.e(LogTag, "Error on bridge: " + ex, ex) )); } } }, ex -> Log.e(LogTag, "Error checking API:" + ex, ex) )); } @Override protected void onResume() { super.onResume(); reload(); } public void reload() { cardArrayAdapter = new CardPreviewArrayAdapter(this, listAvailableCards()); listView.setAdapter(cardArrayAdapter); } private void createOpenCardDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(this); final View openCardOptions = (LayoutInflater.from(this) .inflate(R.layout.create_new_card_view, null)); final EditText cardNameEditText = openCardOptions.findViewById(R.id.card_name_edit_text); builder.setTitle("Create a new card") .setView(openCardOptions) .setNegativeButton("Cancel", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } }) .setPositiveButton("OK", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { CardFile cardfile = new CardFile(CardFile.getDefaultCardStorage(DeckPreviewActivity.this)); cardfile.setName(cardNameEditText.getText().toString()); cardfile.setBackgroundColor(CardFile.DEFAULT_BACKGROUND_COLOR); try { cardfile.save(DeckPreviewActivity.this); } catch (IOException e) { Log.e("Minicards DeckPreview", "Error creating card "+ e, e); Toast.makeText(DeckPreviewActivity.this, "Error creating card " + e, Toast.LENGTH_LONG).show(); return; } CardActivity.openCard(DeckPreviewActivity.this, cardfile, CardActivity.DEVELOPER_VISUALIZATION_MODE); } }); Dialog dialog = builder.create(); dialog.show(); } private PreviewCard[] listAvailableCards() { String cardsPath = CardFile.getDefaultCardStorage(this); File cardsDir = new File(cardsPath); if (!cardsDir.exists()) { cardsDir.mkdir(); } File[] cardFiles = cardsDir.listFiles(); if (cardFiles.length == 0){ try { CardFile.createDefaultCards(cardsDir); } catch (IOException e) { Log.e("Minicards Deck preview", "IOException when creating default cards", e); } cardFiles = cardsDir.listFiles(); } List cards = new Vector<>(cardFiles.length); for (File cardFile : cardFiles) { try { CardFile card = CardFile.load(cardFile.getAbsolutePath(), new StubPartGrid()); cards.add(new PreviewCard(card.getName(), card.getBackgroundColor(), card.getPath())); } catch (ErrorLoadingCardException e) { Log.e("Minicards Deck preview", "Error loading card, [reason=" + e.getReason() + "; path=" + cardFile.getAbsolutePath(), e); } catch (IOException e) { Log.e("Minicards Deck preview", "IOException loading card, [msg=" + e.getMessage() + "; path=" + cardFile.getAbsolutePath(), e); } } return cards.toArray(new PreviewCard[cards.size()]); } }