/* * This class is the main access point to the application. * * @author * * */ package ca.ualberta.cs.cmput301t03app.views; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import android.app.Activity; import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.provider.MediaStore; import android.text.Editable; import android.text.TextWatcher; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemLongClickListener; import android.widget.Button; import android.widget.CheckBox; import android.widget.EditText; import android.widget.Gallery; import android.widget.ImageButton; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.Toast; import ca.ualberta.cs.cmput301t03app.R; import ca.ualberta.cs.cmput301t03app.adapters.MainListAdapter; import ca.ualberta.cs.cmput301t03app.controllers.GeoLocationTracker; import ca.ualberta.cs.cmput301t03app.controllers.PictureController; import ca.ualberta.cs.cmput301t03app.controllers.PostController; import ca.ualberta.cs.cmput301t03app.controllers.PushController; import ca.ualberta.cs.cmput301t03app.datamanagers.ServerDataManager; import ca.ualberta.cs.cmput301t03app.models.GeoLocation; import ca.ualberta.cs.cmput301t03app.models.Question; import java.io.File; import android.database.Cursor; import android.graphics.Bitmap; import android.widget.ImageView; import android.widget.LinearLayout; /** * * This activity is what is launched at the beginning of the application. It * loads questions into a listview that the user can browse through. This * activity is also where the user posts questions. * */ public class MainActivity extends Activity { public AlertDialog alertDialog1; // for testing purposes protected boolean hasPicture = false; protected boolean hasLocation = false; private final int CAMERA_ACTIVITY_REQUEST_CODE = 12345; private final int GALLERY_ACTIVITY_REQUEST_CODE = 67890; protected Uri imageFileUri; protected GeoLocation location; protected String cityName; private PostController pc = new PostController(this); private PushController pushCtrl = new PushController(this); private PictureController pictureController = new PictureController(this); private ServerDataManager sdm = new ServerDataManager(); private ListView lv; private MainListAdapter mla; private PushAsyncTask mTask; /** * onCreate sets up the listview, sets the click listeners and runs the * setupAdapter() method */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); /* Removes the actionbar title text */ getActionBar().setDisplayShowTitleEnabled(false); ListView questionList = (ListView) findViewById(R.id.activity_main_question_list); questionList.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, final int position, long id) { toQuestionActivity(position); } }); //Long clicking a question will add it to the "To Read" list of questions questionList.setOnItemLongClickListener(new OnItemLongClickListener() { public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { { addToToRead(position); } return true; } }); setupAdapter(); //Check for connectivity if (pc.checkConnectivity() == false) { Toast.makeText( this, "You are not connected to the server. To access your locally saved data go to your User home screen.", Toast.LENGTH_LONG).show(); } else { mTask = new PushAsyncTask(); mTask.execute(); } } @Override public void onResume() { super.onResume(); mla.updateAdapter(pc.getQuestionsInstance()); } public void onStop(){ if (mTask != null && mTask.getStatus() != AsyncTask.Status.FINISHED) { mTask.cancel(true); } super.onStop(); } public void onDestroy(){ if (mTask != null && mTask.getStatus() != AsyncTask.Status.FINISHED) { mTask.cancel(true); } super.onDestroy(); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); if (id == R.id.user_home) { Intent intent = new Intent(this, UserHome.class); startActivity(intent); } if (id == R.id.search) { if (pc.checkConnectivity()) { searchQuestions(); } else { Toast.makeText(this, "No connection found. Re-connect to search.", Toast.LENGTH_LONG).show(); } } if (id == R.id.filter_date) { pc.sortQuestionsByDate(); mla.updateAdapter(pc.getQuestionsInstance()); } if (id == R.id.filter_score) { pc.sortQuestionsByUpvote(); mla.updateAdapter(pc.getQuestionsInstance()); } if (id == R.id.filter_picture) { pc.sortQuestionsByPic();; mla.updateAdapter(pc.getQuestionsInstance()); } if (id == R.id.filter_closeby) { location = new GeoLocation(); location.setLatitude(53.53); location.setLongitude(-113.5); pc.sortByLocation(location); mla.updateAdapter(pc.getQuestionsInstance()); } if (id == R.id.sync) { if (pc.checkConnectivity()) { new PushAsyncTask().execute(); pc.resetServerListIndex(); } else { Toast.makeText(this, "No connection found. Cannot sync with the server.", Toast.LENGTH_LONG).show(); } } return super.onOptionsItemSelected(item); } /*##########################################----START OF ADDING QUESTION----#############################################*/ /** * onClick method for calling the dialog box to ask a question Dialog box * requires a Question Title, Question Body, and Author * * @param view * The View it got called from. */ @SuppressWarnings("deprecation") public void addQuestionButtonFunction(View view) { // Pops up dialog box for adding a question LayoutInflater li = LayoutInflater.from(this); View promptsView = li.inflate(R.layout.dialog_activity_respond, null);// Get // XML // file // to // view final EditText questionTitle = (EditText) promptsView .findViewById(R.id.questionTitle); final EditText questionBody = (EditText) promptsView .findViewById(R.id.questionBody); final EditText userName = (EditText) promptsView .findViewById(R.id.UsernameRespondTextView); final ImageButton attachImg = (ImageButton) promptsView .findViewById(R.id.attachImg); final EditText userLocation = (EditText) promptsView .findViewById(R.id.userLocation); final ProgressBar spinner = (ProgressBar) promptsView .findViewById(R.id.progressBar1); spinner.setVisibility(View.GONE); attachImg.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub // TakePicture tp = new TakePicture(); // tp.takeAPhoto(); pictureChooserDialog(); } }); CheckBox check = (CheckBox) promptsView .findViewById(R.id.enableLocation); check.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { hasLocation = !hasLocation; if (hasLocation) { spinner.setVisibility(View.VISIBLE); location = new GeoLocation(); GeoLocationTracker locationTracker = new GeoLocationTracker( MainActivity.this, location); locationTracker.getLocation(); // location.setLatitude(53.53333); // location.setLongitude(-113.5); // Delay for 7 seconds Handler handler = new Handler(); handler.postDelayed(new Runnable() { public void run() { cityName = pc.getCity(location); Log.d("Loc", "Timer is done"); Log.d("Loc", "Lat after: " + location.getLatitude()); Log.d("Loc", "Long after: " + location.getLongitude()); if (cityName != null) { userLocation.setText(cityName); location.setCityName(cityName); } else { userLocation.setText("Location not found."); } spinner.setVisibility(View.GONE); } }, 7000); } } }); final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder( this);// Create a new AlertDialog alertDialogBuilder.setView(promptsView);// Link the alertdialog to the // XML alertDialogBuilder.setPositiveButton("Ask!", new DialogInterface.OnClickListener() { @Override // Building the dialog for adding public void onClick(DialogInterface dialog, int which) { String questionTitleString = (String) questionTitle .getText().toString(); String questionBodyString = (String) questionBody .getText().toString(); String userNameString = (String) userName.getText() .toString(); String userLocationString = userLocation.getText() .toString(); Question q = new Question(questionTitleString, questionBodyString, userNameString); if (hasPicture) { Log.d("Picture","After: " + imageFileUri.getPath()); Bitmap _bitmapScaled = pictureController.ShrinkBitmap( imageFileUri.getPath(), 200, 200); ByteArrayOutputStream bytes = new ByteArrayOutputStream(); _bitmapScaled.compress(Bitmap.CompressFormat.JPEG, 64, bytes); q.setPicture(bytes.toByteArray()); } if (hasLocation) { // Set location if location typed by user is same as // location found if (userLocationString.equals(cityName)) { q.setGeoLocation(location); } // Find the coordinates of place entered by user and // set location else { q.setGeoLocation(pc .turnFromCity(userLocationString)); // Testing GeoLocation testlocation = pc .turnFromCity(userLocationString); Log.d("Location", Double.toString(testlocation .getLatitude())); Log.d("Location", Double.toString(testlocation .getLongitude())); } } if (pc.checkConnectivity()) { Thread thread = new AddThread(q); thread.start(); // thread.interrupt(); } else { pc.addPushQuestion(q); } pc.addUserPost(q); pc.getQuestionsInstance().add(q); pc.sortQuestionsByDate(); mla.updateAdapter(pc.getQuestionsInstance()); Log.d("Debug", "Finishes adding"); hasLocation = false; } }).setNegativeButton("Cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { // Do nothing hasLocation = false; dialog.cancel(); } }); final AlertDialog alertDialog = alertDialogBuilder.create(); alertDialog1 = alertDialog; alertDialog.show(); alertDialog.getButton(AlertDialog.BUTTON1).setEnabled(false); TextWatcher textwatcher = new TextWatcher() { // creating a listener to see if any changes to edit text in dialog private void handleText() { final Button button = alertDialog .getButton(AlertDialog.BUTTON_POSITIVE); if (questionTitle.getText().length() == 0) { // these checks the // edittext to // make sure not // empty edit // text button.setEnabled(false); } else if (questionBody.getText().length() == 0) { button.setEnabled(false); } else if (userName.getText().length() == 0) { button.setEnabled(false); } else { button.setEnabled(true); } } @Override public void afterTextChanged(Editable s) { handleText(); } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { // do nothing } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { // do nothing } }; questionTitle.addTextChangedListener(textwatcher); // adding listeners // to the edittexts questionBody.addTextChangedListener(textwatcher); userName.addTextChangedListener(textwatcher); Toast.makeText(this, "Please write your question", Toast.LENGTH_SHORT) .show(); } /*#######################################----END OF ADDING QUESTION----#############################################*/ /*#####################################----START OF USER INITIATED METHODS----#####################################*/ /** * This method runs when the user chooses to answer a question. It creates * an intent and adds the question ID to the intent then it starts the * ViewQuestion activity. * * @param position * The position of the question clicked */ public void toQuestionActivity(int position) { Intent i = new Intent(this, ViewQuestion.class); i.putExtra("question_id", pc.getQuestionsInstance().get(position) .getId()); pc.addReadQuestion(pc.getQuestionsInstance().get(position)); startActivity(i); } /** * This function is called when the user long clicks on a question in the * list view. It allows the user to save the question in the ToRead list so * that they can read the question later. * * @param position * A final int from the question position the long click was * called from with bitmapfactory * */ public void addToToRead(final int position) { AlertDialog.Builder editDialog = new AlertDialog.Builder(this); editDialog.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dialog.cancel(); } }).setPositiveButton("Add to To-Read", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { pc.addToRead(pc.getQuestionsInstance().get(position)); Toast.makeText(MainActivity.this, "Added to To-Read List", Toast.LENGTH_SHORT) .show(); } }); AlertDialog alertDialog = editDialog.create(); alertDialog.show(); } /** * This is the onClic * * @param view */ public void searchQuestions() { LayoutInflater li = LayoutInflater.from(this); View searchView = li.inflate(R.layout.search_dialog, null); final EditText searchField = (EditText) searchView .findViewById(R.id.searchField); final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder( this); alertDialogBuilder.setView(searchView); alertDialogBuilder.setPositiveButton("Search", new DialogInterface.OnClickListener() { @Override // Building the dialog for adding public void onClick(DialogInterface dialog, int which) { String searchString = ""; if (searchField.getText().toString() != "" || searchField.getText().toString() != null) { searchString = (String) searchField.getText() .toString(); } final String finalString = searchString; new SearchAsyncTask().execute(finalString); } }).setNegativeButton("Cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { // Do nothing dialog.cancel(); } }); final AlertDialog alertDialog = alertDialogBuilder.create(); alertDialog1 = alertDialog; alertDialog.show(); // alertDialog.getButton(AlertDialog.BUTTON1).setEnabled(false); } /** * Method for the LOAD MORE button, which loads the next 10 questions from the server into the sub-questions list and displays them. * @param view The View it gets called from. */ public void loadMoreQuestions(View view) { pc.loadMoreServerQuestions(); mla.updateAdapter(pc.getQuestionsInstance()); Toast.makeText(this, "Loaded more questions", Toast.LENGTH_SHORT) .show(); } /*#####################################----END OF USER INITIATED METHODS----#####################################*/ /*###############################----START OF PICTURE METHODS----###################################*/ private void pictureChooserDialog() { AlertDialog.Builder myAlertDialog = new AlertDialog.Builder(this); myAlertDialog.setTitle("Pictures Option"); myAlertDialog.setMessage("Select Picture Mode"); myAlertDialog.setPositiveButton("Gallery", new DialogInterface.OnClickListener() { public void onClick(DialogInterface arg0, int arg1) { takeFromGallery(); } }); myAlertDialog.setNegativeButton("Camera", new DialogInterface.OnClickListener() { public void onClick(DialogInterface arg0, int arg1) { takeAPhoto(); } }); myAlertDialog.show(); } /** * Captures an image from the camera */ private void takeAPhoto() { /* * Main Activity is getting pretty bloated so I'm trying to move this * out into the Utils package */ String path = Environment.getExternalStorageDirectory() .getAbsolutePath() + "/MyCameraTest"; File folder = new File(path); if (!folder.exists()) { folder.mkdir(); } //Makes the time stamp as part of the filename String imagePathAndFileName = path + File.separator + String.valueOf(System.currentTimeMillis()) + ".jpg"; File imageFile = new File(imagePathAndFileName); imageFileUri = Uri.fromFile(imageFile); Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); intent.putExtra(MediaStore.EXTRA_OUTPUT, imageFileUri); //Sets the ID for when the Camera app sends it back here. startActivityForResult(intent, CAMERA_ACTIVITY_REQUEST_CODE); // matches the ID to the request code in onActivityResult } /** * Opens the gallery */ private void takeFromGallery() { Intent intent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI); intent.setType("image/*"); intent.setAction(Intent.ACTION_GET_CONTENT); startActivityForResult(Intent.createChooser(intent, "Select Picture"), GALLERY_ACTIVITY_REQUEST_CODE); } // This method is run after returning back from camera activity: protected void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { case CAMERA_ACTIVITY_REQUEST_CODE: if (resultCode == RESULT_OK) { hasPicture = true; Log.d("click", "Imag efile path: " + imageFileUri.getPath()); } else if (resultCode == RESULT_CANCELED) { } break; case GALLERY_ACTIVITY_REQUEST_CODE: if (resultCode == RESULT_OK) { hasPicture = true; Uri galleryImageUri = data.getData(); File imageFile = new File(pictureController.getRealPathFromURI(galleryImageUri)); imageFileUri = Uri.fromFile(imageFile); break; } } } /*#################################----END OF PICTURE METHODS----#####################################*/ /*#################################----PRIVATE METHODS----######################################*/ /** * Sets the adapter for the list view. */ private void setupAdapter() { lv = (ListView) findViewById(R.id.activity_main_question_list); mla = new MainListAdapter(this, R.layout.activity_main_question_entity, pc.getQuestionsInstance()); // TODO: Show questions in question bank when user first opens app if // not connected lv.setAdapter(mla); } /** * Private method that tells the GUI to update itself when another Thread is running. */ private Runnable doUpdateGUIList = new Runnable() { public void run() { pc.sortQuestionsByDate(); mla.updateAdapter(pc.getQuestionsInstance()); } }; /*##############################################----PRIVATE CLASSES----###################################################*/ /** * This Thread is used to push a new Question to the server and updates the GUI after adding * */ class AddThread extends Thread { private Question question; public AddThread(Question question) { this.question = question; Log.d("push", this.question.getSubject()); } @Override public void run() { pushCtrl.addQuestionToServer(this.question); runOnUiThread(doUpdateGUIList); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * This Thread is used to push any objects made while offline to the server. * */ class PushThread extends Thread { public PushThread() { } @Override public void run() { pushCtrl.pushNewPosts(); pushCtrl.pushAnswersAndComments(); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } private class SearchAsyncTask extends AsyncTask<String, Void, Void> { ProgressDialog progress; protected void onPreExecute() { progress = new ProgressDialog(MainActivity.this); progress.setMessage("Searching..."); progress.setProgressStyle(ProgressDialog.STYLE_SPINNER); progress.setCanceledOnTouchOutside(false); progress.show(); } protected Void doInBackground(String... arg0) { pc.executeSearch(arg0[0]); return null; } protected void onPostExecute(Void result) { progress.dismiss(); pc.sortQuestionsByDate(); mla.updateAdapter(pc.getQuestionsInstance()); } } private class PushAsyncTask extends AsyncTask<Void, Void, Void> { private ProgressDialog progress; private ArrayList<Question> initList; protected void onPreExecute() { progress = new ProgressDialog(MainActivity.this); progress.setMessage("Syncing..."); progress.setProgressStyle(ProgressDialog.STYLE_SPINNER); progress.setCanceledOnTouchOutside(false); progress.show(); } protected Void doInBackground(Void... values) { pushCtrl.pushNewPosts(); pushCtrl.pushAnswersAndComments(); initList = pc.getQuestionsFromServer(); return null; } protected void onPostExecute(Void result) { progress.dismiss(); mla.updateAdapter(initList); } } /*#############################----TESTING METHODS----###############################*/ /** * For jUnit tests * @return */ public AlertDialog getDialog() { // this is for testing purposes return alertDialog1; } /** * For jUnit tests * @return */ public MainListAdapter getAdapter() { // this is for testing purposes return mla; } }