/*
* ServeStream: A HTTP stream browser/player for Android
* Copyright 2013 William Seemann
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sourceforge.servestream.fragment;
import android.app.AlertDialog;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.app.LoaderManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Typeface;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.CheckBox;
import android.widget.CursorAdapter;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import java.util.Calendar;
import net.sourceforge.servestream.R;
import net.sourceforge.servestream.activity.SetAlarmActivity;
import net.sourceforge.servestream.alarm.Alarm;
import net.sourceforge.servestream.alarm.Alarms;
import net.sourceforge.servestream.alarm.DigitalClock;
import net.sourceforge.servestream.alarm.ToastMaster;
/**
* AlarmClock application.
*/
public class AlarmClockFragment extends ListFragment implements
OnItemClickListener, LoaderManager.LoaderCallbacks<Cursor> {
public static final String PREFERENCES = "AlarmClock";
/** This must be false for production. If true, turns on logging,
test code, etc. */
public static final boolean DEBUG = false;
private static final int URL_LOADER = 0;
//private SharedPreferences mPrefs;
private LayoutInflater mFactory;
private ListView mAlarmsList;
private AlarmTimeAdapter mAdapter;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
setHasOptionsMenu(true);
}
@Override
public void onActivityCreated (Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setListShownNoAnimation(true);
mFactory = LayoutInflater.from(getActivity());
mAdapter = new AlarmTimeAdapter(getActivity(), null, false);
mAlarmsList = getListView();
mAlarmsList.setAdapter(mAdapter);
mAlarmsList.setVerticalScrollBarEnabled(true);
mAlarmsList.setOnItemClickListener(this);
mAlarmsList.setOnCreateContextMenuListener(this);
getLoaderManager().initLoader(URL_LOADER, null, this);
}
private void updateIndicatorAndAlarm(boolean enabled,
Alarm alarm) {
Alarms.enableAlarm(getActivity(), alarm.id, enabled);
if (enabled) {
popAlarmSetToast(getActivity(), alarm.hour, alarm.minutes,
alarm.daysOfWeek);
}
}
private class AlarmTimeAdapter extends CursorAdapter {
public AlarmTimeAdapter(Context context, Cursor cursor, boolean autoRequery) {
super(context, cursor, autoRequery);
}
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View ret = mFactory.inflate(R.layout.alarm_time_item, parent, false);
DigitalClock digitalClock =
(DigitalClock) ret.findViewById(R.id.digitalClock);
digitalClock.setLive(false);
return ret;
}
public void bindView(View view, Context context, Cursor cursor) {
final Alarm alarm = new Alarm(cursor);
View indicator = view.findViewById(R.id.indicator);
// Set the initial state of the clock "checkbox"
final CheckBox clockOnOff =
(CheckBox) indicator.findViewById(R.id.clock_onoff);
clockOnOff.setChecked(alarm.enabled);
// Clicking outside the "checkbox" should also change the state.
indicator.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
clockOnOff.toggle();
updateIndicatorAndAlarm(clockOnOff.isChecked(),
alarm);
}
});
DigitalClock digitalClock =
(DigitalClock) view.findViewById(R.id.digitalClock);
// set the alarm text
final Calendar c = Calendar.getInstance();
c.set(Calendar.HOUR_OF_DAY, alarm.hour);
c.set(Calendar.MINUTE, alarm.minutes);
digitalClock.updateTime(c);
digitalClock.setTypeface(Typeface.DEFAULT);
// Set the repeat text or leave it blank if it does not repeat.
TextView daysOfWeekView =
(TextView) digitalClock.findViewById(R.id.daysOfWeek);
final String daysOfWeekStr =
alarm.daysOfWeek.toString(getActivity(), false);
if (daysOfWeekStr != null && daysOfWeekStr.length() != 0) {
daysOfWeekView.setText(daysOfWeekStr);
daysOfWeekView.setVisibility(View.VISIBLE);
} else {
daysOfWeekView.setVisibility(View.GONE);
}
// Display the label
TextView labelView =
(TextView) view.findViewById(R.id.label);
if (alarm.label != null && alarm.label.length() != 0) {
labelView.setText(alarm.label);
labelView.setVisibility(View.VISIBLE);
} else {
labelView.setVisibility(View.GONE);
}
}
};
@Override
public boolean onContextItemSelected(final android.view.MenuItem item) {
final AdapterContextMenuInfo info =
(AdapterContextMenuInfo) item.getMenuInfo();
final int id = (int) info.id;
// Error check just in case.
if (id == -1) {
return super.onContextItemSelected(item);
}
switch (item.getItemId()) {
case R.id.delete_alarm:
// Confirm that the alarm will be deleted.
new AlertDialog.Builder(getActivity())
.setTitle(getString(R.string.delete_alarm))
.setMessage(getString(R.string.alarm_delete_confirmation_msg))
.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface d,
int w) {
Alarms.deleteAlarm(getActivity(), id);
}
})
.setNegativeButton(android.R.string.cancel, null)
.show();
return true;
case R.id.enable_alarm:
final Cursor c = (Cursor) mAlarmsList.getAdapter()
.getItem(info.position);
final Alarm alarm = new Alarm(c);
Alarms.enableAlarm(getActivity(), alarm.id, !alarm.enabled);
if (!alarm.enabled) {
popAlarmSetToast(getActivity(), alarm.hour, alarm.minutes,
alarm.daysOfWeek);
}
return true;
case R.id.edit_alarm:
Intent intent = new Intent(getActivity(), SetAlarmActivity.class);
intent.putExtra(Alarms.ALARM_ID, id);
startActivity(intent);
return true;
default:
break;
}
return super.onContextItemSelected(item);
}
private void addNewAlarm() {
startActivity(new Intent(getActivity(), SetAlarmActivity.class));
}
@Override
public void onDestroy() {
super.onDestroy();
ToastMaster.cancelToast();
}
@Override
public void onCreateContextMenu(ContextMenu menu, View view,
ContextMenuInfo menuInfo) {
// Inflate the menu from xml.
getActivity().getMenuInflater().inflate(R.menu.alarm_clock_context_menu, menu);
// Use the current item to create a custom view for the header.
final AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
final Cursor c =
(Cursor) mAlarmsList.getAdapter().getItem((int) info.position);
final Alarm alarm = new Alarm(c);
// Construct the Calendar to compute the time.
final Calendar cal = Calendar.getInstance();
cal.set(Calendar.HOUR_OF_DAY, alarm.hour);
cal.set(Calendar.MINUTE, alarm.minutes);
final String time = Alarms.formatTime(getActivity(), cal);
// Inflate the custom view and set each TextView's text.
final View v = mFactory.inflate(R.layout.context_menu_header, new LinearLayout(getActivity()), false);
TextView textView = (TextView) v.findViewById(R.id.header_time);
textView.setText(time);
textView = (TextView) v.findViewById(R.id.header_label);
textView.setText(alarm.label);
// Set the custom view on the menu.
menu.setHeaderView(v);
// Change the text based on the state of the alarm.
if (alarm.enabled) {
menu.findItem(R.id.enable_alarm).setTitle(R.string.disable_alarm);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_item_add_alarm:
addNewAlarm();
return true;
default:
break;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.alarm_clock_menu, menu);
super.onCreateOptionsMenu(menu, inflater);
}
public void onItemClick(AdapterView<?> parent, View v, int pos, long id) {
Intent intent = new Intent(getActivity(), SetAlarmActivity.class);
intent.putExtra(Alarms.ALARM_ID, (int) id);
startActivity(intent);
}
@Override
public Loader<Cursor> onCreateLoader(int loaderID, Bundle bundle) {
switch (loaderID) {
case URL_LOADER:
// Returns a new CursorLoader
return new CursorLoader(
getActivity(),
Alarm.Columns.CONTENT_URI,
Alarm.Columns.ALARM_QUERY_COLUMNS,
null,
null,
Alarm.Columns.DEFAULT_SORT_ORDER
);
default:
// An invalid id was passed in
return null;
}
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
mAdapter.changeCursor(cursor);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
mAdapter.changeCursor(null);
}
/**
* Display a toast that tells the user how long until the alarm
* goes off. This helps prevent "am/pm" mistakes.
*/
public static void popAlarmSetToast(Context context, int hour, int minute,
Alarm.DaysOfWeek daysOfWeek) {
popAlarmSetToast(context,
Alarms.calculateAlarm(hour, minute, daysOfWeek)
.getTimeInMillis());
}
static void popAlarmSetToast(Context context, long timeInMillis) {
String toastText = formatToast(context, timeInMillis);
Toast toast = Toast.makeText(context, toastText, Toast.LENGTH_LONG);
ToastMaster.setToast(toast);
toast.show();
}
/**
* format "Alarm set for 2 days 7 hours and 53 minutes from
* now"
*/
static String formatToast(Context context, long timeInMillis) {
long delta = timeInMillis - System.currentTimeMillis();
long hours = delta / (1000 * 60 * 60);
long minutes = delta / (1000 * 60) % 60;
long days = hours / 24;
hours = hours % 24;
String daySeq = (days == 0) ? "" :
(days == 1) ? context.getString(R.string.day) :
context.getString(R.string.days, Long.toString(days));
String minSeq = (minutes == 0) ? "" :
(minutes == 1) ? context.getString(R.string.minute) :
context.getString(R.string.minutes, Long.toString(minutes));
String hourSeq = (hours == 0) ? "" :
(hours == 1) ? context.getString(R.string.hour) :
context.getString(R.string.hours, Long.toString(hours));
boolean dispDays = days > 0;
boolean dispHour = hours > 0;
boolean dispMinute = minutes > 0;
int index = (dispDays ? 1 : 0) |
(dispHour ? 2 : 0) |
(dispMinute ? 4 : 0);
String[] formats = context.getResources().getStringArray(R.array.alarm_set);
return String.format(formats[index], daySeq, hourSeq, minSeq);
}
}