public class AddNoteFragment extends Fragment {

    @BindView(R.id.note_tags)
    PersonsCompletionView noteTags;
    @BindView(R.id.add_note_text)
    EditText noteText;
    @BindView(R.id.cancel_note)
    Button cancelNote;
    @BindView(R.id.save_note)
    Button saveNote;

    private List<PersonPOJO> taggedPeople = new ArrayList<>();
    private PersonManager personManager = null;
    private NoteManager noteManager = null;
    private CacheManager cacheManager = null;
    private String noteId = null;

    private static final String TAG = AddNoteFragment.class.getSimpleName();

    public AddNoteFragment() {
        // Required empty public constructor
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setHasOptionsMenu(false);

        taggedPeople = new ArrayList<>();
        personManager = PersonManagerImpl.getInstance(getActivity());
        noteManager = NoteManagerImpl.getInstance(getActivity());
        cacheManager = LocalDailyCacheManagerImpl.getInstance(getActivity());
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // set title
        getActivity().setTitle(getString(R.string.person_add_note));

        // create view and bind
        View view = inflater.inflate(R.layout.fragment_add_note, container, false);
        ButterKnife.bind(this, view);

        // add current person to the list of taggedPeople (if we're on a page that shows a person)
        Bundle bundle = getArguments();
        if (bundle != null) {
            if (bundle.containsKey(Constants.PERSON_ID_BUNDLE_ARG)) {
                String personId = bundle.getString(Constants.PERSON_ID_BUNDLE_ARG);
                if (personId != null) {
                    noteTags.addObject(personManager.getPerson(personId));
                }
            }
            if (bundle.containsKey(Constants.NOTE_ID_BUNDLE_ARG)) {
                noteId = bundle.getString(Constants.NOTE_ID_BUNDLE_ARG);
                NotePOJO note = noteManager.getNote(noteId);
                noteText.setText(note.getText());
                if (note.getPeopleTagged() != null) {
                    for (String personId : note.getPeopleTagged()) {
                        noteTags.addObject(personManager.getPerson(personId));
                    }
                }
            }
        }

        // wire the note tags
        List<PersonPOJO> allPersonPOJOs = personManager.getActivePeople();
        noteTags.setAdapter(new ArrayAdapter<>(getActivity(), android.R.layout.simple_dropdown_item_1line, allPersonPOJOs));
        noteTags.setTokenListener(new TokenCompleteTextView.TokenListener<PersonPOJO>() {
            @Override
            public void onTokenAdded(PersonPOJO token) {
                taggedPeople.add(token);
            }

            @Override
            public void onTokenRemoved(PersonPOJO token) {
                taggedPeople.remove(token);
            }
        });

        // wire the buttons
        saveNote.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                hideKeyboard();
                if (noteId != null) {
                    noteManager.editNote(noteId, null, noteText.getText().toString(), taggedPeople);
                } else {
                    noteManager.addNote(null, noteText.getText().toString(), taggedPeople);
                }
                // simulate back button press to exit this fragment
                getActivity().onBackPressed();
            }
        });
        cancelNote.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                hideKeyboard();

                // simulate back button press to exit this fragment
                getActivity().onBackPressed();
            }
        });

        return view;
    }

    private void hideKeyboard() {
        CommonUtils.hideKeyboard(getActivity());
    }

    @Override
    public void onResume() {
        super.onResume();
        AnalyticsUtils.sendScreenViewHit(this.getActivity(), "NoteScreen");
        getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
    }

    @Override
    public void onPause() {
        getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
        super.onPause();
    }
} public class FragmentBackStackManager {

    private final Stack<FragmentState> backstack;

    public FragmentBackStackManager() {
        this.backstack = new Stack<>();
    }

    public void add(FragmentState fragmentState) {
        backstack.add(fragmentState);
    }

    public FragmentState pop() {
        return backstack.pop();
    }

    public FragmentState peek() {
        return backstack.peek();
    }

    public int size() {
        return backstack.size();
    }

    public boolean isEmpty() {
        return backstack.isEmpty();
    }
} public class FragmentState {
    private final String fragmentName;
    private Bundle state;

    public FragmentState(String fragmentName) {
        this.fragmentName = fragmentName;
    }

    public FragmentState(String fragmentName, Bundle state) {
        this.fragmentName = fragmentName;
        this.state = state;
    }

    public String getFragmentName() {
        return fragmentName;
    }

    public Bundle getState() {
        return state;
    }

    public void setState(Bundle state) {
        this.state = state;
    }
} public interface FragmentStateListener {
    void notify(FragmentState fragmentState);
} public class HomeFragment extends Fragment {
    private static final String TAG = HomeFragment.class.getSimpleName();

    @BindView(R.id.home_view)
    ViewPager viewPager;
    @BindView(R.id.home_indicator)
    CirclePageIndicator indicator;

    private MainActivity activity;
    private boolean useCache;
    private FragmentStateListener mListener;
    private CacheManager cacheManager = null;
    private ImageURLService imageService = null;
    private ScriptureService scriptureService = null;
    private PersonManager personManager = null;

    private SearchView searchView;
    private Integer numberOfPeopleToPrayForDaily;
    private ZoomOutPageTransformer zoomOutPageTransformer;

    public HomeFragment() {
        // Required empty public constructor
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setHasOptionsMenu(true);
        // notify fragment state
        try {
            mListener = (FragmentStateListener) getActivity();
        } catch (ClassCastException e) {
            throw new ClassCastException(getActivity().toString() + " must implement FragmentStateListener");
        }

        try {
            activity = (MainActivity) getActivity();
        } catch (ClassCastException e) {
            throw new ClassCastException(getActivity().toString() + " must be an instanceof MainActivity");
        }

        Bundle currentState = new Bundle();
        currentState.putInt(Constants.HOME_SECTION_NUMBER_BUNDLE_ARG, 0);
        mListener.notify(new FragmentState(getString(R.string.nav_home), currentState));

        // do not use the cache if we're trying to get more people.
        Bundle bundle = getArguments();
        if (bundle != null && bundle.containsKey(Constants.USE_CACHE_BUNDLE_ARG)) {
            this.useCache = bundle.getBoolean(Constants.USE_CACHE_BUNDLE_ARG);
        } else {
            this.useCache = true;
        }

        zoomOutPageTransformer = new ZoomOutPageTransformer();
        cacheManager = LocalDailyCacheManagerImpl.getInstance(activity);
        imageService = ImageURLServiceImpl.getInstance();
        scriptureService = ScriptureServiceImpl.getInstance(activity);
        personManager = PersonManagerImpl.getInstance(activity);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // set title
        activity.setTitle(getString(R.string.nav_home));

        // create view and bind
        View view = inflater.inflate(R.layout.fragment_home, container, false);
        ButterKnife.bind(this, view);

        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
        numberOfPeopleToPrayForDaily = Integer.parseInt(preferences.getString(Constants.NUMBER_OF_PEOPLE_TO_PRAY_FOR, "3"));
        return view;
    }

    private void prepareCache() {
        // if we're not using the cache, set the default page to the first
        if (!useCache) {
            Log.d(TAG, "resetting page index to 0");
            cacheManager.cachePageIndex(0);
        }

        // decide whether or not to fetch a new verse image
        String verseImageURL = cacheManager.getCachedVerseImageURL();
        if (!useCache || verseImageURL == null) {
            updateBackgroundImage();
            new ImageFetcher(activity).execute();
        }

        // verse title and text
        ScriptureData scriptureData = cacheManager.getCachedScripture();
        if (!useCache || scriptureData == null) {
            ScriptureData scripture = scriptureService.getScripture();
            if (scripture != null) {
                Log.d(TAG, "scripture = " + scripture);
                // cache
                cacheManager.cacheScripture(scripture);
            } else {
                Log.e(TAG, "Could not fetch scripture!");
            }
        }

        // people to pray for
        List<String> personIds = cacheManager.getCachedPersonIdsToPrayFor();
        if (!useCache || personIds == null || personIds.size() < numberOfPeopleToPrayForDaily) {
            List<PersonPOJO> personPOJOs;
            try {
                personPOJOs = personManager.getNextPeopleToPrayFor(numberOfPeopleToPrayForDaily);
            } catch (AlreadyPrayedForAllContactsException e) {
                // TODO: Celebrate!
                try {
                    personPOJOs = personManager.getNextPeopleToPrayFor(numberOfPeopleToPrayForDaily);
                } catch (AlreadyPrayedForAllContactsException e1) {
                    // TODO: Something is really wrong if this happens, not sure what to do here
                    throw new RuntimeException(e1);
                }
            }
            personIds = new ArrayList<>();
            for (PersonPOJO personPOJO : personPOJOs) {
                personIds.add(personPOJO.getId());
            }
            cacheManager.cachePersonIdsToPrayFor(personIds);
        }

        // now that the cache is updated, mark it ready for use.
        useCache = true;
    }

    private void prepareViewPager() {
        final Activity activity = this.getActivity();
        viewPager.setOffscreenPageLimit(numberOfPeopleToPrayForDaily + 1);
        final FragmentStatePagerAdapter pagerAdapter = getFragmentStatePagerAdapter();
        viewPager.setAdapter(pagerAdapter);
        viewPager.setPageTransformer(true, zoomOutPageTransformer);
        // since this gets called multiple times, we need to clear any existing onpagechangelisteners.
        // otherwise the listeners will accumulate. For example, open a quickcontent intent and go back.
        // suddenly you have two onPageChangeListeners attached.
        // Question: Does this mean that we maybe don't even need to configure the viewpager every single time?
        viewPager.clearOnPageChangeListeners();
        viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            }

            @Override
            public void onPageSelected(int position) {
                ICardPageFragment card = (ICardPageFragment) pagerAdapter.getItem(position);
                cacheManager.cachePageIndex(viewPager.getCurrentItem());
                AnalyticsUtils.sendScreenViewHit(activity, card.getCardName());

                Bundle newState = new Bundle();
                newState.putInt(Constants.HOME_SECTION_NUMBER_BUNDLE_ARG, position);
                if (position > 0 && position < numberOfPeopleToPrayForDaily + 1) {
                    List<String> personIds = cacheManager.getCachedPersonIdsToPrayFor();
                    if (personIds != null && personIds.size() >= position) {
                        String personId = personIds.get(position - 1);
                        newState.putString(Constants.PERSON_ID_BUNDLE_ARG, personId);
                    }
                }

                // notify fragment state
                FragmentState fragmentState = new FragmentState(getString(R.string.nav_home), newState);
                mListener.notify(fragmentState);
            }

            @Override
            public void onPageScrollStateChanged(int state) {
            }
        });

        // wire up the indicator
        indicator.setViewPager(viewPager);

        // set the page if required
        Integer cachedPageIndex = cacheManager.getCachedPageIndex();
        if (cachedPageIndex != null) {
            Log.d(TAG, "setting pager to " + cachedPageIndex);
            viewPager.setCurrentItem(cachedPageIndex);
        }
    }

    private FragmentStatePagerAdapter getFragmentStatePagerAdapter() {
        return new FragmentStatePagerAdapter(activity.getSupportFragmentManager()) {
            @Override
            public androidx.fragment.app.Fragment getItem(int position) {
                androidx.fragment.app.Fragment fragment;
                Bundle bundle = new Bundle();
                if (position == 0) {
                    fragment = new ScriptureCardSupportFragment();
                } else if (position == getCount() - 1) {
                    fragment = new ProgressCardSupportFragment();
                } else {
                    List<String> personIds = cacheManager.getCachedPersonIdsToPrayFor();
                    // we need at least as many people as there are slots to fill
                    if (personIds != null && personIds.size() >= position) {
                        String personId = personIds.get(position - 1);
                        PersonPOJO person = personManager.getPerson(personId);
                        if (person == null) {
                            fragment = new BlankSupportFragment();
                        } else {
                            fragment = PersonSupportFragment.newInstance(personId);
                            bundle.putInt(Constants.HOME_SECTION_NUMBER_BUNDLE_ARG, position);
                        }
                    } else {
                        fragment = new BlankSupportFragment();
                    }
                }

                // update the fragment's bundle so it has the data it needs
                bundle.putBoolean(Constants.USE_CACHE_BUNDLE_ARG, useCache);
                if (fragment.getArguments() != null) {
                    fragment.getArguments().putAll(bundle);
                } else {
                    fragment.setArguments(bundle);
                }

                return fragment;
            }

            @Override
            public int getCount() {
                if (cacheManager == null || cacheManager.getCachedPersonIdsToPrayFor() == null) {
                    return Constants.NUM_AUXILIARY_CARDS;
                }

                return cacheManager.getCachedPersonIdsToPrayFor().size()
                        + Constants.NUM_AUXILIARY_CARDS;
            }
        };
    }

    private void updateBackgroundImage() {
        File nextBackgroundImage = new File(activity.getCacheDir(), Constants.NEXT_BACKGROUND_IMAGE);
        File currentBackgroundImage = new File(activity.getCacheDir(), Constants.CURRENT_BACKGROUND_IMAGE);

        // move cached next background image
        if (nextBackgroundImage.exists()) {
            if (nextBackgroundImage.renameTo(currentBackgroundImage)) {
                Log.d(TAG, "Updated the background image to use from " + nextBackgroundImage + " to " + currentBackgroundImage);
                Log.d(TAG, "New image size: " + currentBackgroundImage.length());
                Picasso.with(activity).invalidate(currentBackgroundImage); // clear the picasso cache
                CommonUtils.setupBackgroundImage(activity, activity.findViewById(R.id.backgroundImageView));
            } else {
                Log.d(TAG, "Could not update the background image to use.");
            }
        }
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if (!activity.getHomeFragmentCreated()) {
            // if the activity was just created and hence the HomeFragment
            // was created for the first time, start on the first page.
            // otherwise, we start on the page where the HomeFragment was before
            cacheManager.cachePageIndex(0);
            activity.setHomeFragmentCreated(true);
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        AnalyticsUtils.sendScreenViewHit(this.getActivity(), "HomeScreen");
        boolean searchViewFocused = searchView != null && searchView.hasFocus();
        // create the pager so we see the cards
        if (activity.getHomeFragmentCreated() && !searchViewFocused) {
            prepareCache();
            prepareViewPager();
        }
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        inflater.inflate(R.menu.menu, menu);
        MenuItem searchItem = menu.findItem(R.id.search);
        SearchManager searchManager =
                (SearchManager) activity.getSystemService(Context.SEARCH_SERVICE);
        searchView =
                (SearchView) searchItem.getActionView();
        searchView.setSearchableInfo(
                searchManager.getSearchableInfo(activity.getComponentName()));
        super.onCreateOptionsMenu(menu, inflater);
    }

    private class ImageFetcher extends AsyncTask<String, Void, String> {

        private Activity activity;

        public ImageFetcher(Activity activity) {
            this.activity = activity;
        }

        @Override
        protected String doInBackground(String... params) {
            return imageService.getImageURL();
        }

        @Override
        protected void onPostExecute(String imageUrl) {
            if (imageUrl != null) {
                Log.d(TAG, "imageUrl = " + imageUrl);
                // cache
                cacheManager.cacheVerseImageURL(imageUrl);
                try {
                    // fetch new background image
                    File nextBackgroundImage = new File(activity.getCacheDir(), Constants.NEXT_BACKGROUND_IMAGE);
                    new DownloadFileAsyncTask(activity, imageUrl, nextBackgroundImage).execute();
                } catch (Exception e) {
                    Log.e(TAG, "Error fetching scripture image!");
                    e.printStackTrace();
                }
            } else {
                Log.e(TAG, "Could not fetch scripture image!");
            }
        }
    }
} public class PeopleFragment extends Fragment {
    private static final String TAG = PeopleFragment.class.getSimpleName();
    private final Handler handler = new Handler();
    @BindView(R.id.people_view)
    ViewPager viewPager;
    @BindView(R.id.people_indicator)
    UnderlinePageIndicator indicator;
    @BindView(R.id.people_active_tab)
    TextView activeTab;
    @BindView(R.id.people_favorite_tab)
    TextView favoriteTab;
    @BindView(R.id.people_removed_tab)
    TextView removedTab;
    private String[] TABS;
    private Runnable runPager;
    private FragmentStateListener mListener;
    private boolean mCreated = false;
    private boolean showingPerson = false;

    public PeopleFragment() {
        // Required empty public constructor
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setHasOptionsMenu(true);
        // notify fragment state
        try {
            mListener = (FragmentStateListener) getActivity();
        } catch (ClassCastException e) {
            throw new ClassCastException(getActivity().toString() + " must implement FragmentStateListener");
        }
        mListener.notify(new FragmentState(getString(R.string.nav_people)));
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // set title
        getActivity().setTitle(getString(R.string.nav_people));

        // set tab titles
        TABS = new String[]{getString(R.string.people_active), getString(R.string.people_favorite), getString(R.string.people_removed)};

        // create view and bind
        View view = inflater.inflate(R.layout.fragment_people, container, false);
        ButterKnife.bind(this, view);

        // wire up the people view pager
        runPager = new Runnable() {
            @Override
            public void run() {
                final Container<Refreshable> activeRefreshable = new Container<>();
                final Container<Refreshable> favoriteRefreshable = new Container<>();
                final Container<Refreshable> removedRefreshable = new Container<>();

                viewPager.setAdapter(new FragmentStatePagerAdapter(((AppCompatActivity) getActivity()).getSupportFragmentManager()) {
                    @Override
                    public androidx.fragment.app.Fragment getItem(int position) {
                        if (position == 0) {
                            PeopleActiveSupportFragment activePeopleFragment = new PeopleActiveSupportFragment();
                            activeRefreshable.set(activePeopleFragment);
                            return activePeopleFragment;
                        } else if (position == 1) {
                            PeopleFavoriteSupportFragment favoritePeopleFragment = new PeopleFavoriteSupportFragment();
                            favoriteRefreshable.set(favoritePeopleFragment);
                            return favoritePeopleFragment;
                        } else if (position == 2) {
                            PeopleRemovedSupportFragment removedPeopleFragment = new PeopleRemovedSupportFragment();
                            removedRefreshable.set(removedPeopleFragment);
                            return removedPeopleFragment;
                        }
                        return null;
                    }

                    @Override
                    public int getCount() {
                        return TABS.length;
                    }
                });
                viewPager.clearOnPageChangeListeners();
                viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
                    @Override
                    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                    }

                    @Override
                    public void onPageSelected(int position) {
                        final int activeTextColor = getResources().getColor(R.color.peopleTabActiveTextColor);
                        final int activeBackground = getResources().getColor(R.color.peopleTabActiveBackground);
                        final int inactiveTextColor = getResources().getColor(R.color.peopleTabInactiveTextColor);
                        final int inactiveBackground = getResources().getColor(R.color.peopleTabInactiveBackground);

                        if (position == 0) {
                            activeTab.setTextColor(activeTextColor);
                            activeTab.setBackgroundColor(activeBackground);
                            favoriteTab.setTextColor(inactiveTextColor);
                            favoriteTab.setBackgroundColor(inactiveBackground);
                            removedTab.setTextColor(inactiveTextColor);
                            removedTab.setBackgroundColor(inactiveBackground);
                            activeRefreshable.get().refreshList();
                        } else if (position == 1) {
                            activeTab.setTextColor(inactiveTextColor);
                            activeTab.setBackgroundColor(inactiveBackground);
                            favoriteTab.setTextColor(activeTextColor);
                            favoriteTab.setBackgroundColor(activeBackground);
                            removedTab.setTextColor(inactiveTextColor);
                            removedTab.setBackgroundColor(inactiveBackground);
                            favoriteRefreshable.get().refreshList();
                        } else if (position == 2) {
                            activeTab.setTextColor(inactiveTextColor);
                            activeTab.setBackgroundColor(inactiveBackground);
                            favoriteTab.setTextColor(inactiveTextColor);
                            favoriteTab.setBackgroundColor(inactiveBackground);
                            removedTab.setTextColor(activeTextColor);
                            removedTab.setBackgroundColor(activeBackground);
                            removedRefreshable.get().refreshList();
                        }
                        activeRefreshable.get().dismissActionMode();
                        favoriteRefreshable.get().dismissActionMode();
                        removedRefreshable.get().dismissActionMode();
                    }

                    @Override
                    public void onPageScrollStateChanged(int state) {
                    }
                });
                // wire up the indicator
                indicator.setViewPager(viewPager);
            }
        };

        // wire up the active, favorite, and removed tabs
        activeTab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                viewPager.setCurrentItem(0);
            }
        });
        favoriteTab.setOnClickListener(new View.OnClickListener() {
           @Override
            public void onClick(View v) {
               viewPager.setCurrentItem(1);
           }
        });
        removedTab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                viewPager.setCurrentItem(2);
            }
        });

        return view;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        Bundle bundle = getArguments();
        if (bundle != null && bundle.containsKey(Constants.PERSON_ID_BUNDLE_ARG)) {
            FragmentUtils.loadFragment(getActivity(), getActivity().getFragmentManager(), null,
                    R.id.person_card, bundle, new FragmentState(getString(R.string.nav_people)));
            showingPerson = true;
        } else {
            showingPerson = false;
        }
        mCreated = true;
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        inflater.inflate(R.menu.people_menu, menu);
        MenuItem searchItem = menu.findItem(R.id.search);
        SearchManager searchManager =
                (SearchManager) getActivity().getSystemService(Context.SEARCH_SERVICE);
        SearchView searchView =
                (SearchView) MenuItemCompat.getActionView(searchItem);
        searchView.setSearchableInfo(
                searchManager.getSearchableInfo(getActivity().getComponentName()));
        super.onCreateOptionsMenu(menu, inflater);
    }

    @Override
    public void onPause() {
        super.onPause();
        handler.removeCallbacks(runPager);
    }

    @Override
    public void onResume() {
        super.onResume();
        if (runPager != null && mCreated && !showingPerson) {
            handler.post(runPager);
        }
        AnalyticsUtils.sendScreenViewHit(this.getActivity(), "PeopleScreen");
    }
} public class DailyNotificationReceiver extends BroadcastReceiver {
    private static final String TAG = DailyNotificationReceiver.class.getSimpleName();

    @Override
    public void onReceive(Context context, Intent intent) {
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
        boolean showNotifications = preferences.getBoolean("showNotifications", true);
        AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);

        PendingIntent pendingIntent;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            Log.d(TAG, "Using a broadcast receiver to show notification");
            Intent notificationIntent = new Intent(context, ShowNotificationReceiver.class);
            pendingIntent = PendingIntent.getBroadcast(context, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
        } else {
            // TODO cleanup this code path.
            // We should be using getBroadcast instead of kicking off a service to show notifications anyway.
            // However, we keep this here to ensure notifications on older versions of the app continue to
            // function. It's tricky to test
            Intent notificationIntent = new Intent(context, DailyNotificationService.class);
            pendingIntent = PendingIntent.getService(context, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
        }
        // always clear existing repeating alarm before we set a new one.
        alarmManager.cancel(pendingIntent);

        if (showNotifications) {
            String notificationTime = preferences.getString("notificationTime", "08:00");
            Calendar firingCal = getNotificationFiringCalendar(notificationTime);

            Log.d(TAG, "Setting daily notification alarm for time: " + notificationTime);

            long intendedTime = firingCal.getTimeInMillis();
            Calendar currentCal = Calendar.getInstance();
            long currentTime = currentCal.getTimeInMillis();

            if (intendedTime >= currentTime) {
                // you can add buffer time too here to ignore some small differences in milliseconds
                Log.d(TAG, "setting alarm for same day.");
                setNotificationAlarm(pendingIntent, alarmManager, intendedTime);

            } else {
                // set from next day
                // you might consider using calendar.add() for adding one day to the current day
                Log.d(TAG, "Setting alarm for next day");
                firingCal.add(Calendar.DAY_OF_MONTH, 1);
                intendedTime = firingCal.getTimeInMillis();
                setNotificationAlarm(pendingIntent, alarmManager, intendedTime);
            }
        } else {
            Log.d(TAG, "No repeating alarm set since notifications are off.");
        }
    }

    private void setNotificationAlarm(PendingIntent pendingIntent, AlarmManager alarmManager, long intendedTime) {

        Log.d(TAG, "Setting intended time to " + intendedTime);
        alarmManager.setRepeating(AlarmManager.RTC_WAKEUP,
                intendedTime, AlarmManager.INTERVAL_DAY,
                pendingIntent);
    }

    @NonNull
    private Calendar getNotificationFiringCalendar(String time) {
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.MILLISECOND, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MINUTE, TimePickerDialogPreference.getMinute(time));
        calendar.set(Calendar.HOUR_OF_DAY, TimePickerDialogPreference.getHour(time));
//        calendar.set(Calendar.MINUTE, calendar.get(Calendar.MINUTE) + 1);
        return calendar;
    }
} public class PersonManagerImpl implements PersonManager {
    private static final String TAG = PersonManagerImpl.class.getSimpleName();
    private static final String CONTACTS_SOURCE = "Contacts";
    private static final int RANDOM_FAVORITE_THRESHOLD = 7;
    private static final int RANDOM_SAMPLE_POST_METRICS = 2;

    private static PersonManager instance;
    private Activity activity;
    private Realm realm;
    private ContentResolver contentResolver;
    private CacheManager cacheManager;

    private PersonManagerImpl(Activity activity) {
        this.activity = activity;
        this.realm = Realm.getDefaultInstance();
        this.contentResolver = this.activity.getContentResolver();
        this.cacheManager = LocalDailyCacheManagerImpl.getInstance(activity);
    }

    public static PersonManager getInstance(Activity activity) {
        if (instance == null) {
            instance = new PersonManagerImpl(activity);
        }
        return instance;
    }

    @Override
    public RealmList<Person> getPersonFromPersonPOJO(List<PersonPOJO> people) {
        RealmList<Person> listOfPersons = new RealmList<>();

        if (people == null || people.size() == 0) {
            return listOfPersons;
        }

        RealmQuery<Person> query = realm.where(Person.class)
                .equalTo(Person.Column.ID, people.get(0).getId());

        for (int i = 1; i < people.size(); i++) {
            query = query.or().equalTo(Person.Column.ID, people.get(i).getId());
        }

        RealmResults<Person> results = query.findAll();
        for (int i = 0; i < results.size(); i++) {
            listOfPersons.add(results.get(i));
        }

        return listOfPersons;
    }

    @Override
    public List<PersonPOJO> getNextPeopleToPrayFor(int n) throws AlreadyPrayedForAllContactsException {
        List<PersonPOJO> peopleToPrayFor = new ArrayList<>();

        // Get all people who haven't been prayed for, sorted by last_prayed
        RealmResults<Person> results = realm.where(Person.class)
                .equalTo(Person.Column.ACTIVE, true)
                .equalTo(Person.Column.IGNORED, false)
                .equalTo(Person.Column.PRAYED, false)
                .sort(Person.Column.LAST_PRAYED)
                .findAll();
        handleAllPrayedFor(results); // resets all the prayed flags and
                                     // throws AlreadyPrayedForAllContactsException when needed
        // We still have people available to be prayed for
        List<Person> peopleAvailableToBePrayedFor = getShuffledListOfAllPeople(results);
        if (peopleAvailableToBePrayedFor.size() < 1) {
            return peopleToPrayFor;
        }

        // Add preselectedPerson to prayer list if available
        Person preselectedPerson = loadPreselectedPerson();
        if (preselectedPerson != null) {
            selectPerson(preselectedPerson);
            peopleToPrayFor.add(getPerson(preselectedPerson.getId()));
            peopleAvailableToBePrayedFor.remove(preselectedPerson);
        }

        // Add more people to pray for (until we run out of unprayed people or have enough)
        Integer numToSelect = Math.min(n - peopleToPrayFor.size(),
                peopleAvailableToBePrayedFor.size());
        int personIndex = 0;
        for (int i = 0; i < numToSelect; i++) {
            Person person = peopleAvailableToBePrayedFor.get(personIndex);
            // Select this person if they haven't been chosen yet
            if (!peopleToPrayFor.contains(getPerson(person.getId()))) {
                selectPerson(person);
                peopleToPrayFor.add(getPerson(person.getId()));
                peopleAvailableToBePrayedFor.remove(person);
                personIndex--;
            }
            personIndex++;
        }

        if (peopleAvailableToBePrayedFor.size() > 0) {
            preselectPerson(peopleAvailableToBePrayedFor);
        }
        return peopleToPrayFor;
    }

    @NonNull
    private List<Person> getShuffledListOfAllPeople(RealmResults<Person> results) {
        // shuffle the list of people
        List<Person> allPeople = new ArrayList<>();
        for (int i = 0; i < results.size(); i++) {
            allPeople.add(results.get(i));
        }
        Collections.shuffle(allPeople);
        return allPeople;
    }

    private void handleAllPrayedFor(RealmResults<Person> results) throws AlreadyPrayedForAllContactsException {
        // if all people are prayed for, then reset and throw exception
        if (getNumPeople() > 0 && results.size() == 0) {
            // Reset all the prayed flags
            realm.beginTransaction();
            RealmResults<Person> resultsToReset = realm.where(Person.class)
                    .equalTo(Person.Column.ACTIVE, true)
                    .equalTo(Person.Column.IGNORED, false)
                    .findAll();
            for (int i = 0; i < resultsToReset.size(); i++) {
                resultsToReset.get(i).setPrayed(false);
            }
            realm.commitTransaction();
            // Throw Exception
            throw new AlreadyPrayedForAllContactsException(getNumPeople());
        }
    }

    private Person selectFavoritedPerson() {
        RealmResults<Person> favoritedPeople = realm.where(Person.class)
                .equalTo(Person.Column.ACTIVE, true)
                .equalTo(Person.Column.FAVORITE, true)
                .equalTo(Person.Column.IGNORED, false)
                .sort(Person.Column.LAST_PRAYED)
                .findAll();

        if (favoritedPeople.size() > 0) {
            if (favoritedPeople.size() < RANDOM_FAVORITE_THRESHOLD) {
                Random random = new Random();
                if (random.nextInt(RANDOM_FAVORITE_THRESHOLD) == 0) {
                    return favoritedPeople.get(0);
                }
            } else {
                // always show a favorited person if they have
                // favorited more than 7 people
                return favoritedPeople.get(0);
            }
        }

        return null;
    }

    private void preselectPerson(List<Person> allPeople) {
        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(activity);
        Person theChosenOne = selectFavoritedPerson();
        if (theChosenOne == null) {
            theChosenOne = allPeople.get(0);
        }
        sp.edit()
                .putString(Constants.PRESELECTED_PERSON_ID, theChosenOne.getId())
                .putString(Constants.PRESELECTED_PERSON_NAME, theChosenOne.getName())
                .apply();
    }

    private Person loadPreselectedPerson() {
        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(activity);
        String personId = sp.getString(Constants.PRESELECTED_PERSON_ID, null);
        if (personId == null) {
            return null;
        }
        return getRealmPerson(personId);
    }

    private void selectPerson(Person person) {
        realm.beginTransaction();
        person.setLastPrayed(new Date());
        person.setPrayed(true);
        realm.commitTransaction();
        Log.d(TAG, "Selecting person " + person);
    }

    @Override
    public List<PersonPOJO> getActivePeople() {
        return RealmUtils.toPersonPOJOs(realm.where(Person.class)
                .equalTo(Person.Column.ACTIVE, true)
                .equalTo(Person.Column.IGNORED, false)
                .sort(Person.Column.NAME)
                .findAll());
    }

    @Override
    public List<PersonPOJO> getFavoritePeople() {
        return RealmUtils.toPersonPOJOs(realm.where(Person.class)
                .equalTo(Person.Column.ACTIVE, true)
                .equalTo(Person.Column.FAVORITE, true)
                .equalTo(Person.Column.IGNORED, false)
                .sort(Person.Column.NAME)
                .findAll());
    }

    @Override
    public List<PersonPOJO> getRemovedPeople() {
        return RealmUtils.toPersonPOJOs(realm.where(Person.class)
                .equalTo(Person.Column.ACTIVE, true)
                .equalTo(Person.Column.IGNORED, true)
                .sort(Person.Column.NAME)
                .findAll());
    }

    @Override
    public long getNumPrayed() {
        return realm.where(Person.class)
                .equalTo(Person.Column.ACTIVE, true)
                .equalTo(Person.Column.IGNORED, false)
                .equalTo(Person.Column.PRAYED, true)
                .count();
    }

    @Override
    public long getNumPeople() {
        return realm.where(Person.class)
                .equalTo(Person.Column.ACTIVE, true)
                .equalTo(Person.Column.IGNORED, false)
                .count();
    }

    @Override
    public long getNumFavoritedPeople() {
        return realm.where(Person.class)
                .equalTo(Person.Column.ACTIVE, true)
                .equalTo(Person.Column.IGNORED, false)
                .equalTo(Person.Column.FAVORITE, true)
                .count();
    }

    @Override
    public long getNumRemovedPeople() {
        return realm.where(Person.class)
                .equalTo(Person.Column.ACTIVE, true)
                .equalTo(Person.Column.IGNORED, true)
                .count();
    }

    @Override
    public PersonPOJO getPerson(String personId) {
        return RealmUtils.toPersonPOJO(getRealmPerson(personId));
    }

    @Override
    public Person getRealmPerson(String personId) {
        return realm.where(Person.class)
                .equalTo(Person.Column.ID, personId)
                .findFirst();
    }

    @Override
    public void ignorePerson(String personId) {
        realm.beginTransaction();
        Person person = getRealmPerson(personId);
        person.setIgnored(true);
        person.setFavorite(false);
        realm.commitTransaction();
    }

    @Override
    public void unignorePerson(String personId) {
        realm.beginTransaction();
        getRealmPerson(personId).setIgnored(false);
        realm.commitTransaction();
    }

    @Override
    public void favoritePerson(String personId) {
        realm.beginTransaction();
        getRealmPerson(personId).setFavorite(true);
        realm.commitTransaction();
    }

    @Override
    public void unfavoritePerson(String personId) {
        realm.beginTransaction();
        getRealmPerson(personId).setFavorite(false);
        realm.commitTransaction();
    }

    public Realm getRealm() {
        return realm;
    }

    public void setRealm(Realm realm) {
        this.realm = realm;
    }

    @Override
    public void populateContacts() {
        Cursor cursor = null;
        realm.beginTransaction();

        int added = 0;
        int updated = 0;
        List<String> ids = new ArrayList<>();
        List<Person> people = new ArrayList<>();

        try {
            cursor = contentResolver.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);

            while (cursor.moveToNext()) {
                if (isValidContact(cursor)) {
                    String id = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID));
                    ids.add(id);
                    String name = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
                    Log.v(TAG, String.format("Person: id=%s name=%s", id, name));

                    Person person = realm.where(Person.class)
                            .equalTo(Person.Column.ID, id)
                            .findFirst();

                    if (person == null) {

                        person = realm.createObject(Person.class, id);
                        person.setName(name);
                        person.setSource(CONTACTS_SOURCE);
                        person.setActive(true);
                        person.setIgnored(false);
                        person.setLastPrayed(new Date(0L));

                        // auto-favorite contacts that were starred by user.
                        boolean starred = cursor.getInt(cursor.getColumnIndex(ContactsContract.Contacts.STARRED)) == 1;
                        person.setFavorite(starred);

                        ++added;
                    } else {
                        Log.v(TAG, "User already existed, updating information.");
                        // Update the name in case the user updates it
                        person.setName(name);
                        person.setActive(true);
                        ++updated;
                    }
                    people.add(person);
                }
            }
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }

        // Clean up contacts whose ids have now changed
        // Copy their contents to an existing contact if there is a matching one.
        RealmResults<Person> fullList = realm.where(Person.class)
                .equalTo(Person.Column.ACTIVE, true)
                .findAll();

        List<Pair<String, Person>> peopleWithUpdatedIds = new ArrayList<>();

        for (int i = 0; i < fullList.size(); i++) {
            Person p = fullList.get(i);
            if (!ids.contains(p.getId())) {
                Log.v(TAG, "Contact with id " + p.getId() + " no longer exists.");
                Person matchingContact = findPersonWithName(p.getName(), people);
                if (matchingContact != null) {
                    Log.v(TAG, "A contact with name " + p.getName() + " exists. Merging details.");
                    copyContact(p, matchingContact);
                    // update daily cache with new ids if any persons selected for today are affected
                    peopleWithUpdatedIds.add(Pair.create(p.getId(), matchingContact));
                    // cleanup the obsolete contact (must be after we've update the cache)
                    p.deleteFromRealm();
                } else {
                    Log.v(TAG, "Marking this contact inactive because it no longer exists on the phone.");
                    p.setActive(false);
                    peopleWithUpdatedIds.add(Pair.create(p.getId(), (Person) null));
                }
            }
        }

        realm.commitTransaction();

        // we need to update the cache in a separate transaction
        for (Pair<String, Person> p : peopleWithUpdatedIds) {
            updateCachedPersonIds(p.first, p.second);
        }

        Log.d(TAG, String.format("Successfully added %d and updated %d contacts.", added, updated));
        sampleAndPostMetrics();
    }

    private void updateCachedPersonIds(String oldContactId, Person newContact) {
        List<String> personIds = cacheManager.getCachedPersonIdsToPrayFor();
        if (personIds != null && personIds.contains(oldContactId)) {
            Log.i(TAG, "Updating a selected contact in daily cache.");
            personIds.remove(oldContactId);
            if (newContact != null) {
                personIds.add(newContact.getId());
            }
            cacheManager.cachePersonIdsToPrayFor(personIds);
        }
    }

    private Person findPersonWithName(String name, List<Person> people) {
        for (Person p : people) {
            if (p.getName().equals(name)) {
                return p;
            }
        }
        return null;
    }

    private void copyContact(Person src, Person dst) {
        // do not copy id or name.
        // copy over everything else
        dst.setActive(src.isActive());
        dst.setFavorite(src.isFavorite());
        dst.setIgnored(src.isIgnored());
        dst.setLastPrayed(src.getLastPrayed());
        dst.setPrayed(src.isPrayed());
        dst.setSource(src.getSource());

        transferNotes(src, dst);

        // TODO potentially update the Cache
        // so all ids that pointed to the old contact, point to the new.
    }

    private void transferNotes(Person src, Person dst) {
        // transfer notes over
        RealmList<Note> notes = src.getNotes();
        for (Note n : notes) {
            RealmList<Person> peopleTagged = n.getPeopleTagged();
            RealmList<Person> newPeopleTagged = new RealmList<>();
            Iterator<Person> i = peopleTagged.iterator();
            // remove the old version of this person on the note
            while (i.hasNext()) {
                Person p = i.next();
                if (p.getId().equals(src.getId())) {
                    // add the new version of this person on the note
                    newPeopleTagged.add(dst);
                } else {
                    newPeopleTagged.add(p);
                }
            }
            n.setPeopleTagged(newPeopleTagged);
        }
        dst.setNotes(src.getNotes());
    }

    @Override
    public List<PersonPOJO> queryPeopleByName(String query) {
        List<PersonPOJO> people = RealmUtils.toPersonPOJOs(realm.where(Person.class)
                .contains(Person.Column.NAME, query, Case.INSENSITIVE)
                .equalTo(Person.Column.ACTIVE, true)
                .sort(Person.Column.NAME)
                .findAll());
        return people;
    }

    private boolean isValidContact(Cursor cursor) {
        boolean hasPhoneNumber = cursor.getInt(cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER)) == 1;
        String name = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
        return hasPhoneNumber && !name.startsWith("#") && !name.startsWith("+") && contactNameFilter(name);
    }

    private boolean contactNameFilter(String name) {

        if (name == null || name.isEmpty()) {
            return false;
        }

        if (Character.isDigit(name.charAt(0))) {
            // if the name starts with a number ignore it.
            return false;
        }

        if (android.util.Patterns.EMAIL_ADDRESS.matcher(name).matches()) {
            return false;
        }

        List<String> blacklist = Arrays.asList(activity.getResources().getStringArray(R.array.contact_name_blacklist));
        for (String s : blacklist) {
            // match the name to the blacklist elements
            // since the argument to matches is a regular expression
            // note that we can also use Pattern.quote to not treat it as a regular expression.
            // we do it this way since in the future our blacklist may want to consist in regular expressions
            if (name.matches(s)) {
                return false;
            }
        }

        return true;
    }

    private void sampleAndPostMetrics() {
        Random random = new Random();
        if (random.nextInt(RANDOM_SAMPLE_POST_METRICS) == 0) {
            Log.i(TAG, "Posting contact metrics for analytics");
            String installationId = Installation.id(activity);
            AnalyticsUtils.sendEventWithCategoryAndValue(activity,
                    getString(R.string.ga_address_book_sync),
                    getString(R.string.ga_post_total_active_contacts),
                    installationId,
                    getNumPeople());

            AnalyticsUtils.sendEventWithCategoryAndValue(activity,
                    getString(R.string.ga_address_book_sync),
                    getString(R.string.ga_post_total_favorited),
                    installationId,
                    getNumFavoritedPeople());

            AnalyticsUtils.sendEventWithCategoryAndValue(activity,
                    getString(R.string.ga_address_book_sync),
                    getString(R.string.ga_post_total_removed_contacts),
                    installationId,
                    getNumRemovedPeople());

            AnalyticsUtils.sendEventWithCategoryAndValue(activity,
                    getString(R.string.ga_prayer_progress),
                    getString(R.string.ga_post_total_prayed_for),
                    installationId,
                    getNumPrayed());
        }
    }

    private String getString(int resId) {
        return this.activity.getString(resId);
    }

} public class HomeTutorialFragment extends Fragment {
    private static final String TAG = HomeTutorialFragment.class.getSimpleName();
    private static final Integer CARD_COUNT = 3;
    @BindView(R.id.tutorial_viewpager)
    ViewPager viewPager;
    @BindView(R.id.tutorial_indicator)
    CirclePageIndicator indicator;

    private FragmentStateListener mListener;

    public HomeTutorialFragment() {
        // Required empty public constructor
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setHasOptionsMenu(true);
        // notify fragment state
        try {
            mListener = (FragmentStateListener) getActivity();
        } catch (ClassCastException e) {
            throw new ClassCastException(getActivity().toString() + " must implement FragmentStateListener");
        }
        Bundle currentState = new Bundle();
        mListener.notify(new FragmentState(getString(R.string.nav_home_tutorial), currentState));

    }

    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                     Bundle savedInstanceState) {

        // set title
        String title = getString(R.string.nav_home_tutorial);
        getActivity().setTitle(title);

        // create view
        View view = inflater.inflate(R.layout.fragment_home_tutorial, container, false);
        ButterKnife.bind(this, view);

       final FragmentStatePagerAdapter pagerAdapter =
               new FragmentStatePagerAdapter(((AppCompatActivity) getActivity()).getSupportFragmentManager()) {
                   List<androidx.fragment.app.Fragment> pages = Arrays.asList(new androidx.fragment.app.Fragment[CARD_COUNT]);

                   @Override
                   public androidx.fragment.app.Fragment getItem(int i) {
                       if (pages.get(i) == null) {
                           switch (i) {
                               case 0:
                                   pages.set(i, HTFDemoScriptureFragment.newInstance(viewPager));
                                   break;
                               case 1:
                                   pages.set(i, HTFDemoPersonFragment.newInstance(getString(R.string.tutorial_person_name), true));
                                   break;
                               case 2:
                                   pages.set(i, new HTFDemoProgressFragment());
                                   break;
                               default:
                                   pages.set(i, new BlankSupportFragment());
                                   break;
                           }
                       }
                       return pages.get(i);
                   }

                   @Override
                   public int getCount() {
                       return CARD_COUNT;
                   }
               };

        viewPager.setAdapter(pagerAdapter);
        viewPager.setPageTransformer(true, new ZoomOutPageTransformer());
        viewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
            @Override
            public void onPageSelected(int i) {
                ((HTFDemoFragment) pagerAdapter.getItem(i)).onSelected();
            }
        });
        indicator.setViewPager(viewPager);
        return view;
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {

        inflater.inflate(R.menu.menu, menu);
        MenuItem search = menu.findItem(R.id.search);
        MenuItem addNote = menu.findItem(R.id.person_add_note);
        MenuItem skipTutorial = menu.findItem(R.id.skip_tutorial);
        search.setVisible(false);
        addNote.setVisible(false);
        skipTutorial.setVisible(true);
        skipTutorial.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem item) {
                ((MainActivity) getActivity()).loadMainFragment();
                return false;
            }
        });

        super.onCreateOptionsMenu(menu, inflater);
    }


} public class Tutorial {
    private static Tutorial ourInstance = new Tutorial();
    private static final String LAST_ACCESSED = "Tutorial_Last_Accessed_Date";
    private static final boolean ALWAYS_SHOW_TUTORIAL = false;

    public static Tutorial getInstance() {
        return ourInstance;
    }

    private Tutorial() {
    }

    /**
     * @param activity the context needed to get preferences
     * @return whether or not the tutorial should be shown
     */
    public static boolean shouldShowTutorial(Activity activity) {
        boolean result;

        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
        SharedPreferences.Editor editor = preferences.edit();

        result = ALWAYS_SHOW_TUTORIAL || (preferences.getLong(LAST_ACCESSED, 0) == 0);
        editor.putLong(LAST_ACCESSED, System.currentTimeMillis());
        editor.apply();

        return result;
    }
} public class CommonUtils {

    private static final String TAG = CommonUtils.class.getSimpleName();

    public static Uri getContactUri(String contactId) {
        return ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, Long.parseLong(contactId));
    }

    public static void injectPersonIntoView(final Activity activity, final PersonManager personManager, TextView personName,
                                            RoundedImageView personImage, ListView notes, View view, final String personId,
                                            String emptyNotesMessage, final FragmentManager fragmentManager, final FragmentState backStackInfo) {
        PersonPOJO personPOJO = personManager.getPerson(personId);
        if (null == personPOJO){
            return;
        }
        Uri contactUri = getContactUri(personPOJO.getId());

        // display name and picture
        personName.setText(personPOJO.getName());
        Picasso.with(activity).load(contactUri).placeholder(R.drawable.placeholder_user)
                .fit().centerInside().into(personImage);

        TextView removedLabel = (TextView) view.findViewById(R.id.person_removed_label);
        if (personPOJO.isIgnored()) {
            removedLabel.setVisibility(View.VISIBLE);
        } else {
            removedLabel.setVisibility(View.INVISIBLE);
        }

        // display notes
        final List<NotePOJO> notePOJOs = personPOJO.getNotes();
        Collections.sort(notePOJOs, new Comparator<NotePOJO>() { // sort by latest first
            @Override
            public int compare(NotePOJO lhs, NotePOJO rhs) {
                return -1 * lhs.getLastUpdatedDate().compareTo(rhs.getLastUpdatedDate());
            }
        });
        if (notePOJOs.isEmpty()) {
            ListView emptyNotes = (ListView) view.findViewById(R.id.empty_person_notes);
            emptyNotes.setAdapter(new ArrayAdapter<>(activity, R.layout.list_item_empty_notes, new String[]{emptyNotesMessage}));
            emptyNotes.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                    loadAddNote(personId, activity, fragmentManager, backStackInfo);
                }
            });
        } else {
            notes.setAdapter(new PersonNotesArrayAdapter(activity, notePOJOs));
            notes.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                    Log.d(TAG, "item has been clicked");
                    Bundle bundle = new Bundle();
                    bundle.putString(Constants.NOTE_ID_BUNDLE_ARG, notePOJOs.get(position).getId());
                    FragmentUtils.loadFragment(activity, activity.getFragmentManager(), null,
                            R.id.add_note_fragment, bundle, backStackInfo);
                }
            });
        }
    }

    public static void wireFavoriteShortcut(final Activity activity, final View view, final String personId, final PersonManager personManager) {
        PersonPOJO personPOJO = personManager.getPerson(personId);
        final IconTextView favorite = (IconTextView) view.findViewById(R.id.favorite_btn);
        final String favoriteOn = "{fa-heart}";
        final String favoriteOff = activity.getString(R.string.favorite_off);

        // favorite
        if (personPOJO.isFavorite()) {
            favorite.setText(favoriteOn);
        } else {
            favorite.setText(favoriteOff);
        }
        favorite.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                PersonPOJO updatedPerson = personManager.getPerson(personId);
                if (updatedPerson.isFavorite()) {
                    personManager.unfavoritePerson(updatedPerson.getId());
                    favorite.setText(favoriteOff);
                } else {
                    personManager.favoritePerson(updatedPerson.getId());
                    favorite.setText(favoriteOn);
                }
            }
        });
    }

    public static void wireSendMessage(final Activity activity, View view, final String personId) {
        final IconTextView messageShortcut = (IconTextView) view.findViewById(R.id.message_btn);
        messageShortcut.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                AnalyticsUtils.sendEventWithCategory(activity,
                        activity.getString(R.string.ga_person_card_actions),
                        activity.getString(R.string.ga_tapped_send_message),
                        Installation.id(activity));
                Uri contactUri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, Long.parseLong(personId));
                ContactsContract.QuickContact.showQuickContact(activity, messageShortcut, contactUri, ContactsContract.QuickContact.MODE_MEDIUM, null);
            }
        });
    }

    public static void wireInvitePerson(final Activity activity, View view, final String personId) {
        final IconTextView inviteShortcut = (IconTextView) view.findViewById(R.id.invite_btn);
        inviteShortcut.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                AnalyticsUtils.sendEventWithCategory(activity,
                        activity.getString(R.string.ga_person_card_actions),
                        activity.getString(R.string.ga_tapped_invite),
                        Installation.id(activity));
                Uri contactUri1 = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, Long.parseLong(personId));
                ContactsContract.QuickContact.showQuickContact(activity, v, contactUri1, ContactsContract.QuickContact.MODE_MEDIUM, null);
            }
        });
    }

    public static void wireDeletePerson(final Activity activity, View view, final String personId, final PersonManager personManager) {
        PersonPOJO personPOJO = personManager.getPerson(personId);
        final IconTextView ignore = (IconTextView) view.findViewById(R.id.delete_btn);
        final TextView removedLabel = (TextView) view.findViewById(R.id.person_removed_label);
        final String ignoreOn = "{fa-trash}";
        final String ignoreOff = "{fa-trash-o}";

        // favorite
        if (personPOJO.isIgnored()) {
            ignore.setText(ignoreOn);
        } else {
            ignore.setText(ignoreOff);
        }
        ignore.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                PersonPOJO updatedPerson = personManager.getPerson(personId);
                if (updatedPerson.isIgnored()) {
                    personManager.unignorePerson(updatedPerson.getId());
                    ignore.setText(ignoreOff);
                    removedLabel.setVisibility(View.INVISIBLE);
                } else {
                    personManager.ignorePerson(updatedPerson.getId());
                    ignore.setText(ignoreOn);
                    removedLabel.setVisibility(View.VISIBLE);
                }
            }
        });
    }

    // wire the add note icon
    public static void wireAddNote(final View view, final String personId, final Activity activity, final FragmentState backStackInfo) {
        view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                AnalyticsUtils.sendEventWithCategory(activity,
                        activity.getString(R.string.ga_person_card_actions),
                        activity.getString(R.string.ga_tapped_add_note),
                        Installation.id(activity));
                CommonUtils.loadAddNote(personId, activity, activity.getFragmentManager(), backStackInfo);
            }
        });
    }

    public static void loadAddNote(String personId, Activity activity, FragmentManager fragmentManager, FragmentState backStackInfo) {
        Bundle addNoteBundle = new Bundle();
        addNoteBundle.putString(Constants.PERSON_ID_BUNDLE_ARG, personId);
        FragmentUtils.loadFragment(activity, fragmentManager, null, R.id.person_add_note, addNoteBundle, backStackInfo);
    }

    public static void wireShowPersonMenu(View view, final String personId, final Activity activity, final FragmentState backStackInfo, final PersonManager personManager) {
        final ImageView personImage = (ImageView) view.findViewById(R.id.person_image);
        final IconTextView favorite = (IconTextView) view.findViewById(R.id.favorite_btn);
        final TextView removedLabel = (TextView) view.findViewById(R.id.person_removed_label);

        personImage.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(final View v) {
                PopupMenu popup = new PopupMenu(activity, v);
                popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
                    @Override
                    public boolean onMenuItemClick(MenuItem item) {
                        int id = item.getItemId();
                        switch (id) {
                            case R.id.person_fragment_add_note:
                                AnalyticsUtils.sendEventWithCategory(activity,
                                        activity.getString(R.string.ga_person_card_actions),
                                        activity.getString(R.string.ga_tapped_add_note),
                                        Installation.id(activity));
                                CommonUtils.loadAddNote(personId, activity, activity.getFragmentManager(), backStackInfo);
                                return true;
                            case R.id.person_fragment_remove:
                                personManager.ignorePerson(personId);
                                removedLabel.setVisibility(View.VISIBLE);
                                return true;
                            case R.id.person_fragment_add:
                                personManager.unignorePerson(personId);
                                removedLabel.setVisibility(View.INVISIBLE);
                                return true;
                            case R.id.person_fragment_send_message:
                                AnalyticsUtils.sendEventWithCategory(activity,
                                        activity.getString(R.string.ga_person_card_actions),
                                        activity.getString(R.string.ga_tapped_send_message),
                                        Installation.id(activity));
                                Uri contactUri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, Long.parseLong(personId));
                                ContactsContract.QuickContact.showQuickContact(activity, v, contactUri, ContactsContract.QuickContact.MODE_MEDIUM, null);
                                return true;
                            case R.id.person_fragment_invite:
                                AnalyticsUtils.sendEventWithCategory(activity,
                                        activity.getString(R.string.ga_person_card_actions),
                                        activity.getString(R.string.ga_tapped_invite),
                                        Installation.id(activity));
                                Uri contactUri1 = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, Long.parseLong(personId));
                                ContactsContract.QuickContact.showQuickContact(activity, v, contactUri1, ContactsContract.QuickContact.MODE_MEDIUM, null);
                                return true;
                            case R.id.person_fragment_favorite:
                                favorite.setText(activity.getString(R.string.favorite_on));
                                personManager.favoritePerson(personId);
                                return true;
                            case R.id.person_fragment_unfavorite:
                                favorite.setText(activity.getString(R.string.favorite_off));
                                personManager.unfavoritePerson(personId);
                                return true;
                            default:
                                return false;
                        }
                    }
                });
                popup.inflate(R.menu.person_menu);
                Menu menu = popup.getMenu();
                PersonPOJO updatedPerson = personManager.getPerson(personId);

                if (updatedPerson.isIgnored()) {
                    menu.removeItem(R.id.person_fragment_remove);
                } else {
                    menu.removeItem(R.id.person_fragment_add);
                }

                if (updatedPerson.isFavorite()) {
                    menu.removeItem(R.id.person_fragment_favorite);
                } else {
                    menu.removeItem(R.id.person_fragment_unfavorite);
                }

                popup.show();
            }
        });
    }

    public static void setDynamicImage(Context context, ImageView target) {
        File currentBackgroundImage = new File(context.getCacheDir(), Constants.CURRENT_BACKGROUND_IMAGE);
        List<Transformation> transformations = new ArrayList<>();
        transformations.add(new BlurTransformation(context, 25, 4));

        if (currentBackgroundImage.exists()) {
            Picasso.with(context).load(currentBackgroundImage)
                    .placeholder(R.drawable.placeholder_rectangle_scene)
                    .fit()
                    .centerCrop()
                    .transform(transformations)
                    .into(target);
        } else {
            Picasso.with(context).load(R.drawable.at_the_beach)
                    .placeholder(R.drawable.placeholder_rectangle_scene)
                    .fit()
                    .centerCrop()
                    .transform(transformations)
                    .into(target);
        }
    }

    public static void setupBackgroundImage(Context context, ImageView target) {
        File currentBackgroundImage = new File(context.getCacheDir(), Constants.CURRENT_BACKGROUND_IMAGE);
        if (currentBackgroundImage.exists()) {
            Picasso.with(context)
                    .load(currentBackgroundImage)
                    .transform(new BlurTransformation(context, 25, 3))
                    .into(target);
            Log.d(TAG, "Background image has been set to " + currentBackgroundImage);
        } else {
            Log.d(TAG, "Showing default background image");
            Picasso.with(context)
                    .load(R.drawable.at_the_beach)
                    .transform(new BlurTransformation(context, 25, 3))
                    .into(target);
        }
    }

    private static class PersonNotesArrayAdapter extends ArrayAdapter<NotePOJO> {
        private final Context context;
        private final List<NotePOJO> notes;
        private final LayoutInflater inflater;

        public PersonNotesArrayAdapter(Context context, List<NotePOJO> notes) {
            super(context, -1, notes);
            this.context = context;
            this.notes = notes;
            this.inflater = LayoutInflater.from(context);
        }

        @Override
        public View getView(int position, View view, ViewGroup parent) {
            final ViewHolder holder;
            if (view == null) {
                view = inflater.inflate(R.layout.list_item_person_notes, parent, false);
                holder = new ViewHolder();
                holder.noteDate = (TextView) view.findViewById(R.id.person_note_date);
                holder.noteText = (TextView) view.findViewById(R.id.person_note_text);
                view.setTag(holder);
            } else {
                holder = (ViewHolder) view.getTag();
            }
            NotePOJO note = notes.get(position);
            DateFormat formatter = SimpleDateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.SHORT);
            holder.noteDate.setText(formatter.format(note.getLastUpdatedDate()));
            holder.noteText.setText(note.getText());
            return view;
        }

        private class ViewHolder {
            TextView noteDate;
            TextView noteText;
        }
    }

    public static void hideKeyboard(Activity activity) {
        // hide keyboard if it's open
        View view = activity.getCurrentFocus();
        if (view != null) {
            InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
            imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
        }
    }

} public class Constants {
    public static final int NUM_AUXILIARY_CARDS = 2;
    public static final int SCHEMA_VERSION = 0;
    public static final String REALM_FILE_NAME = "org.theotech.ceaselessandroid";

    public static final String HOME_SECTION_NUMBER_BUNDLE_ARG = "homeSectionNumber";
    public static final String USE_CACHE_BUNDLE_ARG = "useCache";
    public static final String PERSON_ID_BUNDLE_ARG = "personId";
    public static final String NOTE_ID_BUNDLE_ARG = "noteId";
    public static final String NEXT_BACKGROUND_IMAGE = "nextBackgroundImage";
    public static final String CURRENT_BACKGROUND_IMAGE = "currentBackgroundImage";
    public static final String PRESELECTED_PERSON_NAME = "preselectedPersonName";
    public static final String PRESELECTED_PERSON_ID = "preselectedPersonId";

    public static final String SHOW_PERSON_INTENT = "org.theotech.ceaselessandroid.SHOW_PERSON";
    public static final String SHOW_NOTE_INTENT = "org.theotech.ceaselessandroid.SHOW_NOTE";
    public static final String DEFAULT_PREFERENCES_FILE = "org.theotech.ceaselessandroid_preferences";

    public static final String NUMBER_OF_PEOPLE_TO_PRAY_FOR = "numberOfPeopleToPrayForDaily";

    public static final String DEMO_NAME_BUNDLE_ARG = "demoName";
    public static final String DEMO_TOOLTIP_BUNDLE_ARG = "demoShowToolTip";

    public static final String DEFAULT_CEASELESS_CHANNEL_ID = "ceaselessPrayerReminders";
} public class FragmentUtils {
    private static final String TAG = FragmentUtils.class.getSimpleName();

    public static void loadFragment(Activity activity, FragmentManager fragmentManager, NavigationView navigation,
                                    int resourceId) {
        loadFragment(activity, fragmentManager, navigation, resourceId, (Bundle) null);
    }

    public static void loadFragment(Activity activity, FragmentManager fragmentManager, NavigationView navigation,
                                    int resourceId, Bundle loadingFragmentState /* state of the fragment being loaded */) {
        loadFragment(activity, fragmentManager, navigation, resourceId, loadingFragmentState, null);
    }

    public static void loadFragment(Activity activity, FragmentManager fragmentManager, NavigationView navigation,
                                    int resourceId, FragmentState backStackInfo) {
        loadFragment(activity, fragmentManager, navigation, resourceId, null, backStackInfo);
    }

    public static void loadFragment(Activity activity, FragmentManager fragmentManager, NavigationView navigation,
                                    int resourceId, Bundle loadingFragmentState /* state of the fragment being loaded */,
                                    FragmentState backStackInfo) {
        Fragment fragment = getFragmentForResourceId(resourceId);
        String fragmentTag = getFragmentTagForResourceId(activity, resourceId);
        Fragment fragmentForTag = fragmentManager.findFragmentByTag(fragmentTag);
        /*if(fragmentForTag == null) {
            fragmentForTag = fragmentManager.findFragmentById(resourceId);
            fragmentTag = fragmentForTag.getTag();
        }*/
        if (fragment != null && (fragmentForTag == null || !fragmentForTag.isVisible())) {
            MainActivity main = (MainActivity) activity;
            if (backStackInfo != null) {
                main.getFragmentBackStackManager().add(backStackInfo);
            }

            if (resourceId == R.id.nav_home || resourceId == R.id.show_more_people) {
                // if the user tapped Home in the navigation
                // ensure we treat it as if it were the first time the fragment
                // were created (e.g. showing the first page instead of where they left off)
                main.setHomeFragmentCreated(false);
            }

            fragment.setArguments(loadingFragmentState);
            fragmentManager.beginTransaction().replace(R.id.fragment, fragment, fragmentTag).commit();

            if (navigation != null && fragmentTag != null) {
                navigation.setCheckedItem(getNavigationItemIdForFragmentName(activity, fragmentTag));
            }
            activity.setTitle(fragmentTag);
        } else {
            Log.d(TAG, String.format("Required fragment %s already visible, not reloading", fragmentTag));
        }
    }

    private static Fragment getFragmentForResourceId(int resourceId) {
        if (resourceId == R.id.nav_home) {
            return new HomeFragment();
        } else if (resourceId == R.id.nav_people) {
            return new PeopleFragment();
        } else if (resourceId == R.id.nav_journal) {
            return new JournalFragment();
        } else if (resourceId == R.id.nav_settings) {
            return new SettingsFragment();
        } else if (resourceId == R.id.nav_help) {
            return new HelpFragment();
        } else if (resourceId == R.id.nav_contact_us) {
            return new ContactUsFragment();
        } else if (resourceId == R.id.nav_rate_this_app) {
            return new HomeFragment();
        } else if (resourceId == R.id.show_more_people) {
            return new HomeFragment();
        } else if (resourceId == R.id.person_add_note) {
            return new AddNoteFragment();
        } else if (resourceId == R.id.person_card) {
            return new PersonFragment();
        } else if (resourceId == R.id.add_note_fragment) {
            return new AddNoteFragment();
        } else if (resourceId == R.id.nav_about) {
            return new AboutFragment();
        }
        return null;
    }

    private static String getFragmentTagForResourceId(Context context, int resourceId) {
        if (resourceId == R.id.nav_home) {
            return context.getString(R.string.nav_home);
        } else if (resourceId == R.id.nav_people) {
            return context.getString(R.string.nav_people);
        } else if (resourceId == R.id.nav_journal) {
            return context.getString(R.string.nav_journal);
        } else if (resourceId == R.id.nav_settings) {
            return context.getString(R.string.nav_settings);
        } else if (resourceId == R.id.nav_help) {
            return context.getString(R.string.nav_help);
        } else if (resourceId == R.id.nav_contact_us) {
            return context.getString(R.string.nav_contact_us);
        } else if (resourceId == R.id.nav_rate_this_app) {
            return context.getString(R.string.nav_rate_this_app);
        } else if (resourceId == R.id.nav_about) {
            return context.getString(R.string.nav_about);
        } else if (resourceId == R.id.person_add_note) {
            return context.getString(R.string.person_add_note);
        //} else if (resourceId == R.id.show_more_people) {
          //  return context.getString(R.string.nav_home);
        }
        return null;
    }

    public static Integer getNavigationItemIdForFragmentName(Context context, String fragmentName) {
        if (fragmentName.equals(context.getString(R.string.nav_home))) {
            return R.id.nav_home;
        } else if (fragmentName.equals(context.getString(R.string.nav_people))) {
            return R.id.nav_people;
        } else if (fragmentName.equals(context.getString(R.string.nav_journal))) {
            return R.id.nav_journal;
        } else if (fragmentName.equals(context.getString(R.string.nav_settings))) {
            return R.id.nav_settings;
        } else if (fragmentName.equals(context.getString(R.string.nav_help))) {
            return R.id.nav_help;
        } else if (fragmentName.equals(context.getString(R.string.nav_contact_us))) {
            return R.id.nav_contact_us;
        } else if (fragmentName.equals(context.getString(R.string.nav_rate_this_app))) {
            return R.id.nav_rate_this_app;
        } else if (fragmentName.equals(context.getString(R.string.nav_about))) {
            return R.id.nav_about;
        } else if (fragmentName.equals(context.getString(R.string.person_add_note))) {
            return R.id.nav_journal;
        } else if (fragmentName.equals(context.getString(R.string.person_view))) {
            return R.id.person_card;
        } else if (fragmentName.equals(context.getString(R.string.show_more_people))) {
            return R.id.nav_home;
        }
        return null;
    }
}
import android.Manifest;
import android.app.Fragment;
import android.app.backup.BackupManager;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract;
import androidx.annotation.NonNull;
import com.google.android.material.navigation.NavigationView;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import android.util.Log;
import android.view.MenuItem;
import android.widget.ImageView;
import android.widget.Toast;
import org.codechimp.apprater.AppRater;
import org.theotech.ceaselessandroid.R;
import org.theotech.ceaselessandroid.fragment.AddNoteFragment;
import org.theotech.ceaselessandroid.fragment.FragmentBackStackManager;
import org.theotech.ceaselessandroid.fragment.FragmentState;
import org.theotech.ceaselessandroid.fragment.FragmentStateListener;
import org.theotech.ceaselessandroid.fragment.HomeFragment;
import org.theotech.ceaselessandroid.fragment.PeopleFragment;
import org.theotech.ceaselessandroid.notification.DailyNotificationReceiver;
import org.theotech.ceaselessandroid.person.PersonManagerImpl;
import org.theotech.ceaselessandroid.tutorial.HomeTutorialFragment;
import org.theotech.ceaselessandroid.tutorial.Tutorial;
import org.theotech.ceaselessandroid.util.CommonUtils;
import org.theotech.ceaselessandroid.util.Constants;
import org.theotech.ceaselessandroid.util.FragmentUtils;
import butterknife.BindView;
import butterknife.ButterKnife;
package org.theotech.ceaselessandroid.activity;




public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener,
        FragmentStateListener,
        ActivityCompat.OnRequestPermissionsResultCallback {
    private static final String TAG = MainActivity.class.getSimpleName();
    private static final int POPULATE_CONTACTS_REQUEST_CODE = 1;
    private static final int ADD_CONTACT_REQUEST_CODE = 2;
    private boolean homeFragmentCreated = false;

    @BindView(R.id.toolbar)
    Toolbar toolbar;
    @BindView(R.id.drawer_layout)
    DrawerLayout drawer;
    @BindView(R.id.nav_view)
    NavigationView navigation;
    @BindView(R.id.backgroundImageView)
    ImageView backgroundImageView;

    private FragmentBackStackManager backStackManager;
    private FragmentState currentFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (savedInstanceState != null) {
            Log.d(TAG, "reloading from savedInstanceState " + savedInstanceState);
        } else {
            Log.d(TAG, "Starting activity fresh");
        }

        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        setSupportActionBar(toolbar);

        navigation.setCheckedItem(R.id.nav_home);
        navigation.setNavigationItemSelectedListener(this);

        populateContacts();
        setNotification();
        CommonUtils.setupBackgroundImage(this, backgroundImageView);