CrimeBaseHelper.java
package com.example.aster.criminalintent.database.CrimeDbSchema;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import com.example.aster.criminalintent.Crime;
import com.example.aster.criminalintent.database.CrimeDbSchema.CrimeDbSchema.CrimeTable;
public class CrimeBaseHelper extends SQLiteOpenHelper {
private static final int VERSION = 1 ;
private static final String DATABASE_NAME = "crimeBase.db" ;
public CrimeBaseHelper (Context context) {
super (context, DATABASE_NAME, null , VERSION);
}
@Override
public void onCreate (SQLiteDatabase db) {
db.execSQL("create table " + CrimeTable.NAME + "(" +
" _id integer primary key autoincrement, " +
CrimeTable.Cols.UUID + "," +
CrimeTable.Cols.TITLE + "," +
CrimeTable.Cols.DATE + "," +
CrimeTable.Cols.SOLVED +
")"
);
}
@Override
public void onUpgrade (SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
CrimeCursorWrapper.java
package com .example .aster .criminalintent .database .CrimeDbSchema
import android.database .Cursor
import android.database .CursorWrapper
import com .example .aster .criminalintent .Crime
import java.util .Date
import java.util .UUID
import static com .example .aster .criminalintent .database .CrimeDbSchema .CrimeDbSchema .*
public class CrimeCursorWrapper extends CursorWrapper {
public CrimeCursorWrapper(Cursor cursor) {
super(cursor)
}
public Crime getCrime() {
String uuidString = getString(getColumnIndex(CrimeTable.Cols .UUID ))
String title = getString(getColumnIndex(CrimeTable.Cols .TITLE ))
long date = getLong(getColumnIndex(CrimeTable.Cols .DATE ))
int isSolved = getInt(getColumnIndex(CrimeTable.Cols .SOLVED ))
Crime crime = new Crime(UUID.fromString (uuidString))
crime.setTitle (title)
crime.setDate (new Date(date))
crime.setSolved (isSolved != 0 )
return crime
}
}
CrimeDbSchema
package com.example.aster.criminalintent.database.CrimeDbSchema;
public class CrimeDbSchema {
public static final class CrimeTable {
public static final String NAME = "crimes" ;
public static final class Cols {
public static final String UUID = "uuid" ;
public static final String TITLE = "title" ;
public static final String DATE = "date" ;
public static final String SOLVED = "solved" ;
}
}
}
Crime.java
package com.example.aster.criminalintent;
import java.util.Date;
import java.util.UUID;
public class Crime {
private UUID mId;
private String mTitle;
private Date mDate;
private boolean mSolved;
private String mSuspect;
public Crime () {
this (UUID.randomUUID());
}
public Crime (UUID id) {
mId = id;
mDate = new Date();
}
public UUID getId () {
return mId;
}
public String getTitle () {
return mTitle;
}
public void setTitle (String title) {
mTitle = title;
}
public Date getDate () {
return mDate;
}
public void setDate (Date date) {
mDate = date;
}
public boolean isSolved () {
return mSolved;
}
public void setSolved (boolean solved) {
mSolved = solved;
}
public String getSuspect () {
return mSuspect;
}
public void setSuspect (String suspect) {
mSuspect = suspect;
}
public String getPhotoFilename () {
return "IMG_" + getId().toString() + ".jpg" ;
}
}
CrimeFragment.java
package com.example.aster.criminalintent;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.provider.MediaStore;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.content.FileProvider;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import java.io.File;
import java.text.DateFormat;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import static android.widget.CompoundButton.*;
public class CrimeFragment extends Fragment {
private static final String ARG_CRIME_ID = "crime_id" ;
private static final String DIALOG_DATE = "DialogDate" ;
private static final int REQUEST_DATE = 0 ;
private static final int REQUEST_CONTACT = 1 ;
private static final int REQUEST_PHOTO = 2 ;
private Crime mCrime;
private File mPhotoFile;
private EditText mTitleField;
private Button mDateButton;
private CheckBox mSolvedCheckBox;
private Button mSuspectButton;
private Button mReportButton;
private ImageButton mPhotoButton;
private ImageView mPhotoView;
private Callbacks mCallbacks;
/**
* Required interface for hosting activities
*/
public interface Callbacks {
void onCrimeUpdated(Crime crime);
}
public static CrimeFragment newInstance (UUID crimeId) {
Bundle args = new Bundle();
args.putSerializable(ARG_CRIME_ID, crimeId);
CrimeFragment fragment = new CrimeFragment();
fragment.setArguments(args);
return fragment;
}
@Override
public void onAttach (Context context) {
super .onAttach(context);
mCallbacks = (Callbacks) context;
}
@Override
public void onCreate (Bundle savedInstanceState) {
super .onCreate(savedInstanceState);
UUID crimeId = (UUID) getArguments().getSerializable(ARG_CRIME_ID);
mCrime = CrimeLab.get(getActivity()).getCrime(crimeId);
mPhotoFile = CrimeLab.get(getActivity()).getPhotoFile(mCrime);
}
@Override
public void onPause () {
super .onPause();
CrimeLab.get(getActivity())
.updateCrime(mCrime);
}
@Override
public void onDetach () {
super .onDetach();
mCallbacks = null ;
}
@Override
public View onCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_crime, container, false );
mTitleField = (EditText) v.findViewById(R.id.crime_title);
mTitleField.setText(mCrime.getTitle());
mTitleField.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged (CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged (CharSequence s, int start, int before, int count) {
mCrime.setTitle(s.toString());
updateCrime();
}
@Override
public void afterTextChanged (Editable s) {
}
});
mDateButton = (Button) v.findViewById(R.id.crime_date);
updateDate();
mDateButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick (View v) {
FragmentManager manager = getFragmentManager();
DatePickerFragment dialog = DatePickerFragment
.newInstance(mCrime.getDate());
dialog.setTargetFragment(CrimeFragment.this , REQUEST_DATE);
dialog.show(manager, DIALOG_DATE);
}
});
mSolvedCheckBox = (CheckBox) v.findViewById(R.id.crime_solved);
mSolvedCheckBox.setChecked(mCrime.isSolved());
mSolvedCheckBox.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged (CompoundButton buttonView, boolean isChecked) {
mCrime.setSolved(isChecked);
updateCrime();
}
});
mReportButton = (Button) v.findViewById(R.id.crime_report);
mReportButton.setOnClickListener(new View.OnClickListener() {
public void onClick (View v) {
Intent i = new Intent(Intent.ACTION_SEND);
i.setType("text/plain" );
i.putExtra(Intent.EXTRA_TEXT, getCrimeReport());
i.putExtra(Intent.EXTRA_SUBJECT,
getString(R.string.crime_report_subject));
i = Intent.createChooser(i, getString(R.string.send_report));
startActivity(i);
}
});
final Intent pickContact = new Intent(Intent.ACTION_PICK,
ContactsContract.Contacts.CONTENT_URI);
mSuspectButton = (Button) v.findViewById(R.id.crime_suspect);
mSuspectButton.setOnClickListener(new View.OnClickListener() {
public void onClick (View v) {
startActivityForResult(pickContact, REQUEST_CONTACT);
}
});
if (mCrime.getSuspect() != null ) {
mSuspectButton.setText(mCrime.getSuspect());
}
PackageManager packageManager = getActivity().getPackageManager();
if (packageManager.resolveActivity(pickContact,
PackageManager.MATCH_DEFAULT_ONLY) == null ) {
mSuspectButton.setEnabled(false );
}
mPhotoButton = (ImageButton) v.findViewById(R.id.crime_camera);
final Intent captureImage = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
boolean canTakePhoto = mPhotoFile != null &&
captureImage.resolveActivity(packageManager) != null ;
mPhotoButton.setEnabled(canTakePhoto);
mPhotoButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick (View v) {
Uri uri = FileProvider.getUriForFile(getActivity(),
"com.example.aster.criminalintent.fileprovider" , mPhotoFile);
captureImage.putExtra(MediaStore.EXTRA_OUTPUT, uri);
List<ResolveInfo> cameraActivities = getActivity()
.getPackageManager().queryIntentActivities(captureImage,
PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo activity : cameraActivities) {
getActivity().grantUriPermission(activity.activityInfo.packageName,
uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
startActivityForResult(captureImage, REQUEST_PHOTO);
}
});
mPhotoView = (ImageView) v.findViewById(R.id.crime_photo);
updatePhotoView();
return v;
}
@Override
public void onActivityResult (int requestCode, int resultCode, Intent data) {
if (resultCode != Activity.RESULT_OK) {
return ;
}
if (requestCode == REQUEST_DATE) {
Date date = (Date) data
.getSerializableExtra(DatePickerFragment.EXTRA_DATE);
mCrime.setDate(date);
updateCrime();
updateDate();
} else if (requestCode == REQUEST_CONTACT && data != null ) {
Uri contactUri = data.getData();
String[] queryFields = new String[] {
ContactsContract.Contacts.DISPLAY_NAME
};
Cursor c = getActivity().getContentResolver()
.query(contactUri, queryFields, null , null , null );
try {
if (c.getCount() == 0 ) {
return ;
}
c.moveToFirst();
String suspect = c.getString(0 );
mCrime.setSuspect(suspect);
updateCrime();
mSuspectButton.setText(suspect);
} finally {
c.close();
}
} else if (requestCode == REQUEST_PHOTO) {
Uri uri = FileProvider.getUriForFile(getActivity(),
"com.example.aster.criminalintent.fileprovider" ,
mPhotoFile);
getActivity().revokeUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
updateCrime();
updatePhotoView();
}
}
private void updateCrime () {
CrimeLab.get(getActivity()).updateCrime(mCrime);
mCallbacks.onCrimeUpdated(mCrime);
}
private void updateDate () {
mDateButton.setText(mCrime.getDate().toString());
}
private String getCrimeReport () {
String solvedString = null ;
if (mCrime.isSolved()) {
solvedString = getString(R.string.crime_report_solved);
} else {
solvedString = getString(R.string.crime_report_unsolved);
}
String dateFormat = "EEE, MMM dd" ;
String dateString = android.text.format.DateFormat.format(dateFormat, mCrime.getDate()).toString();
String suspect = mCrime.getSuspect();
if (suspect == null ) {
suspect = getString(R.string.crime_report_no_suspect);
} else {
suspect = getString(R.string.crime_report_suspect, suspect);
}
String report = getString(R.string.crime_report,
mCrime.getTitle(), dateString, solvedString, suspect);
return report;
}
private void updatePhotoView () {
if (mPhotoFile == null || !mPhotoFile.exists()) {
mPhotoView.setImageDrawable(null );
} else {
Bitmap bitmap = PictureUtils.getScaledBitmap(mPhotoFile.getPath(), getActivity());
mPhotoView.setImageBitmap(bitmap);
}
}
}
CrimeLab.java
package com .example .aster .criminalintent
import android.content .ContentValues
import android.content .Context
import android.database .Cursor
import android.database .sqlite .SQLiteDatabase
import com .example .aster .criminalintent .database .CrimeDbSchema .CrimeBaseHelper
import com .example .aster .criminalintent .database .CrimeDbSchema .CrimeCursorWrapper
import com .example .aster .criminalintent .database .CrimeDbSchema .CrimeDbSchema
import java.io .File
import java.util .ArrayList
import java.util .List
import java.util .UUID
import static com .example .aster .criminalintent .database .CrimeDbSchema .CrimeDbSchema .*
public class CrimeLab {
private static CrimeLab sCrimeLab
private Context mContext
private SQLiteDatabase mDatabase
public static CrimeLab get(Context context) {
if (sCrimeLab == null) {
sCrimeLab = new CrimeLab(context)
}
return sCrimeLab
}
private CrimeLab(Context context) {
mContext = context.getApplicationContext ()
mDatabase = new CrimeBaseHelper(mContext)
.getWritableDatabase ()
}
public void addCrime(Crime c) {
ContentValues values = getContentValues(c)
mDatabase.insert (CrimeTable.NAME , null, values)
}
public List<Crime> getCrimes() {
List<Crime> crimes = new ArrayList<>()
CrimeCursorWrapper cursor = queryCrimes(null, null)
try {
cursor.moveToFirst ()
while (!cursor.isAfterLast ()) {
crimes.add (cursor.getCrime ())
cursor.moveToNext ()
}
} finally {
cursor.close ()
}
return crimes
}
public Crime getCrime(UUID id) {
CrimeCursorWrapper cursor = queryCrimes(
CrimeTable.Cols .UUID + " = ?" ,
new String[] { id.toString () }
)
try {
if (cursor.getCount () == 0 ) {
return null
}
cursor.moveToFirst ()
return cursor.getCrime ()
} finally {
cursor.close ()
}
}
public File getPhotoFile(Crime crime) {
File filesDir = mContext.getFilesDir ()
return new File(filesDir, crime.getPhotoFilename ())
}
public void updateCrime(Crime crime) {
String uuidString = crime.getId ().toString ()
ContentValues values = getContentValues(crime)
mDatabase.update (CrimeTable.NAME , values,
CrimeTable.Cols .UUID + " = ?" ,
new String[] { uuidString })
}
private CrimeCursorWrapper queryCrimes(String whereClause, String[] whereArgs) {
Cursor cursor = mDatabase.query (
CrimeTable.NAME ,
null, // Columns - null selects all columns
whereClause,
whereArgs,
null, // groupBy
null, // having
null // orderBy
)
return new CrimeCursorWrapper(cursor)
}
private static ContentValues getContentValues(Crime crime) {
ContentValues values = new ContentValues()
values.put (CrimeTable.Cols .UUID , crime.getId ().toString ())
values.put (CrimeTable.Cols .TITLE , crime.getTitle ())
values.put (CrimeTable.Cols .DATE , crime.getDate ().getTime ())
values.put (CrimeTable.Cols .SOLVED , crime.isSolved () ? 1 : 0 )
values.put (CrimeTable.Cols .SUSPECT , crime.getSuspect ())
return values
}
}
CrimeListActivity.java
package com.example.aster.criminalintent;
import android.content.Intent;
import android.support.v4.app.Fragment;
public class CrimeListActivity extends SingleFragmentActivity
implements CrimeListFragment .Callbacks , CrimeFragment .Callbacks {
@Override
protected Fragment createFragment () {
return new CrimeListFragment();
}
@Override
protected int getLayoutResId () {
return R.layout.activity_masterdetail;
}
@Override
public void onCrimeSelected (Crime crime) {
if (findViewById(R.id.detail_fragment_container) == null ) {
Intent intent = CrimePagerActivity.newIntent(this , crime.getId());
startActivity(intent);
} else {
Fragment newDetail = CrimeFragment.newInstance(crime.getId());
getSupportFragmentManager().beginTransaction()
.replace(R.id.detail_fragment_container, newDetail)
.commit();
}
}
public void onCrimeUpdated (Crime crime) {
CrimeListFragment listFragment = (CrimeListFragment)
getSupportFragmentManager()
.findFragmentById(R.id.fragment_container);
listFragment.updateUI();
}
}
CrimeListFragment.java
package com.example.aster.criminalintent;
import android.app.Activity;
import android.app.LoaderManager;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
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.util.List;
public class CrimeListFragment extends Fragment {
private static final String SAVED_SUBTITLE_VISIBLE = "subtitle" ;
private RecyclerView mCrimeRecyclerView;
private CrimeAdapter mAdapter;
private boolean mSubtitleVisible;
private Callbacks mCallbacks;
public interface Callbacks {
void onCrimeSelected(Crime crime);
}
@Override
public void onAttach (Context context) {
super.onAttach(context);
mCallbacks = (Callbacks) context;
}
@Override
public void onCreate (Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true );
}
@Override
public View onCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_crime_list, container, false );
mCrimeRecyclerView = (RecyclerView) view.findViewById(R.id.crime_recycler_view);
mCrimeRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
if (savedInstanceState != null ) {
mSubtitleVisible = savedInstanceState.getBoolean(SAVED_SUBTITLE_VISIBLE);
}
updateUI();
return view;
}
@Override
public void onResume () {
super.onResume();
updateUI();
}
@Override
public void onSaveInstanceState (Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(SAVED_SUBTITLE_VISIBLE, mSubtitleVisible);
}
@Override
public void onDetach () {
super.onDetach();
mCallbacks = null ;
}
@Override
public void onCreateOptionsMenu (Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.fragment_crime_list, menu);
MenuItem subtitleItem = menu.findItem(R.id.show_subtitle);
if (mSubtitleVisible) {
subtitleItem.setTitle(R.string .hide_subtitle);
} else {
subtitleItem.setTitle(R.string .show_subtitle);
}
}
@Override
public boolean onOptionsItemSelected (MenuItem item) {
switch (item.getItemId()) {
case R.id.new_crime:
Crime crime = new Crime();
CrimeLab.get (getActivity()).addCrime(crime);
updateUI();
mCallbacks.onCrimeSelected(crime);
return true ;
case R.id.show_subtitle:
mSubtitleVisible = !mSubtitleVisible;
getActivity().invalidateOptionsMenu();
updateSubtitle();
return true ;
default :
return super.onOptionsItemSelected(item);
}
}
private void updateSubtitle () {
CrimeLab crimeLab = CrimeLab.get (getActivity());
int crimeCount = crimeLab.getCrimes().size();
String subtitle = getString(R.string .subtitle_format, crimeCount);
if (!mSubtitleVisible) {
subtitle = null ;
}
AppCompatActivity activity = (AppCompatActivity) getActivity();
activity.getSupportActionBar().setSubtitle(subtitle);
}
public void updateUI () {
CrimeLab crimeLab = CrimeLab.get (getActivity());
List<Crime> crimes = crimeLab.getCrimes();
if (mAdapter == null ) {
mAdapter = new CrimeAdapter(crimes);
mCrimeRecyclerView.setAdapter(mAdapter);
} else {
mAdapter.setCrimes(crimes);
mAdapter.notifyDataSetChanged();
}
updateSubtitle();
}
private class CrimeHolder extends RecyclerView.ViewHolder
implements View.OnClickListener {
private Crime mCrime;
private TextView mTitleTextView;
private TextView mDateTextView;
private ImageView mSolvedImageView;
public CrimeHolder (LayoutInflater inflater, ViewGroup parent) {
super(inflater.inflate(R.layout.list_item_crime, parent, false ));
itemView.setOnClickListener(this );
mTitleTextView = (TextView) itemView.findViewById(R.id.crime_title);
mDateTextView = (TextView) itemView.findViewById(R.id.crime_date);
mSolvedImageView = (ImageView) itemView.findViewById(R.id.crime_solved);
}
public void bind (Crime crime) {
mCrime = crime;
mTitleTextView.setText(mCrime.getTitle());
mDateTextView.setText(mCrime.getDate().toString());
mSolvedImageView.setVisibility(crime.isSolved() ? View.VISIBLE : View.GONE);
}
@Override
public void onClick (View view) {
mCallbacks.onCrimeSelected(mCrime);
}
}
private class CrimeAdapter extends RecyclerView.Adapter<CrimeHolder> {
private List<Crime> mCrimes;
public CrimeAdapter (List<Crime> crimes) {
mCrimes = crimes;
}
@Override
public CrimeHolder onCreateViewHolder (ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from (getActivity());
return new CrimeHolder(layoutInflater, parent);
}
@Override
public void onBindViewHolder (CrimeHolder holder, int position) {
Crime crime = mCrimes.get (position);
holder.bind(crime);
}
@Override
public int getItemCount () {
return mCrimes.size();
}
public void setCrimes (List<Crime> crimes) {
mCrimes = crimes;
}
}
}
package com.example.aster.criminalintent;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import java.util.List;
import java.util.UUID;
public class CrimePagerActivity extends AppCompatActivity
implements CrimeFragment .Callbacks {
private static final String EXTRA_CRIME_ID =
"com.example.aster.criminalintent.crime_id" ;
private ViewPager mViewPager;
private List<Crime> mCrimes;
public static Intent newIntent (Context packageContext, UUID crimeId) {
Intent intent = new Intent(packageContext, CrimePagerActivity.class);
intent.putExtra(EXTRA_CRIME_ID, crimeId);
return intent;
}
@Override
protected void onCreate (Bundle savedInstanceState) {
super .onCreate(savedInstanceState);
setContentView(R.layout.activity_crime_pager);
UUID crimeId = (UUID) getIntent()
.getSerializableExtra(EXTRA_CRIME_ID);
mViewPager = (ViewPager) findViewById(R.id.crime_view_pager);
mCrimes = CrimeLab.get(this ).getCrimes();
FragmentManager fragmentManager = getSupportFragmentManager();
mViewPager.setAdapter(new FragmentStatePagerAdapter(fragmentManager) {
@Override
public Fragment getItem (int position) {
Crime crime = mCrimes.get(position);
return CrimeFragment.newInstance(crime.getId());
}
@Override
public int getCount () {
return mCrimes.size();
}
});
for (int i = 0 ; i < mCrimes.size(); i++) {
if (mCrimes.get(i).getId().equals(crimeId)) {
mViewPager.setCurrentItem(i);
break ;
}
}
}
@Override
public void onCrimeUpdated (Crime crime) {
}
}
DatePickerFragment.java
package com.example.aster.criminalintent;
import android.app.Activity;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.DatePicker;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
public class DatePickerFragment extends DialogFragment {
public static final String EXTRA_DATE =
"com.example.aster.criminalintent.date" ;
private static final String ARG_DATE = "date" ;
private DatePicker mDatePicker;
public static DatePickerFragment newInstance (Date date) {
Bundle args = new Bundle();
args.putSerializable(ARG_DATE, date);
DatePickerFragment fragment = new DatePickerFragment();
fragment.setArguments(args);
return fragment;
}
@Override
public Dialog onCreateDialog (Bundle savedInstanceState) {
Date date = (Date) getArguments().getSerializable(ARG_DATE);
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
int year = calendar.get(Calendar.YEAR);
int month = calendar.get(Calendar.MONTH);
int day = calendar.get(Calendar.DAY_OF_MONTH);
View v = LayoutInflater.from(getActivity())
.inflate(R.layout.dialog_date, null );
mDatePicker = (DatePicker) v.findViewById(R.id.dialog_date_picker);
mDatePicker.init(year, month, day, null );
return new AlertDialog.Builder(getActivity())
.setView(v)
.setTitle(R.string.date_picker_title)
.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
@Override
public void onClick (DialogInterface dialog, int which) {
int year = mDatePicker.getYear();
int month = mDatePicker.getMonth();
int day = mDatePicker.getDayOfMonth();
Date date = new GregorianCalendar(year, month, day).getTime();
sendResult(Activity.RESULT_OK, date);
}
})
.create();
}
private void sendResult (int resultCode, Date date) {
if (getTargetFragment() == null ) {
return ;
}
Intent intent = new Intent();
intent.putExtra(EXTRA_DATE, date);
getTargetFragment()
.onActivityResult(getTargetRequestCode(), resultCode, intent);
}
}
PictureUtils.java
package com.example.aster.criminalintent;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Point;
public class PictureUtils {
public static Bitmap getScaledBitmap (String path, Activity activity) {
Point size = new Point();
activity.getWindowManager().getDefaultDisplay().getSize(size);
return getScaledBitmap(path, size.x, size.y);
}
public static Bitmap getScaledBitmap (String path, int destWidth, int destHeight) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true ;
BitmapFactory.decodeFile(path, options);
float srcWidth = options.outWidth;
float srcHeight = options.outHeight;
int inSampleSize = 1 ;
if (srcHeight > destHeight || srcWidth > destWidth) {
float heightScale = srcHeight / destHeight;
float widthScale = srcWidth / destWidth;
inSampleSize = Math.round(heightScale > widthScale ? heightScale : widthScale);
}
options = new BitmapFactory.Options();
options.inSampleSize = inSampleSize;
return BitmapFactory.decodeFile(path, options);
}
}
SingleFragmentActivity.java
package com.example.aster.criminalintent;
import android.os.Bundle;
import android.support.annotation.LayoutRes;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;
public abstract class SingleFragmentActivity extends AppCompatActivity {
protected abstract Fragment createFragment ();
@LayoutRes
protected int getLayoutResId () {
return R.layout.activity_fragment;
}
@Override
protected void onCreate (Bundle savedInstanceState) {
super .onCreate(savedInstanceState);
setContentView(getLayoutResId());
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentById(R.id.fragment_container);
if (fragment == null ) {
fragment = createFragment();
fm.beginTransaction().add(R.id.fragment_container, fragment).commit();
}
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android ="http://schemas.android.com/apk/res/android"
package ="com.example.aster.criminalintent" >
<uses-feature android:name ="android.hardware.camera"
android:required ="false"
/>
<application
android:allowBackup ="true"
android:icon ="@mipmap/ic_launcher"
android:label ="@string/app_name"
android:roundIcon ="@mipmap/ic_launcher_round"
android:supportsRtl ="true"
android:theme ="@style/AppTheme" >
<activity android:name =".CrimeListActivity" >
<intent-filter >
<action android:name ="android.intent.action.MAIN" />
<category android:name ="android.intent.category.LAUNCHER" />
</intent-filter >
</activity >
<activity
android:name =".CrimePagerActivity"
android:parentActivityName =".CrimeListActivity" >
</activity >
<provider
android:name ="android.support.v4.content.FileProvider"
android:authorities ="com.example.aster.criminalintent.fileprovider"
android:exported ="false"
android:grantUriPermissions ="true" >
<meta-data
android:name ="android.support.FILE_PROVIDER_PATHS"
android:resource ="@xml/files" />
</provider >
</application >
</manifest >
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.view.ViewPager
xmlns:android ="http://schemas.android.com/apk/res/android"
android:id ="@+id/crime_view_pager"
android:layout_width ="match_parent"
android:layout_height ="match_parent" >
</android.support.v4.view.ViewPager >
activity_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android ="http://schemas.android.com/apk/res/android"
android:id ="@+id/fragment_container"
android:layout_width ="match_parent"
android:layout_height ="match_parent" />
activity_twopane.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android ="http://schemas.android.com/apk/res/android"
android:layout_width ="match_parent"
android:layout_height ="match_parent"
android:divider ="?android:attr/dividerHorizontal"
android:showDividers ="middle"
android:orientation ="horizontal" >
<FrameLayout
android:id ="@+id/fragment_container"
android:layout_width ="0dp"
android:layout_height ="match_parent"
android:layout_weight ="1" >
</FrameLayout >
<FrameLayout
android:id ="@+id/detail_fragment_container"
android:layout_width ="0dp"
android:layout_height ="match_parent"
android:layout_weight ="3" >
</FrameLayout >
</LinearLayout >
dialog_date.xml
<?xml version="1.0" encoding="utf-8"?>
<DatePicker
xmlns:android ="http://schemas.android.com/apk/res/android"
android:id ="@+id/dialog_date_picker"
android:layout_width ="wrap_content"
android:layout_height ="wrap_content"
android:calendarViewShown ="false" >
</DatePicker >
fragment_crime.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android ="http://schemas.android.com/apk/res/android"
android:layout_width ="match_parent"
android:layout_height ="match_parent"
android:layout_margin ="16dp"
android:orientation ="vertical" >
<LinearLayout
android:orientation ="horizontal"
android:layout_width ="match_parent"
android:layout_height ="wrap_content"
android:layout_marginLeft ="16dp"
android:layout_marginTop ="16dp" >
<LinearLayout
android:orientation ="vertical"
android:layout_width ="wrap_content"
android:layout_height ="wrap_content" >
<ImageView
android:id ="@+id/crime_photo"
android:layout_width ="80dp"
android:layout_height ="80dp"
android:scaleType ="centerInside"
android:cropToPadding ="true"
android:background ="@android:color/darker_gray" />
<ImageButton
android:id ="@+id/crime_camera"
android:layout_width ="match_parent"
android:layout_height ="wrap_content"
android:src ="@android:drawable/ic_menu_camera" />
</LinearLayout >
<LinearLayout
android:orientation ="vertical"
android:layout_width ="0dp"
android:layout_height ="wrap_content"
android:layout_weight ="1" >
<TextView
android:layout_width ="match_parent"
android:layout_height ="wrap_content"
android:text ="@string/crime_title_label"
style ="?android:listSeparatorTextViewStyle" />
<EditText
android:id ="@+id/crime_title"
android:layout_width ="match_parent"
android:layout_height ="wrap_content"
android:hint ="@string/crime_title_hint" />
</LinearLayout >
</LinearLayout >
<TextView
style ="?android:listSeparatorTextViewStyle"
android:layout_width ="match_parent"
android:layout_height ="wrap_content"
android:text ="@string/crime_details_label" />
<Button
android:id ="@+id/crime_date"
android:layout_width ="match_parent"
android:layout_height ="wrap_content" />
<CheckBox
android:id ="@+id/crime_solved"
android:layout_width ="match_parent"
android:layout_height ="wrap_content"
android:text ="@string/crime_solved_label" />
<Button
android:id ="@+id/crime_suspect"
android:layout_width ="match_parent"
android:layout_height ="wrap_content"
android:text ="@string/crime_suspect_text" />
<Button
android:id ="@+id/crime_report"
android:layout_width ="match_parent"
android:layout_height ="wrap_content"
android:text ="@string/crime_report_text" />
</LinearLayout >
fragment_crime_list.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView
xmlns:android ="http://schemas.android.com/apk/res/android"
android:id ="@+id/crime_recycler_view"
android:layout_width ="match_parent"
android:layout_height ="match_parent" />
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android ="http://schemas.android.com/apk/res/android"
xmlns:app ="http://schemas.android.com/apk/res-auto" >
<item
android:id ="@+id/new_crime"
android:icon ="@drawable/ic_menu_add"
android:title ="@string/new_crime"
app:showAsAction ="ifRoom|withText" />
<item
android:id ="@+id/show_subtitle"
android:title ="@string/show_subtitle"
app:showAsAction ="ifRoom" />
</menu >
list_item_crime.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android ="http://schemas.android.com/apk/res/android"
xmlns:app ="http://schemas.android.com/apk/res-auto"
xmlns:tools ="http://schemas.android.com/tools"
android:id ="@+id/linearLayout"
android:layout_width ="match_parent"
android:layout_height ="wrap_content"
tools:layout_editor_absoluteY ="81dp" >
<TextView
android:id ="@+id/crime_title"
android:layout_width ="0dp"
android:layout_height ="wrap_content"
android:layout_marginEnd ="8dp"
android:layout_marginStart ="16dp"
android:layout_marginTop ="16dp"
android:text ="Crime Title"
app:layout_constraintEnd_toStartOf ="@+id/crime_solved"
app:layout_constraintStart_toStartOf ="parent"
app:layout_constraintTop_toTopOf ="parent" />
<TextView
android:id ="@+id/crime_date"
android:layout_width ="0dp"
android:layout_height ="wrap_content"
android:layout_marginEnd ="8dp"
android:layout_marginStart ="16dp"
android:layout_marginTop ="8dp"
android:text ="Crime Date"
app:layout_constraintEnd_toStartOf ="@+id/crime_solved"
app:layout_constraintStart_toStartOf ="parent"
app:layout_constraintTop_toBottomOf ="@+id/crime_title" />
<ImageView
android:id ="@+id/crime_solved"
android:layout_width ="wrap_content"
android:layout_height ="wrap_content"
android:layout_marginBottom ="16dp"
android:layout_marginEnd ="16dp"
android:layout_marginTop ="16dp"
app:layout_constraintBottom_toBottomOf ="parent"
app:layout_constraintEnd_toEndOf ="parent"
app:layout_constraintTop_toTopOf ="parent"
app:srcCompat ="@drawable/ic_solved" />
</android.support.constraint.ConstraintLayout >
strings.xml
<resources>
<string name ="app_name" >CriminalIntent</string >
<string name ="crime_title_hint" >Enter a title for the crime.</string >
<string name ="crime_title_label" >Title</string >
<string name ="crime_details_label" >Details</string >
<string name ="crime_solved_label" >Solved</string >
<string name ="date_picker_title" >Date of crime:</string >
<string name ="new_crime" >New Crime</string >
<string name ="show_subtitle" >Show Subtitle</string >
<string name ="hide_subtitle" >Hide Subtitle</string >
<string name ="subtitle_format" >%1 $d crimes</string >
<string name ="crime_suspect_text" >Choose Suspect</string >
<string name ="crime_report_text" >Send Crime Report</string >
<string name ="crime_report" >%1 $s!
The crime was discovered on %2 $s. %3 $s, %4 $s</string >
<string name ="crime_report_solved" >The case is solved</string >
<string name ="crime_report_unsolved" >The case is not solved</string >
<string name ="crime_report_no_suspect" >there is no suspect.</string >
<string name ="crime_report_suspect" >the suspect is %s.</string >
<string name ="crime_report_subject" >CriminalIntent Crime Report</string >
<string name ="send_report" >Send crime report via</string >
</resources>
xml/files.xml
<?xml version="1.0" encoding="utf-8"?>
<paths >
<files-path
name ="crime_photos"
path ="." />
</paths >
zh/strings.xml
<?xml version ="1.0" encoding="utf-8" ?>
<resources>
<string name ="app_name" >CriminalIntent</string >
<string name ="crime_title_hint" >crime简短描述</string >
<string name ="crime_title_label" >标题</string >
<string name ="crime_details_label" >明细</string >
<string name ="crime_solved_label" >是否解决</string >
<string name ="date_picker_title" >crime发生日期</string >
<string name ="new_crime" >新增Crime记录</string >
<string name ="show_subtitle" >显示子标题</string >
<string name ="hide_subtitle" >隐藏子标题</string >
<string name ="subtitle_format" >%1 $d crimes</string >
<string name ="crime_suspect_text" >嫌疑人联系方式</string >
<string name ="crime_report_text" >抗议或投诉</string >
<string name ="crime_report" >%1 $s!
crime发生于 %2 $s. %3 $s, %4 $s</string >
<string name ="crime_report_solved" >问题已解决</string >
<string name ="crime_report_unsolved" >问题未解决</string >
<string name ="crime_report_no_suspect" >没找到嫌疑人</string >
<string name ="crime_report_suspect" >嫌疑人是 %s</string >
<string name ="crime_report_subject" >crime处理情况报告</string >
<string name ="send_report" >投诉方式</string >
</resources>
w600dp/strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources >
<string name ="crime_title_hint" >
Enter a meaningful, memorable title for the crime.
</string >
</resources >
zh-w600dp/strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources >
<string name ="crime_title_hint" >
请输入简短、好记的crime描述
</string >
</resources >