/*
* Catroid: An on-device visual programming system for Android devices
* Copyright (C) 2010-2016 The Catrobat Team
* (<http://developer.catrobat.org/credits>)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* An additional term exception under section 7 of the GNU Affero
* General Public License, version 3, is available at
* http://developer.catrobat.org/license_additional_term
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.catrobat.catroid.ui.fragment;
import android.app.Fragment;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Parcelable;
import android.preference.PreferenceManager;
import android.text.InputType;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import android.util.Log;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.SearchView;
import android.widget.TextView;
import com.google.common.base.Preconditions;
import org.catrobat.catroid.R;
import org.catrobat.catroid.common.Constants;
import org.catrobat.catroid.common.ScratchProgramData;
import org.catrobat.catroid.common.ScratchSearchResult;
import org.catrobat.catroid.scratchconverter.ConversionManager;
import org.catrobat.catroid.transfers.SearchScratchProgramsTask;
import org.catrobat.catroid.ui.ScratchConverterActivity;
import org.catrobat.catroid.ui.ScratchProgramDetailsActivity;
import org.catrobat.catroid.ui.adapter.ScratchProgramAdapter;
import org.catrobat.catroid.utils.ExpiringLruMemoryObjectCache;
import org.catrobat.catroid.utils.ToastUtil;
import org.catrobat.catroid.web.ScratchDataFetcher;
import java.util.ArrayList;
import java.util.List;
public class SearchScratchSearchProjectsListFragment extends Fragment
implements SearchScratchProgramsTask.SearchScratchProgramsTaskDelegate,
ScratchProgramAdapter.OnScratchProgramEditListener {
private static final String TAG = SearchScratchSearchProjectsListFragment.class.getSimpleName();
private static final String BUNDLE_ARGUMENTS_SCRATCH_PROJECT_DATA = "scratch_project_data";
private static final String SHARED_PREFERENCE_NAME = "showDetailsScratchProjects";
private ScratchDataFetcher dataFetcher;
private ConversionManager conversionManager;
private ScratchConverterActivity activity;
private String convertActionModeTitle;
private static String singleItemAppendixConvertActionMode;
private static String multipleItemAppendixConvertActionMode;
private SearchView searchView;
private ImageButton audioButton;
private ListView searchResultsListView;
private List<ScratchProgramData> scratchProgramDataList;
private ScratchProgramData scratchProgramToEdit;
private ExpiringLruMemoryObjectCache<ScratchSearchResult> scratchSearchResultCache;
private ScratchProgramAdapter scratchProgramAdapter;
private ActionMode actionMode;
private SearchScratchProgramsTask currentSearchTask = null;
// dependency-injection for testing with mock object
public void setDataFetcher(final ScratchDataFetcher fetcher) {
dataFetcher = fetcher;
}
public void setConversionManager(final ConversionManager manager) {
conversionManager = manager;
}
public void setSearchResultsListViewMargin(int left, int top, int right, int bottom) {
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) searchResultsListView.getLayoutParams();
params.setMargins(left, top, right, bottom);
searchResultsListView.setLayoutParams(params);
}
private ActionMode.Callback convertModeCallBack = new ActionMode.Callback() {
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
setSelectMode(ListView.CHOICE_MODE_MULTIPLE);
convertActionModeTitle = getString(R.string.convert);
singleItemAppendixConvertActionMode = getString(R.string.program);
multipleItemAppendixConvertActionMode = getString(R.string.programs);
mode.setTitle(convertActionModeTitle);
searchView.setVisibility(View.GONE);
audioButton.setVisibility(View.GONE);
setSearchResultsListViewMargin(0, 0, 0, 0);
return true;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
return false;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
if (scratchProgramAdapter.getAmountOfCheckedPrograms() == 0) {
clearCheckedProjectsAndEnableButtons();
} else {
convertCheckedProjects();
clearCheckedProjectsAndEnableButtons();
}
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
setRetainInstance(true);
super.onCreate(savedInstanceState);
scratchSearchResultCache = ExpiringLruMemoryObjectCache.getInstance();
}
@Override
public void onDestroy() {
super.onDestroy();
if (currentSearchTask != null) {
currentSearchTask.cancel(true);
}
}
@Override
public void onPause() {
super.onPause();
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(activity.getApplicationContext());
SharedPreferences.Editor editor = settings.edit();
editor.putBoolean(SHARED_PREFERENCE_NAME, getShowDetails());
editor.commit();
}
@Override
public void onResume() {
super.onResume();
if (actionMode != null) {
actionMode.finish();
actionMode = null;
}
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(activity.getApplicationContext());
setShowDetails(settings.getBoolean(SHARED_PREFERENCE_NAME, false));
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
activity = (ScratchConverterActivity) getActivity();
final View rootView = inflater.inflate(R.layout.fragment_scratch_search_projects_list, container, false);
searchView = (SearchView) rootView.findViewById(R.id.search_view_scratch);
searchResultsListView = (ListView) rootView.findViewById(R.id.list_view_search_scratch);
searchResultsListView.setVisibility(View.INVISIBLE);
audioButton = (ImageButton) rootView.findViewById(R.id.mic_button_image_scratch);
int id = searchView.getContext().getResources().getIdentifier("android:id/search_src_text", null, null);
final TextView searchTextView = (TextView) searchView.findViewById(id);
searchTextView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus && !activity.isSlideUpPanelEmpty()) {
activity.showSlideUpPanelBar(500);
} else {
activity.hideSlideUpPanelBar();
}
}
});
searchTextView.setInputType(InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE);
audioButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
searchView.setQuery("", false);
searchView.clearFocus();
activity.displaySpeechRecognizer();
}
});
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
Log.i(TAG, "Submitting query: " + query);
searchView.clearFocus();
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
Log.i(TAG, newText);
if (newText.length() <= 2) {
searchResultsListView.setVisibility(View.INVISIBLE);
return false;
}
search(newText);
return false;
}
});
initAdapter();
return rootView;
}
public void searchAndUpdateText(final String text) {
searchView.setQuery(text, false);
}
public void search(final String text) {
ScratchSearchResult cachedResult = scratchSearchResultCache.get(text);
if (cachedResult != null) {
Log.d(TAG, "Cache hit!");
onPostExecute(cachedResult);
return;
}
// cache miss
Log.d(TAG, "Cache miss! -> Searching...");
if (currentSearchTask != null) {
currentSearchTask.cancel(true);
}
currentSearchTask = new SearchScratchProgramsTask();
currentSearchTask.setContext(activity).setDelegate(this).setFetcher(dataFetcher);
currentSearchTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, text);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
scratchProgramToEdit = (ScratchProgramData) savedInstanceState.getSerializable(BUNDLE_ARGUMENTS_SCRATCH_PROJECT_DATA);
}
initAdapter();
searchView.setFocusable(false);
fetchDefaultProjects();
}
private void fetchDefaultProjects() {
Log.d(TAG, "Fetching default scratch projects");
searchView.setQuery("", false);
if (currentSearchTask != null) {
currentSearchTask.cancel(true);
}
currentSearchTask = new SearchScratchProgramsTask();
currentSearchTask.setContext(activity);
currentSearchTask.setDelegate(this);
currentSearchTask.setFetcher(dataFetcher);
currentSearchTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
}
@Override
public void onSaveInstanceState(Bundle outState) {
outState.putSerializable(BUNDLE_ARGUMENTS_SCRATCH_PROJECT_DATA, scratchProgramToEdit);
super.onSaveInstanceState(outState);
}
public boolean getShowDetails() {
return scratchProgramAdapter.getShowDetails();
}
public void setShowDetails(boolean showDetails) {
scratchProgramAdapter.setShowDetails(showDetails);
scratchProgramAdapter.notifyDataSetChanged();
}
public int getSelectMode() {
return scratchProgramAdapter.getSelectMode();
}
public void setSelectMode(int selectMode) {
scratchProgramAdapter.setSelectMode(selectMode);
scratchProgramAdapter.notifyDataSetChanged();
}
private void initAdapter() {
if (scratchProgramDataList == null) {
scratchProgramDataList = new ArrayList<>();
}
scratchProgramAdapter = new ScratchProgramAdapter(activity,
R.layout.fragment_scratch_project_list_item,
R.id.scratch_projects_list_item_title,
scratchProgramDataList);
searchResultsListView.setAdapter(scratchProgramAdapter);
initClickListener();
}
private void initClickListener() {
scratchProgramAdapter.setOnScratchProgramEditListener(this);
}
@Override
public boolean onProgramChecked() {
if (scratchProgramAdapter.getSelectMode() == ListView.CHOICE_MODE_SINGLE || actionMode == null) {
return true;
}
int numberOfPrograms = scratchProgramAdapter.getAmountOfCheckedPrograms()
+ conversionManager.getNumberOfJobsInProgress();
if (numberOfPrograms > Constants.SCRATCH_CONVERTER_MAX_NUMBER_OF_JOBS_PER_CLIENT) {
ToastUtil.showError(activity, getResources().getQuantityString(
R.plurals.error_cannot_convert_more_than_x_programs,
Constants.SCRATCH_CONVERTER_MAX_NUMBER_OF_JOBS_PER_CLIENT,
Constants.SCRATCH_CONVERTER_MAX_NUMBER_OF_JOBS_PER_CLIENT));
return false;
}
updateActionModeTitle();
return true;
}
private void updateActionModeTitle() {
int numberOfSelectedItems = scratchProgramAdapter.getAmountOfCheckedPrograms();
if (numberOfSelectedItems == 0) {
actionMode.setTitle(convertActionModeTitle);
} else {
String appendix = multipleItemAppendixConvertActionMode;
if (numberOfSelectedItems == 1) {
appendix = singleItemAppendixConvertActionMode;
}
String numberOfItems = Integer.toString(numberOfSelectedItems);
String completeTitle = convertActionModeTitle + " " + numberOfItems + " " + appendix;
int titleLength = convertActionModeTitle.length();
Spannable completeSpannedTitle = new SpannableString(completeTitle);
completeSpannedTitle.setSpan(
new ForegroundColorSpan(getResources().getColor(R.color.actionbar_title_color)), titleLength + 1,
titleLength + (1 + numberOfItems.length()), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
actionMode.setTitle(completeSpannedTitle);
}
}
@Override
public void onProgramEdit(int position) {
Preconditions.checkState(conversionManager != null);
ScratchProgramDetailsActivity.setDataFetcher(dataFetcher);
ScratchProgramDetailsActivity.setConversionManager(conversionManager);
Intent intent = new Intent(activity, ScratchProgramDetailsActivity.class);
intent.putExtra(Constants.INTENT_SCRATCH_PROGRAM_DATA, (Parcelable) scratchProgramAdapter.getItem(position));
activity.startActivityForResult(intent, Constants.INTENT_REQUEST_CODE_CONVERT);
}
public void startConvertActionMode() {
if (actionMode == null) {
actionMode = activity.startActionMode(convertModeCallBack);
}
}
private void convertCheckedProjects() {
ArrayList<ScratchProgramData> projectsToConvert = new ArrayList<>();
for (int position : scratchProgramAdapter.getCheckedPrograms()) {
scratchProgramToEdit = (ScratchProgramData) searchResultsListView.getItemAtPosition(position);
projectsToConvert.add(scratchProgramToEdit);
Log.d(TAG, "Converting project '" + scratchProgramToEdit.getTitle() + "'");
}
initAdapter();
activity.convertProjects(projectsToConvert);
}
private void clearCheckedProjectsAndEnableButtons() {
setSelectMode(ListView.CHOICE_MODE_NONE);
scratchProgramAdapter.clearCheckedPrograms();
actionMode = null;
searchView.setVisibility(View.VISIBLE);
audioButton.setVisibility(View.VISIBLE);
int marginTop = activity.getResources().getDimensionPixelSize(R.dimen.scratch_project_search_list_view_margin_top);
int marginBottom = activity.getResources().getDimensionPixelSize(R.dimen.scratch_project_search_list_view_margin_bottom);
setSearchResultsListViewMargin(0, marginTop, 0, marginBottom);
}
//----------------------------------------------------------------------------------------------
// Scratch Search Task Delegate Methods
//----------------------------------------------------------------------------------------------
@Override
public void onPreExecute() {
Log.d(TAG, "onPreExecute for SearchScratchProgramsTask called");
}
@Override
public void onPostExecute(final ScratchSearchResult result) {
Log.d(TAG, "onPostExecute for SearchScratchProgramsTask called");
Preconditions.checkNotNull(scratchProgramAdapter, "Scratch project adapter cannot be null!");
if (result == null || result.getProgramDataList() == null) {
ToastUtil.showError(activity, R.string.search_failed);
return;
}
if (result.getQuery() != null) {
scratchSearchResultCache.put(result.getQuery(), result);
}
scratchProgramAdapter.clear();
for (ScratchProgramData projectData : result.getProgramDataList()) {
scratchProgramAdapter.add(projectData);
Log.d(TAG, projectData.getTitle());
}
scratchProgramAdapter.notifyDataSetChanged();
searchResultsListView.setVisibility(View.VISIBLE);
}
}