package be.digitalia.fosdem.fragments; import android.annotation.SuppressLint; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.graphics.drawable.Animatable; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.provider.CalendarContract; import android.support.customtabs.CustomTabsIntent; import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager; import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.app.ShareCompat; import android.support.v4.content.ContextCompat; import android.support.v4.content.Loader; import android.text.Spannable; import android.text.SpannableString; import android.text.SpannableStringBuilder; import android.text.TextPaint; import android.text.TextUtils; import android.text.method.LinkMovementMethod; import android.text.method.MovementMethod; import android.text.style.ClickableSpan; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; import java.text.DateFormat; import java.util.Date; import java.util.List; import be.digitalia.fosdem.R; import be.digitalia.fosdem.activities.PersonInfoActivity; import be.digitalia.fosdem.db.DatabaseManager; import be.digitalia.fosdem.loaders.BookmarkStatusLoader; import be.digitalia.fosdem.loaders.LocalCacheLoader; import be.digitalia.fosdem.model.Building; import be.digitalia.fosdem.model.Event; import be.digitalia.fosdem.model.Link; import be.digitalia.fosdem.model.Person; import be.digitalia.fosdem.utils.DateUtils; import be.digitalia.fosdem.utils.StringUtils; public class EventDetailsFragment extends Fragment { /** * Interface implemented by container activities */ public interface FloatingActionButtonProvider { // May return null ImageView getActionButton(); } static class EventDetails { List<Person> persons; List<Link> links; } static class ViewHolder { LayoutInflater inflater; TextView personsTextView; View linksHeader; ViewGroup linksContainer; } private static final int BOOKMARK_STATUS_LOADER_ID = 1; private static final int EVENT_DETAILS_LOADER_ID = 2; private static final String ARG_EVENT = "event"; Event event; int personsCount = 1; Boolean isBookmarked; ViewHolder holder; private MenuItem bookmarkMenuItem; private ImageView actionButton; public static EventDetailsFragment newInstance(Event event) { EventDetailsFragment f = new EventDetailsFragment(); Bundle args = new Bundle(); args.putParcelable(ARG_EVENT, event); f.setArguments(args); return f; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); event = getArguments().getParcelable(ARG_EVENT); } public Event getEvent() { return event; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_event_details, container, false); holder = new ViewHolder(); holder.inflater = inflater; ((TextView) view.findViewById(R.id.title)).setText(event.getTitle()); TextView textView = (TextView) view.findViewById(R.id.subtitle); String text = event.getSubTitle(); if (TextUtils.isEmpty(text)) { textView.setVisibility(View.GONE); } else { textView.setText(text); } MovementMethod linkMovementMethod = LinkMovementMethod.getInstance(); // Set the persons summary text first; replace it with the clickable text when the loader completes holder.personsTextView = (TextView) view.findViewById(R.id.persons); String personsSummary = event.getPersonsSummary(); if (TextUtils.isEmpty(personsSummary)) { holder.personsTextView.setVisibility(View.GONE); } else { holder.personsTextView.setText(personsSummary); holder.personsTextView.setMovementMethod(linkMovementMethod); holder.personsTextView.setVisibility(View.VISIBLE); } textView = ((TextView) view.findViewById(R.id.track)); text = event.getTrack().getName(); textView.setText(text); textView.setContentDescription(getString(R.string.track_content_description, text)); textView = ((TextView) view.findViewById(R.id.time)); Date startTime = event.getStartTime(); Date endTime = event.getEndTime(); DateFormat timeDateFormat = DateUtils.getTimeDateFormat(getActivity()); text = String.format("%1$s, %2$s ― %3$s", event.getDay().toString(), (startTime != null) ? timeDateFormat.format(startTime) : "?", (endTime != null) ? timeDateFormat.format(endTime) : "?"); textView.setText(text); textView.setContentDescription(getString(R.string.time_content_description, text)); textView = (TextView) view.findViewById(R.id.room); final String roomName = event.getRoomName(); Spannable roomText = new SpannableString(String.format("%1$s (Building %2$s)", roomName, Building.fromRoomName(roomName))); final int roomImageResId = getResources().getIdentifier(StringUtils.roomNameToResourceName(roomName), "drawable", getActivity().getPackageName()); // If the room image exists, make the room text clickable to display it if (roomImageResId != 0) { roomText.setSpan(new ClickableSpan() { @Override public void onClick(View view) { RoomImageDialogFragment.newInstance(roomName, roomImageResId).show(getFragmentManager()); } @Override public void updateDrawState(TextPaint ds) { super.updateDrawState(ds); ds.setUnderlineText(false); } }, 0, roomText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); textView.setMovementMethod(linkMovementMethod); } textView.setText(roomText); textView.setContentDescription(getString(R.string.room_content_description, roomText)); textView = (TextView) view.findViewById(R.id.abstract_text); text = event.getAbstractText(); if (TextUtils.isEmpty(text)) { textView.setVisibility(View.GONE); } else { textView.setText(StringUtils.parseHtml(text, getResources())); textView.setMovementMethod(linkMovementMethod); } textView = (TextView) view.findViewById(R.id.description); text = event.getDescription(); if (TextUtils.isEmpty(text)) { textView.setVisibility(View.GONE); } else { textView.setText(StringUtils.parseHtml(text, getResources())); textView.setMovementMethod(linkMovementMethod); } holder.linksHeader = view.findViewById(R.id.links_header); holder.linksContainer = (ViewGroup) view.findViewById(R.id.links_container); return view; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); Activity activity = getActivity(); if (activity instanceof FloatingActionButtonProvider) { actionButton = ((FloatingActionButtonProvider) activity).getActionButton(); if (actionButton != null) { actionButton.setOnClickListener(actionButtonClickListener); } } // Ensure the actionButton is initialized before creating the options menu setHasOptionsMenu(true); LoaderManager loaderManager = getLoaderManager(); loaderManager.initLoader(BOOKMARK_STATUS_LOADER_ID, null, bookmarkStatusLoaderCallbacks); loaderManager.initLoader(EVENT_DETAILS_LOADER_ID, null, eventDetailsLoaderCallbacks); } private final View.OnClickListener actionButtonClickListener = new View.OnClickListener() { @Override public void onClick(View view) { if (isBookmarked != null) { new UpdateBookmarkAsyncTask(event).execute(isBookmarked); } } }; @Override public void onDestroyView() { super.onDestroyView(); holder = null; if (actionButton != null) { // Clear the reference to this fragment actionButton.setOnClickListener(null); actionButton = null; } } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.event, menu); menu.findItem(R.id.share).setIntent(getShareChooserIntent()); bookmarkMenuItem = menu.findItem(R.id.bookmark); if (actionButton != null) { bookmarkMenuItem.setEnabled(false).setVisible(false); } updateBookmarkMenuItem(false); } private Intent getShareChooserIntent() { return ShareCompat.IntentBuilder.from(getActivity()) .setSubject(String.format("%1$s (FOSDEM)", event.getTitle())) .setType("text/plain") .setText(String.format("%1$s %2$s #FOSDEM", event.getTitle(), event.getUrl())) .setChooserTitle(R.string.share) .createChooserIntent(); } void updateBookmarkMenuItem(boolean animate) { if (actionButton != null) { // Action Button is used as bookmark button if (isBookmarked == null) { actionButton.setEnabled(false); } else { // Only animate if the button was showing a previous value animate = animate && (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) && actionButton.isEnabled(); actionButton.setEnabled(true); if (isBookmarked) { actionButton.setContentDescription(getString(R.string.remove_bookmark)); actionButton.setImageResource(animate ? R.drawable.avd_bookmark_add_24dp : R.drawable.ic_bookmark_white_24dp); } else { actionButton.setContentDescription(getString(R.string.add_bookmark)); actionButton.setImageResource(animate ? R.drawable.avd_bookmark_remove_24dp : R.drawable.ic_bookmark_outline_white_24dp); } if (animate) { ((Animatable) actionButton.getDrawable()).start(); } } } else { // Standard menu item is used as bookmark button if (bookmarkMenuItem != null) { if (isBookmarked == null) { bookmarkMenuItem.setEnabled(false); } else { // Only animate if the menu item was showing a previous value animate = animate && (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) && bookmarkMenuItem.isEnabled(); bookmarkMenuItem.setEnabled(true); if (isBookmarked) { bookmarkMenuItem.setTitle(R.string.remove_bookmark); bookmarkMenuItem.setIcon(animate ? R.drawable.avd_bookmark_add_24dp : R.drawable.ic_bookmark_white_24dp); } else { bookmarkMenuItem.setTitle(R.string.add_bookmark); bookmarkMenuItem.setIcon(animate ? R.drawable.avd_bookmark_remove_24dp : R.drawable.ic_bookmark_outline_white_24dp); } if (animate) { ((Animatable) bookmarkMenuItem.getIcon()).stop(); ((Animatable) bookmarkMenuItem.getIcon()).start(); } } } } } @Override public void onDestroyOptionsMenu() { super.onDestroyOptionsMenu(); bookmarkMenuItem = null; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.bookmark: if (isBookmarked != null) { new UpdateBookmarkAsyncTask(event).execute(isBookmarked); } return true; case R.id.add_to_agenda: addToAgenda(); return true; } return false; } private static class UpdateBookmarkAsyncTask extends AsyncTask<Boolean, Void, Void> { private final Event event; public UpdateBookmarkAsyncTask(Event event) { this.event = event; } @Override protected Void doInBackground(Boolean... remove) { if (remove[0]) { DatabaseManager.getInstance().removeBookmark(event); } else { DatabaseManager.getInstance().addBookmark(event); } return null; } } @SuppressLint("InlinedApi") private void addToAgenda() { Intent intent = new Intent(Intent.ACTION_EDIT); intent.setType("vnd.android.cursor.item/event"); intent.putExtra(CalendarContract.Events.TITLE, event.getTitle()); intent.putExtra(CalendarContract.Events.EVENT_LOCATION, "ULB - " + event.getRoomName()); String description = event.getAbstractText(); if (TextUtils.isEmpty(description)) { description = event.getDescription(); } description = StringUtils.stripHtml(description); // Add speaker info if available if (personsCount > 0) { description = String.format("%1$s: %2$s\n\n%3$s", getResources().getQuantityString(R.plurals.speakers, personsCount), event.getPersonsSummary(), description); } intent.putExtra(CalendarContract.Events.DESCRIPTION, description); Date time = event.getStartTime(); if (time != null) { intent.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, time.getTime()); } time = event.getEndTime(); if (time != null) { intent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, time.getTime()); } try { startActivity(intent); } catch (ActivityNotFoundException e) { Toast.makeText(getActivity(), R.string.calendar_not_found, Toast.LENGTH_LONG).show(); } } private final LoaderCallbacks<Boolean> bookmarkStatusLoaderCallbacks = new LoaderCallbacks<Boolean>() { @Override public Loader<Boolean> onCreateLoader(int id, Bundle args) { return new BookmarkStatusLoader(getActivity(), event); } @Override public void onLoadFinished(Loader<Boolean> loader, Boolean data) { if (isBookmarked != data) { isBookmarked = data; updateBookmarkMenuItem(true); } } @Override public void onLoaderReset(Loader<Boolean> loader) { } }; private static class EventDetailsLoader extends LocalCacheLoader<EventDetails> { private final Event event; public EventDetailsLoader(Context context, Event event) { super(context); this.event = event; } @Override public EventDetails loadInBackground() { EventDetails result = new EventDetails(); DatabaseManager dbm = DatabaseManager.getInstance(); result.persons = dbm.getPersons(event); result.links = dbm.getLinks(event); return result; } } private final LoaderCallbacks<EventDetails> eventDetailsLoaderCallbacks = new LoaderCallbacks<EventDetails>() { @Override public Loader<EventDetails> onCreateLoader(int id, Bundle args) { return new EventDetailsLoader(getActivity(), event); } @Override public void onLoadFinished(Loader<EventDetails> loader, EventDetails data) { // 1. Persons if (data.persons != null) { personsCount = data.persons.size(); if (personsCount > 0) { // Build a list of clickable persons SpannableStringBuilder sb = new SpannableStringBuilder(); int length = 0; for (Person person : data.persons) { if (length != 0) { sb.append(", "); } String name = person.getName(); sb.append(name); length = sb.length(); sb.setSpan(new PersonClickableSpan(person), length - name.length(), length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } holder.personsTextView.setText(sb); holder.personsTextView.setVisibility(View.VISIBLE); } } // 2. Links holder.linksContainer.removeAllViews(); if ((data.links != null) && (data.links.size() > 0)) { holder.linksHeader.setVisibility(View.VISIBLE); holder.linksContainer.setVisibility(View.VISIBLE); for (Link link : data.links) { View view = holder.inflater.inflate(R.layout.item_link, holder.linksContainer, false); TextView tv = (TextView) view.findViewById(R.id.description); tv.setText(link.getDescription()); view.setOnClickListener(new LinkClickListener(link)); holder.linksContainer.addView(view); } } else { holder.linksHeader.setVisibility(View.GONE); holder.linksContainer.setVisibility(View.GONE); } } @Override public void onLoaderReset(Loader<EventDetails> loader) { } }; private static class PersonClickableSpan extends ClickableSpan { private final Person person; public PersonClickableSpan(Person person) { this.person = person; } @Override public void onClick(View v) { Context context = v.getContext(); Intent intent = new Intent(context, PersonInfoActivity.class).putExtra(PersonInfoActivity.EXTRA_PERSON, person); context.startActivity(intent); } @Override public void updateDrawState(TextPaint ds) { super.updateDrawState(ds); ds.setUnderlineText(false); } } private class LinkClickListener implements View.OnClickListener { private final Link link; public LinkClickListener(Link link) { this.link = link; } @Override public void onClick(View v) { String url = link.getUrl(); if (url != null) { try { Activity context = getActivity(); new CustomTabsIntent.Builder() .setToolbarColor(ContextCompat.getColor(context, event.getTrack().getType().getColorResId())) .setShowTitle(true) .setStartAnimations(context, R.anim.slide_in_right, R.anim.slide_out_left) .setExitAnimations(context, R.anim.slide_in_left, R.anim.slide_out_right) .build() .launchUrl(context, Uri.parse(url)); } catch (ActivityNotFoundException ignore) { } } } } }