/* * 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; import android.app.ActionBar; import android.app.Activity; import android.app.ProgressDialog; import android.content.Intent; import android.graphics.Color; import android.os.AsyncTask; import android.os.Bundle; import android.os.Parcelable; import android.support.annotation.NonNull; import android.util.Log; import android.util.TypedValue; import android.view.Gravity; import android.view.View; import android.widget.Button; import android.widget.ImageView; import android.widget.ListView; import android.widget.RelativeLayout; import android.widget.ScrollView; import android.widget.TextView; import com.google.common.base.Preconditions; import com.squareup.picasso.Picasso; import org.catrobat.catroid.R; import org.catrobat.catroid.common.Constants; import org.catrobat.catroid.common.ScratchProgramData; import org.catrobat.catroid.common.ScratchVisibilityState; import org.catrobat.catroid.scratchconverter.Client; import org.catrobat.catroid.scratchconverter.ConversionManager; import org.catrobat.catroid.scratchconverter.protocol.Job; import org.catrobat.catroid.transfers.FetchScratchProgramDetailsTask; import org.catrobat.catroid.ui.adapter.ScratchRemixedProgramAdapter; import org.catrobat.catroid.ui.adapter.ScratchRemixedProgramAdapter.ScratchRemixedProgramEditListener; import org.catrobat.catroid.ui.scratchconverter.JobViewListener; import org.catrobat.catroid.utils.ToastUtil; import org.catrobat.catroid.utils.Utils; import org.catrobat.catroid.web.ScratchDataFetcher; import java.util.ArrayList; import java.util.List; import java.util.Locale; import uk.co.deanwild.flowtextview.FlowTextView; import static android.view.View.GONE; import static android.view.View.VISIBLE; public class ScratchProgramDetailsActivity extends BaseActivity implements FetchScratchProgramDetailsTask.ScratchProgramListTaskDelegate, ScratchRemixedProgramEditListener, JobViewListener, Client.DownloadCallback { private static final String TAG = ScratchProgramDetailsActivity.class.getSimpleName(); private static ScratchDataFetcher dataFetcher = null; private static ConversionManager conversionManager = null; private ScratchProgramData programData; private TextView titleTextView; private TextView ownerTextView; private ImageView imageView; private TextView visibilityWarningTextView; private FlowTextView instructionsFlowTextView; private TextView notesAndCreditsLabelView; private TextView notesAndCreditsTextView; private TextView favoritesTextView; private TextView lovesTextView; private TextView viewsTextView; private TextView tagsTextView; private TextView sharedTextView; private TextView modifiedTextView; private Button convertButton; private ListView remixedProjectsListView; private ProgressDialog progressDialog; private ScratchRemixedProgramAdapter scratchRemixedProgramAdapter; private ScrollView mainScrollView; private RelativeLayout detailsLayout; private TextView remixesLabelView; private FetchScratchProgramDetailsTask fetchRemixesTask = new FetchScratchProgramDetailsTask(); private View separationLineBottom; public static void setDataFetcher(final ScratchDataFetcher fetcher) { dataFetcher = fetcher; } public static void setConversionManager(final ConversionManager manager) { conversionManager = manager; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_scratch_project_details); setUpActionBar(); setReturnByPressingBackButton(true); programData = getIntent().getParcelableExtra(Constants.INTENT_SCRATCH_PROGRAM_DATA); Preconditions.checkState(programData != null); titleTextView = (TextView) findViewById(R.id.scratch_project_title); ownerTextView = (TextView) findViewById(R.id.scratch_project_owner); mainScrollView = (ScrollView) findViewById(R.id.scratch_project_scroll_view); imageView = (ImageView) findViewById(R.id.scratch_project_image_view); visibilityWarningTextView = (TextView) findViewById(R.id.scratch_project_visibility_warning); instructionsFlowTextView = (FlowTextView) findViewById(R.id.scratch_project_instructions_flow_text); notesAndCreditsLabelView = (TextView) findViewById(R.id.scratch_project_notes_and_credits_label); notesAndCreditsTextView = (TextView) findViewById(R.id.scratch_project_notes_and_credits_text); favoritesTextView = (TextView) findViewById(R.id.scratch_project_favorites_text); lovesTextView = (TextView) findViewById(R.id.scratch_project_loves_text); viewsTextView = (TextView) findViewById(R.id.scratch_project_views_text); tagsTextView = (TextView) findViewById(R.id.scratch_project_tags_text); sharedTextView = (TextView) findViewById(R.id.scratch_project_shared_text); modifiedTextView = (TextView) findViewById(R.id.scratch_project_modified_text); remixedProjectsListView = (ListView) findViewById(R.id.scratch_project_remixes_list_view); convertButton = (Button) findViewById(R.id.scratch_project_convert_button); detailsLayout = (RelativeLayout) findViewById(R.id.scratch_project_details_layout); remixesLabelView = (TextView) findViewById(R.id.scratch_project_remixes_label); separationLineBottom = findViewById(R.id.separation_line_bottom); if (conversionManager.isJobInProgress(programData.getId())) { onJobInProgress(); } else if (conversionManager.isJobDownloading(programData.getId())) { onJobDownloading(); } else { onJobNotInProgress(); } conversionManager.addJobViewListener(programData.getId(), this); conversionManager.addGlobalDownloadCallback(this); final Activity activity = this; convertButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { final int numberOfJobsInProgress = conversionManager.getNumberOfJobsInProgress(); if (numberOfJobsInProgress >= 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; } onJobInProgress(); Intent intent = new Intent(); intent.putExtra(Constants.INTENT_SCRATCH_PROGRAM_DATA, (Parcelable) programData); setResult(RESULT_OK, intent); finish(); } }); loadAdditionalData(programData); } @Override protected void onStart() { super.onStart(); conversionManager.setCurrentActivity(this); } @Override protected void onDestroy() { super.onDestroy(); Log.d(TAG, "Destroyed " + TAG); conversionManager.removeJobViewListener(programData.getId(), this); conversionManager.removeGlobalDownloadCallback(this); fetchRemixesTask.cancel(true); progressDialog.dismiss(); } private void setUpActionBar() { final ActionBar actionBar = getActionBar(); actionBar.setTitle(R.string.title_activity_scratch_converter); actionBar.setHomeButtonEnabled(true); } @Override public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) { if (requestCode == Constants.INTENT_REQUEST_CODE_CONVERT && resultCode == RESULT_OK) { setResult(RESULT_OK, intent); finish(); } super.onActivityResult(requestCode, resultCode, intent); } private void loadAdditionalData(ScratchProgramData scratchProgramData) { Preconditions.checkArgument(scratchProgramData != null); Log.i(TAG, scratchProgramData.getTitle()); instructionsFlowTextView.setText("-"); notesAndCreditsLabelView.setVisibility(GONE); notesAndCreditsTextView.setVisibility(GONE); remixesLabelView.setVisibility(GONE); remixedProjectsListView.setVisibility(GONE); detailsLayout.setVisibility(GONE); tagsTextView.setVisibility(GONE); visibilityWarningTextView.setVisibility(GONE); convertButton.setVisibility(GONE); separationLineBottom.setVisibility(GONE); if (scratchRemixedProgramAdapter != null) { scratchRemixedProgramAdapter.clear(); } titleTextView.setText(scratchProgramData.getTitle()); if (scratchProgramData.getImage() != null && scratchProgramData.getImage().getUrl() != null) { final int height = getResources().getDimensionPixelSize(R.dimen.scratch_project_image_height); final String originalImageURL = scratchProgramData.getImage().getUrl().toString(); // load image but only thumnail! // in order to download only thumbnail version of the original image // we have to reduce the image size in the URL final String thumbnailImageURL = Utils.changeSizeOfScratchImageURL(originalImageURL, height); Picasso.with(this).load(thumbnailImageURL).into(imageView); } fetchRemixesTask.setContext(this).setDelegate(this).setFetcher(dataFetcher); fetchRemixesTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, scratchProgramData.getId()); } private void initRemixAdapter(List<ScratchProgramData> scratchRemixedProjectsData) { if (scratchRemixedProjectsData == null) { scratchRemixedProjectsData = new ArrayList<>(); } scratchRemixedProgramAdapter = new ScratchRemixedProgramAdapter(this, R.layout.fragment_scratch_project_list_item, R.id.scratch_projects_list_item_title, scratchRemixedProjectsData); remixedProjectsListView.setAdapter(scratchRemixedProgramAdapter); scratchRemixedProgramAdapter.setScratchRemixedProgramEditListener(this); Utils.setListViewHeightBasedOnItems(remixedProjectsListView); } public void onProjectEdit(int position) { Log.i(TAG, "Clicked on remix at position: " + position); ScratchProgramData remixData = scratchRemixedProgramAdapter.getItem(position); Log.i(TAG, "Project ID of clicked item is: " + remixData.getId()); Intent intent = new Intent(this, ScratchProgramDetailsActivity.class); intent.putExtra(Constants.INTENT_SCRATCH_PROGRAM_DATA, (Parcelable) remixData); startActivityForResult(intent, Constants.INTENT_REQUEST_CODE_CONVERT); } private void onJobNotInProgress() { convertButton.setText(R.string.convert); convertButton.setEnabled(true); } private void onJobInProgress() { convertButton.setEnabled(false); convertButton.setText(R.string.converting); } private void onJobDownloading() { convertButton.setEnabled(false); convertButton.setText(R.string.status_downloading); } //---------------------------------------------------------------------------------------------- // Scratch Project Details Task Delegate Methods //---------------------------------------------------------------------------------------------- @Override public void onPreExecute() { Log.d(TAG, "onPreExecute for FetchScratchProgramRemixesTask called"); final ScratchProgramDetailsActivity activity = this; progressDialog = new ProgressDialog(activity); progressDialog.setCancelable(false); progressDialog.getWindow().setGravity(Gravity.CENTER); progressDialog.setMessage(activity.getResources().getString(R.string.loading)); progressDialog.show(); } @Override public void onPostExecute(final ScratchProgramData programData) { Log.d(TAG, "onPostExecute for FetchScratchProgramRemixesTask called"); Preconditions.checkNotNull(progressDialog, "No progress dialog set/initialized!"); progressDialog.dismiss(); if (programData == null) { ToastUtil.showError(this, R.string.error_scratch_project_data_not_available); return; } this.programData = programData; updateViews(); // workaround to avoid scrolling down to list view after all list items have been initialized mainScrollView.postDelayed(new Runnable() { public void run() { mainScrollView.fullScroll(ScrollView.FOCUS_UP); } }, 300); } private void updateViews() { titleTextView.setText(programData.getTitle()); ownerTextView.setText(getString(R.string.by_x, programData.getOwner())); if (programData.getNotesAndCredits() != null && programData.getNotesAndCredits().length() > 0) { final String notesAndCredits = programData.getNotesAndCredits().replace("\n\n", "\n"); notesAndCreditsTextView.setText(notesAndCredits); notesAndCreditsLabelView.setVisibility(VISIBLE); notesAndCreditsTextView.setVisibility(VISIBLE); } else { notesAndCreditsLabelView.setVisibility(GONE); notesAndCreditsTextView.setVisibility(GONE); } if (programData.getInstructions() != null) { String instructionsText = programData.getInstructions().replace("\n\n", "\n"); instructionsText = (instructionsText.length() > 0) ? instructionsText : "--"; instructionsFlowTextView.setText(instructionsText); float textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 14, getResources().getDisplayMetrics()); instructionsFlowTextView.setTextSize(textSize); instructionsFlowTextView.setColor(Color.LTGRAY); } else { instructionsFlowTextView.setText("-"); } favoritesTextView.setText(Utils.humanFriendlyFormattedShortNumber(programData.getFavorites())); lovesTextView.setText(Utils.humanFriendlyFormattedShortNumber(programData.getLoves())); viewsTextView.setText(Utils.humanFriendlyFormattedShortNumber(programData.getViews())); if (programData.getTags() != null) { StringBuilder tagList = new StringBuilder(); int index = 0; for (String tag : programData.getTags()) { tagList.append(index++ > 0 ? ", " : "").append(tag); } if (tagList.length() > 0) { tagsTextView.setText(tagList); tagsTextView.setVisibility(VISIBLE); } } if (programData.getSharedDate() != null) { final String sharedDateString = Utils.formatDate(programData.getSharedDate(), Locale.getDefault()); sharedTextView.setText(getString(R.string.shared_at_x, sharedDateString)); } else { sharedTextView.setVisibility(GONE); } if (programData.getModifiedDate() != null) { final String modifiedDateString = Utils.formatDate(programData.getModifiedDate(), Locale.getDefault()); modifiedTextView.setText(getString(R.string.modified_at_x, modifiedDateString)); } else { modifiedTextView.setVisibility(GONE); } detailsLayout.setVisibility(VISIBLE); ScratchVisibilityState visibilityState = programData.getVisibilityState(); if (visibilityState != null && visibilityState != ScratchVisibilityState.PUBLIC) { visibilityWarningTextView.setVisibility(VISIBLE); convertButton.setVisibility(GONE); } else { visibilityWarningTextView.setVisibility(GONE); convertButton.setVisibility(VISIBLE); } if (programData.getRemixes() != null && programData.getRemixes().size() > 0) { remixesLabelView.setVisibility(VISIBLE); remixedProjectsListView.setVisibility(VISIBLE); initRemixAdapter(programData.getRemixes()); } separationLineBottom.setVisibility(VISIBLE); } //---------------------------------------------------------------------------------------------- // JobViewListener Events //---------------------------------------------------------------------------------------------- @Override public void onJobScheduled(final Job job) { if (job.getJobID() == programData.getId()) { onJobInProgress(); } } @Override public void onJobReady(final Job job) { // nothing to do } @Override public void onJobStarted(final Job job) { // nothing to do } @Override public void onJobProgress(final Job job, final short progress) { // nothing to do } @Override public void onJobOutput(final Job job, @NonNull final String[] lines) { // nothing to do } @Override public void onJobFinished(final Job job) { // nothing to do } @Override public void onJobFailed(final Job job) { if (job.getJobID() == programData.getId()) { onJobNotInProgress(); } } @Override public void onUserCanceledJob(final Job job) { if (job.getJobID() == programData.getId()) { onJobNotInProgress(); } } @Override public void onDownloadStarted(final String url) { final long jobID = Utils.extractScratchJobIDFromURL(url); if (jobID == programData.getId()) { onJobDownloading(); } } @Override public void onDownloadProgress(short progress, String url) { // nothing to do } @Override public void onDownloadFinished(final String catrobatProgramName, final String url) { final long jobID = Utils.extractScratchJobIDFromURL(url); if (jobID == programData.getId()) { onJobNotInProgress(); } } @Override public void onUserCanceledDownload(final String url) { final long jobID = Utils.extractScratchJobIDFromURL(url); if (jobID == programData.getId()) { onJobNotInProgress(); } } }