diff --git a/.travis.yml b/.travis.yml index 8d46ac8e..57962a94 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: android -jdk: oraclejdk7 +jdk: oraclejdk8 sudo: false cache: directories: @@ -12,19 +12,19 @@ cache: android: components: - tools - - build-tools-23.0.2 - - android-23 + - build-tools-24.0.2 + - android-24 - platform-tools - extra-google-google_play_services - extra-android-support - extra-android-m2repository - extra-google-m2repository - - addon-google_apis-google-23 + - addon-google_apis-google-24 licenses: - - 'android-sdk-preview-license-52d11cd2' + - 'android-sdk-license-c81a61d9' - 'android-sdk-license-.+' - - 'google-gdk-license-.+' + script: - ./gradlew build diff --git a/build.gradle b/build.gradle index 194738b7..f1cfa575 100644 --- a/build.gradle +++ b/build.gradle @@ -4,10 +4,12 @@ buildscript { apply from: 'secret.gradle' repositories { jcenter() + maven { url 'https://maven.fabric.io/public' } } dependencies { classpath 'com.android.tools.build:gradle:2.1.3' classpath 'com.google.gms:google-services:3.0.0' + classpath 'io.fabric.tools:gradle:1.21.5' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } diff --git a/mobile/build.gradle b/mobile/build.gradle index 74abfabe..97c1ee15 100644 --- a/mobile/build.gradle +++ b/mobile/build.gradle @@ -1,20 +1,10 @@ -buildscript { - repositories { - maven { url 'https://maven.fabric.io/public' } - } - - dependencies { - classpath 'io.fabric.tools:gradle:1.21.5' - } -} apply plugin: 'com.android.application' apply plugin: 'io.fabric' apply plugin: 'com.google.gms.google-services' android { - compileSdkVersion 23 - buildToolsVersion '23.0.2' - + compileSdkVersion 24 + buildToolsVersion '24.0.2' applicationVariants.all { variant -> variant.outputs.each { output -> @@ -28,12 +18,13 @@ android { defaultConfig { applicationId 'com.alexstyl.specialdates' minSdkVersion 16 - targetSdkVersion 22 - versionCode 57 - versionName '3.8' + targetSdkVersion 24 + versionCode 58 + versionName '4.0' manifestPlaceholders = [crashlyticsApiKey: crashlyticsKey] buildConfigField 'String', 'API_KEY_VENDING', '\"' + androidVendingKey + "\"" + buildConfigField 'String', 'MIXPANEL_TOKEN', '\"' + mixpanelKey + "\"" } buildTypes { @@ -54,6 +45,10 @@ android { abortOnError false } + testOptions { + unitTests.returnDefaultValues = true + } + } repositories { @@ -66,13 +61,12 @@ repositories { } dependencies { - compile fileTree(dir: 'libs', exclude: 'android-support-v4.jar', include: ['*.jar']) - compile project(':numberpicker') - compile 'com.android.support:support-annotations:23.3.0' - compile 'com.android.support:design:23.3.0' - compile 'com.android.support:appcompat-v7:23.3.0' - compile 'com.android.support:recyclerview-v7:23.3.0' + compile 'com.android.support:support-annotations:24.2.0' + compile 'com.android.support:design:24.2.0' + compile 'com.android.support:appcompat-v7:24.2.0' + compile 'com.android.support:recyclerview-v7:24.2.0' + compile 'com.android.support:transition:24.2.0' compile 'net.danlew:android.joda:2.8.0' compile('de.psdev.licensesdialog:licensesdialog:1.5.0') { exclude module: 'support-v4' @@ -83,7 +77,7 @@ dependencies { compile('com.crashlytics.sdk.android:crashlytics:2.5.5@aar') { transitive = true; } - compile 'com.google.firebase:firebase-core:9.4.0' + compile 'com.mixpanel.android:mixpanel-android:4.9.2' debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1' diff --git a/mobile/src/debug/AndroidManifest.xml b/mobile/src/debug/AndroidManifest.xml index a42b85dd..4a93fe71 100644 --- a/mobile/src/debug/AndroidManifest.xml +++ b/mobile/src/debug/AndroidManifest.xml @@ -1,30 +1,23 @@ - + package="com.alexstyl.specialdates"> + android:taskAffinity="com.alexstyl.debug"> + - diff --git a/mobile/src/debug/java/com/alexstyl/specialdates/debug/DebugActivity.java b/mobile/src/debug/java/com/alexstyl/specialdates/debug/DebugActivity.java index 2e1ff275..d23a74b3 100644 --- a/mobile/src/debug/java/com/alexstyl/specialdates/debug/DebugActivity.java +++ b/mobile/src/debug/java/com/alexstyl/specialdates/debug/DebugActivity.java @@ -1,26 +1,9 @@ package com.alexstyl.specialdates.debug; -import android.annotation.TargetApi; -import android.content.ActivityNotFoundException; -import android.content.ContentUris; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.Build; import android.os.Bundle; -import android.preference.Preference; -import android.provider.CalendarContract; -import android.widget.Toast; import com.alexstyl.specialdates.R; -import com.alexstyl.specialdates.contact.actions.IntentAction; -import com.alexstyl.specialdates.service.DailyReminderIntentService; import com.alexstyl.specialdates.ui.base.MementoPreferenceActivity; -import com.alexstyl.specialdates.ui.base.MementoPreferenceFragment; -import com.alexstyl.specialdates.util.Utils; -import com.alexstyl.specialdates.widgetprovider.TodayWidgetProvider; - -import java.util.Calendar; public class DebugActivity extends MementoPreferenceActivity { @Override @@ -29,70 +12,4 @@ public void onCreate(Bundle savedInstanceState) { setContentView(R.layout.fragment_debug); } - public static class DebugFragment extends MementoPreferenceFragment { - - @Override - public void onCreate(Bundle paramBundle) { - super.onCreate(paramBundle); - addPreferencesFromResource(R.xml.preference_debug); - - findPreference(R.string.key_debug_refresh_widget).setOnPreferenceClickListener( - new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - TodayWidgetProvider.updateWidgets(getActivity()); - Toast.makeText(getActivity(), "Widget(s) refreshed", Toast.LENGTH_SHORT).show(); - return true; - } - } - ); - - findPreference(R.string.key_debug_daily_reminder).setOnPreferenceClickListener( - new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - DailyReminderIntentService.startService(getActivity()); - Toast.makeText(getActivity(), "Daily Reminder Triggered", Toast.LENGTH_SHORT).show(); - return true; - } - } - ); - - findPreference(R.string.key_debug_start_calendar).setOnPreferenceClickListener( - new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - startDateIntent(); - return true; - } - - } - ); - } - - private void startDateIntent() { - IntentAction i = new IntentAction() { - @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) - @Override - public void onStartAction(Context context) throws ActivityNotFoundException { - Calendar cal = Calendar.getInstance(); - Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon(); - builder.appendPath("time"); - ContentUris.appendId(builder, cal.getTimeInMillis()); - Intent intent = new Intent(Intent.ACTION_VIEW) - .setData(builder.build()); - - startActivity(intent); - - } - - @Override - public String getName() { - return "date debug"; - } - }; - Utils.openIntentSafely(getActivity(), i); - } - } - } diff --git a/mobile/src/debug/java/com/alexstyl/specialdates/debug/DebugFragment.java b/mobile/src/debug/java/com/alexstyl/specialdates/debug/DebugFragment.java new file mode 100644 index 00000000..34ff655c --- /dev/null +++ b/mobile/src/debug/java/com/alexstyl/specialdates/debug/DebugFragment.java @@ -0,0 +1,116 @@ +package com.alexstyl.specialdates.debug; + +import android.app.DatePickerDialog; +import android.content.ActivityNotFoundException; +import android.content.ContentUris; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.preference.Preference; +import android.provider.CalendarContract; +import android.widget.DatePicker; +import android.widget.Toast; + +import com.alexstyl.specialdates.R; +import com.alexstyl.specialdates.contact.actions.IntentAction; +import com.alexstyl.specialdates.dailyreminder.DailyReminderDebugPreferences; +import com.alexstyl.specialdates.date.DayDate; +import com.alexstyl.specialdates.service.DailyReminderIntentService; +import com.alexstyl.specialdates.ui.base.MementoPreferenceFragment; +import com.alexstyl.specialdates.util.Utils; +import com.alexstyl.specialdates.widgetprovider.TodayWidgetProvider; + +import java.util.Calendar; + +public class DebugFragment extends MementoPreferenceFragment { + + private DailyReminderDebugPreferences dailyReminderDebugPreferences; + + @Override + public void onCreate(Bundle paramBundle) { + super.onCreate(paramBundle); + addPreferencesFromResource(R.xml.preference_debug); + dailyReminderDebugPreferences = DailyReminderDebugPreferences.newInstance(getActivity()); + findPreference(R.string.key_debug_refresh_widget).setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + TodayWidgetProvider.updateWidgets(getActivity()); + Toast.makeText(getActivity(), "Widget(s) refreshed", Toast.LENGTH_SHORT).show(); + return true; + } + }); + + findPreference(R.string.key_debug_daily_reminder_date_enable) + .setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + dailyReminderDebugPreferences.setEnabled((boolean) newValue); + return true; + } + }); + findPreference(R.string.key_debug_daily_reminder_date).setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + DayDate today = dailyReminderDebugPreferences.getSelectedDate(); + DatePickerDialog datePickerDialog = new DatePickerDialog( + getActivity(), onDailyReminderDateSelectedListener, + today.getYear(), today.getMonth() - 1, today.getDayOfMonth() + ); + datePickerDialog.show(); + return false; + } + }); + + findPreference(R.string.key_debug_daily_reminder).setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + DailyReminderIntentService.startService(getActivity()); + Toast.makeText(getActivity(), "Daily Reminder Triggered", Toast.LENGTH_SHORT).show(); + return true; + } + }); + + findPreference(R.string.key_debug_start_calendar).setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + startDateIntent(); + return true; + } + + }); + } + + private void startDateIntent() { + IntentAction i = new IntentAction() { + @Override + public void onStartAction(Context context) throws ActivityNotFoundException { + Calendar cal = Calendar.getInstance(); + Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon(); + builder.appendPath("time"); + ContentUris.appendId(builder, cal.getTimeInMillis()); + Intent intent = new Intent(Intent.ACTION_VIEW) + .setData(builder.build()); + + startActivity(intent); + + } + + @Override + public String getName() { + return "date debug"; + } + }; + Utils.openIntentSafely(getActivity(), i); + } + + private final DatePickerDialog.OnDateSetListener onDailyReminderDateSelectedListener = new DatePickerDialog.OnDateSetListener() { + + @Override + public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) { + int month1 = month + 1; // dialog picker months have 0 index + dailyReminderDebugPreferences.setSelectedDate(dayOfMonth, month1, year); + } + }; + +} diff --git a/mobile/src/debug/java/com/alexstyl/specialdates/debug/MixingColorsActivity.java b/mobile/src/debug/java/com/alexstyl/specialdates/debug/MixingColorsActivity.java deleted file mode 100644 index 82679837..00000000 --- a/mobile/src/debug/java/com/alexstyl/specialdates/debug/MixingColorsActivity.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.alexstyl.specialdates.debug; - -import android.os.Bundle; -import android.widget.TextView; - -import com.alexstyl.specialdates.R; -import com.alexstyl.specialdates.ui.base.MementoActivity; -import com.alexstyl.specialdates.widgetprovider.WidgetColorCalculator; -import com.novoda.notils.caster.Views; - -public class MixingColorsActivity extends MementoActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.debug_activity_mixing_colors); - - TextView text = Views.findById(this, R.id.text); - - WidgetColorCalculator calculator = new WidgetColorCalculator(getResources().getColor(android.R.color.white)); - int color = calculator.getColorForDaysDifference(0); - text.setTextColor(color); - } -} diff --git a/mobile/src/debug/res/layout/fragment_debug.xml b/mobile/src/debug/res/layout/fragment_debug.xml index baae73be..1609a88f 100644 --- a/mobile/src/debug/res/layout/fragment_debug.xml +++ b/mobile/src/debug/res/layout/fragment_debug.xml @@ -1,7 +1,7 @@ diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml index 86974324..d6814953 100644 --- a/mobile/src/main/AndroidManifest.xml +++ b/mobile/src/main/AndroidManifest.xml @@ -8,15 +8,13 @@ - - - + + android:launchMode="singleTop" + android:windowSoftInputMode="adjustPan"> @@ -129,6 +128,8 @@ + + firstPair, Pair... otherPairs) { + SharedPreferences.Editor edit = prefs.edit(); + edit.putInt(key(firstPair.first), firstPair.second); + for (Pair pair : otherPairs) { + edit.putInt(key(pair.first), pair.second); + } + edit.apply(); + } } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/MementoApplication.java b/mobile/src/main/java/com/alexstyl/specialdates/MementoApplication.java index 004a50e7..c726e93c 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/MementoApplication.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/MementoApplication.java @@ -13,7 +13,6 @@ public class MementoApplication extends Application { - private static final String TAG = "Memento"; private static Context context; public static String getVersionName(Context context) { @@ -33,7 +32,7 @@ public static String getVersionName(Context context) { public static final String MARKET_LINK_SHORT = "http://goo.gl/ZQiAsi"; - public static Context getAppContext() { + public static Context getContext() { return context; } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/Navigator.java b/mobile/src/main/java/com/alexstyl/specialdates/Navigator.java index 848626ec..287c446a 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/Navigator.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/Navigator.java @@ -1,22 +1,33 @@ package com.alexstyl.specialdates; +import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.support.annotation.NonNull; +import com.alexstyl.specialdates.about.AboutActivity; +import com.alexstyl.specialdates.addevent.AddBirthdayActivity; +import com.alexstyl.specialdates.analytics.Analytics; +import com.alexstyl.specialdates.analytics.Screen; import com.alexstyl.specialdates.contact.actions.IntentAction; +import com.alexstyl.specialdates.permissions.ContactPermissionActivity; +import com.alexstyl.specialdates.search.SearchActivity; +import com.alexstyl.specialdates.settings.MainPreferenceActivity; +import com.alexstyl.specialdates.support.SupportDonateDialog; import com.alexstyl.specialdates.util.Utils; public class Navigator { public static final Uri GOOGLE_PLUS_COMMUNITY = Uri.parse("https://plus.google.com/u/0/communities/112144353599130693487"); - private final Context context; + private final Activity activity; + private final Analytics analytics; - public Navigator(Context context) { - this.context = context; + public Navigator(Activity activity, Analytics analytics) { + this.activity = activity; + this.analytics = analytics; } public boolean canGoToPlayStore() { @@ -25,13 +36,13 @@ public boolean canGoToPlayStore() { } private boolean canResolveIntent(Intent intent) { - return context.getPackageManager().resolveActivity(intent, 0) != null; + return activity.getPackageManager().resolveActivity(intent, 0) != null; } public void toPlayStore() { try { Intent intent = buildPlayStoreIntent(); - context.startActivity(intent); + activity.startActivity(intent); } catch (ActivityNotFoundException e) { ErrorTracker.track(e); } @@ -40,7 +51,7 @@ public void toPlayStore() { @NonNull private Intent buildPlayStoreIntent() { Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse("market://details?id=" + context.getPackageName())); + intent.setData(Uri.parse("market://details?id=" + activity.getPackageName())); return intent; } @@ -48,7 +59,7 @@ public void toGooglePlusCommunityBrowser() { try { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(GOOGLE_PLUS_COMMUNITY); - context.startActivity(intent); + activity.startActivity(intent); } catch (ActivityNotFoundException e) { ErrorTracker.track(e); } @@ -59,18 +70,18 @@ public void toGooglePlusCommunityApp() { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setPackage("com.google.android.apps.plus"); intent.setData(GOOGLE_PLUS_COMMUNITY); - context.startActivity(intent); + activity.startActivity(intent); } catch (ActivityNotFoundException e) { ErrorTracker.track(e); } } public boolean canGoToEmailSupport() { - return canResolveIntent(Utils.getSupportEmailIntent(context)); + return canResolveIntent(Utils.getSupportEmailIntent(activity)); } public void toEmailSupport() { - Utils.openIntentSafely(context, new IntentAction() { + Utils.openIntentSafely(activity, new IntentAction() { @Override public void onStartAction(Context context) throws ActivityNotFoundException { Intent intent = Utils.getSupportEmailIntent(context); @@ -83,4 +94,38 @@ public String getName() { } }); } + + public void toAddBirthday() { + Intent intent = new Intent(activity, AddBirthdayActivity.class); + activity.startActivity(intent); + } + + public void toSearch() { + Intent intent = new Intent(activity, SearchActivity.class); + activity.startActivity(intent); + } + + public void toAbout() { + Intent intent = new Intent(activity, AboutActivity.class); + activity.startActivity(intent); + analytics.trackScreen(Screen.ABOUT); + } + + public void toDonateDialog() { + Intent intent = new Intent(activity, SupportDonateDialog.class); + activity.startActivity(intent); + analytics.trackScreen(Screen.DONATE); + } + + public void toSettings() { + Intent intent = new Intent(activity, MainPreferenceActivity.class); + activity.startActivity(intent); + analytics.trackScreen(Screen.SETTINGS); + } + + public void toContactPermissionRequired(int requestCode) { + Intent intent = new Intent(activity, ContactPermissionActivity.class); + activity.startActivityForResult(intent, requestCode); + analytics.trackScreen(Screen.CONTACT_PERMISSION_REQUESTED); + } } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/PayPal.java b/mobile/src/main/java/com/alexstyl/specialdates/PayPal.java deleted file mode 100644 index 814c03b5..00000000 --- a/mobile/src/main/java/com/alexstyl/specialdates/PayPal.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.alexstyl.specialdates; - -/** - *

Created by alexstyl on 05/02/15.

- */ -public class PayPal { - - public static final String URL_DONATIONS = "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=M7V8YHDL2NVUC"; -} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/about/AboutActivity.java b/mobile/src/main/java/com/alexstyl/specialdates/about/AboutActivity.java index 2d79bdd4..f1a9fc45 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/about/AboutActivity.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/about/AboutActivity.java @@ -11,6 +11,7 @@ import com.alexstyl.specialdates.Navigator; import com.alexstyl.specialdates.R; +import com.alexstyl.specialdates.analytics.AnalyticsProvider; import com.alexstyl.specialdates.theming.AttributeExtractor; import com.alexstyl.specialdates.ui.CheatsSheat; import com.alexstyl.specialdates.ui.activity.MainActivity; @@ -41,7 +42,7 @@ protected void onCreate(Bundle savedInstanceState) { SimpleChromeCustomTabs.initialize(this); - navigator = new Navigator(this); + navigator = new Navigator(this, AnalyticsProvider.getAnalytics(this)); MementoToolbar toolbar = Views.findById(this, R.id.memento_toolbar); setSupportActionBar(toolbar); diff --git a/mobile/src/main/java/com/alexstyl/specialdates/addevent/AddBirthdayActivity.java b/mobile/src/main/java/com/alexstyl/specialdates/addevent/AddBirthdayActivity.java index cc5df3b4..d9b9b8b5 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/addevent/AddBirthdayActivity.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/addevent/AddBirthdayActivity.java @@ -9,10 +9,9 @@ import com.alexstyl.specialdates.addevent.ui.ContactHeroView; import com.alexstyl.specialdates.addevent.ui.ContactsAutoCompleteView; import com.alexstyl.specialdates.analytics.Action; -import com.alexstyl.specialdates.analytics.Analytics; import com.alexstyl.specialdates.analytics.ActionWithParameters; -import com.alexstyl.specialdates.analytics.Firebase; -import com.alexstyl.specialdates.analytics.Screen; +import com.alexstyl.specialdates.analytics.Analytics; +import com.alexstyl.specialdates.analytics.AnalyticsProvider; import com.alexstyl.specialdates.contact.Birthday; import com.alexstyl.specialdates.contact.Contact; import com.alexstyl.specialdates.theming.MementoTheme; @@ -35,9 +34,8 @@ public class AddBirthdayActivity extends ThemedActivity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - analytics = Firebase.get(this); - analytics.trackScreen(Screen.ADD_BIRTHDAY); - MementoTheme theme = Themer.get().getCurrentTheme(); + analytics = AnalyticsProvider.getAnalytics(this); + MementoTheme theme = Themer.get(this).getCurrentTheme(); setContentView(R.layout.activity_add_birthday, theme); contactHeroView = Views.findById(this, R.id.addbirthday_hero); diff --git a/mobile/src/main/java/com/alexstyl/specialdates/addevent/BirthdayQuery.java b/mobile/src/main/java/com/alexstyl/specialdates/addevent/BirthdayQuery.java index dabfff15..4ec4f5a0 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/addevent/BirthdayQuery.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/addevent/BirthdayQuery.java @@ -15,7 +15,6 @@ import com.alexstyl.specialdates.date.DateParseException; import com.alexstyl.specialdates.date.DayDate; import com.alexstyl.specialdates.util.DateParser; -import com.alexstyl.specialdates.util.Utils; import com.novoda.notils.exception.DeveloperError; public class BirthdayQuery { @@ -127,8 +126,7 @@ public static Cursor query(ContentResolver cr) { } private static final Uri CONTENT_URI = ContactsContract.Data.CONTENT_URI; - private static final String COL_DISPLAY_NAME = Utils.hasHoneycomb() ? ContactsContract.Contacts.DISPLAY_NAME_PRIMARY - : ContactsContract.Contacts.DISPLAY_NAME; + private static final String COL_DISPLAY_NAME = ContactsContract.Contacts.DISPLAY_NAME_PRIMARY; public static final String WHERE = "(" + ContactsContract.Data.MIMETYPE + " = ? AND " + diff --git a/mobile/src/main/java/com/alexstyl/specialdates/addevent/ui/BirthdayDatePicker.java b/mobile/src/main/java/com/alexstyl/specialdates/addevent/ui/BirthdayDatePicker.java index 7c5ba697..7df892dd 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/addevent/ui/BirthdayDatePicker.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/addevent/ui/BirthdayDatePicker.java @@ -8,6 +8,7 @@ import android.view.View; import android.widget.CheckedTextView; import android.widget.LinearLayout; +import android.widget.NumberPicker; import com.alexstyl.specialdates.R; import com.alexstyl.specialdates.contact.Birthday; @@ -18,19 +19,17 @@ import java.util.Locale; -import net.simonvt.numberpicker.NumberPicker; - public class BirthdayDatePicker extends LinearLayout { - private MonthLabels labels; + private final MonthLabels labels; - private NumberPicker dayPicker; - private NumberPicker monthPicker; - private NumberPicker yearPicker; + private final NumberPicker dayPicker; + private final NumberPicker monthPicker; + private final NumberPicker yearPicker; - private CheckedTextView includesYearCheckbox; + private final CheckedTextView includesYearCheckbox; - private DayDate today; + private final DayDate today; public BirthdayDatePicker(Context context, AttributeSet attrs) { super(context, attrs); diff --git a/mobile/src/main/java/com/alexstyl/specialdates/analytics/Action.java b/mobile/src/main/java/com/alexstyl/specialdates/analytics/Action.java index 9b192da8..2966b8a8 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/analytics/Action.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/analytics/Action.java @@ -1,12 +1,12 @@ package com.alexstyl.specialdates.analytics; public enum Action { - ADD_BIRTHDAY("add birthday"), - DAILY_REMINDER("enable daily reminder"), + ADD_BIRTHDAY("add_bday"), + DAILY_REMINDER("reminder"), DONATION("donate"), - INTERACT_CONTACT("interact contact"), - SELECT_THEME("select theme"), - GO_TO_TODAY("go to today"); + INTERACT_CONTACT("contact"), + SELECT_THEME("theme"), + GO_TO_TODAY("gotoday"); private final String name; diff --git a/mobile/src/main/java/com/alexstyl/specialdates/analytics/ActionWithParameters.java b/mobile/src/main/java/com/alexstyl/specialdates/analytics/ActionWithParameters.java index 4b455c5b..85e791f1 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/analytics/ActionWithParameters.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/analytics/ActionWithParameters.java @@ -30,4 +30,12 @@ public String getValue() { return value; } + @Override + public String toString() { + return "ActionWithParameters{" + + "actionName=" + actionName + + ", label='" + label + '\'' + + ", value='" + value + '\'' + + '}'; + } } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/analytics/Analytics.java b/mobile/src/main/java/com/alexstyl/specialdates/analytics/Analytics.java index 2da83786..97713c86 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/analytics/Analytics.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/analytics/Analytics.java @@ -1,7 +1,7 @@ package com.alexstyl.specialdates.analytics; public interface Analytics { - void trackAction(Action goToToday); + void trackAction(Action action); void trackAction(ActionWithParameters event); diff --git a/mobile/src/main/java/com/alexstyl/specialdates/analytics/AnalyticsProvider.java b/mobile/src/main/java/com/alexstyl/specialdates/analytics/AnalyticsProvider.java new file mode 100644 index 00000000..e9e0aa66 --- /dev/null +++ b/mobile/src/main/java/com/alexstyl/specialdates/analytics/AnalyticsProvider.java @@ -0,0 +1,25 @@ +package com.alexstyl.specialdates.analytics; + +import android.content.Context; + +import com.alexstyl.specialdates.BuildConfig; +import com.mixpanel.android.mpmetrics.MixpanelAPI; + +public class AnalyticsProvider { + + private static Analytics ANALYTICS; + + public static Analytics getAnalytics(Context context) { + if (ANALYTICS == null) { + ANALYTICS = createMixpanelAnalytics(context); + } + return ANALYTICS; + } + + private static Analytics createMixpanelAnalytics(Context context) { + String projectToken = BuildConfig.MIXPANEL_TOKEN; + MixpanelAPI mixpanel = MixpanelAPI.getInstance(context, projectToken); + return new MixPanel(mixpanel); + } + +} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/analytics/Firebase.java b/mobile/src/main/java/com/alexstyl/specialdates/analytics/Firebase.java deleted file mode 100644 index e002cce4..00000000 --- a/mobile/src/main/java/com/alexstyl/specialdates/analytics/Firebase.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.alexstyl.specialdates.analytics; - -import android.content.Context; -import android.os.Bundle; - -import com.google.firebase.analytics.FirebaseAnalytics; -import com.novoda.notils.logger.simple.Log; - -import java.util.Locale; - -public class Firebase implements Analytics { - - private static final Bundle NO_DATA = null; - - private final FirebaseAnalytics firebaseAnalytics; - - private static Firebase INSTANCE; - - public static Firebase get(Context context) { - if (INSTANCE == null) { - INSTANCE = new Firebase(FirebaseAnalytics.getInstance(context)); - } - return INSTANCE; - } - - private Firebase(FirebaseAnalytics firebaseAnalytics) { - this.firebaseAnalytics = firebaseAnalytics; - } - - @Override - public void trackAction(Action goToToday) { - String actionName = goToToday.getName(); - firebaseAnalytics.logEvent(actionName, NO_DATA); - Log.d("Tracking event:" + actionName); - } - - @Override - public void trackAction(ActionWithParameters action) { - String formattedAction = format(action); - firebaseAnalytics.logEvent(formattedAction, NO_DATA); - Log.d("Tracking event:" + formattedAction); - } - - @Override - public void trackScreen(Screen screen) { - firebaseAnalytics.logEvent("screen_view:" + screen.screenName(), NO_DATA); - Log.d("Tracking screen_view:" + screen); - } - - private String format(ActionWithParameters action) { - return String.format(Locale.US, "%s:%s:%s", action.getName(), action.getLabel(), action.getValue()); - } -} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/analytics/MixPanel.java b/mobile/src/main/java/com/alexstyl/specialdates/analytics/MixPanel.java new file mode 100644 index 00000000..793207f2 --- /dev/null +++ b/mobile/src/main/java/com/alexstyl/specialdates/analytics/MixPanel.java @@ -0,0 +1,42 @@ +package com.alexstyl.specialdates.analytics; + +import com.mixpanel.android.mpmetrics.MixpanelAPI; + +import org.json.JSONException; +import org.json.JSONObject; + +public class MixPanel implements Analytics { + + private final MixpanelAPI mixpanel; + + MixPanel(MixpanelAPI mixpanel) { + this.mixpanel = mixpanel; + } + + @Override + public void trackAction(Action action) { + mixpanel.track("Action: " + action.getName()); + } + + @Override + public void trackAction(ActionWithParameters event) { + JSONObject properties = createJSONfor(event); + mixpanel.track("Action: " + event.getName(), properties); + + } + + @Override + public void trackScreen(Screen screen) { + mixpanel.track("ScreenView: " + screen.screenName()); + } + + private static JSONObject createJSONfor(ActionWithParameters event) { + JSONObject properties = new JSONObject(); + try { + properties.put(event.getLabel(), event.getValue()); + } catch (JSONException e) { + e.printStackTrace(); + } + return properties; + } +} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/analytics/Screen.java b/mobile/src/main/java/com/alexstyl/specialdates/analytics/Screen.java index 7b1cbf34..0139bcaa 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/analytics/Screen.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/analytics/Screen.java @@ -1,13 +1,14 @@ package com.alexstyl.specialdates.analytics; public enum Screen { - HOME("home"), + HOME("upcoming"), ADD_BIRTHDAY("add birthday"), SEARCH("search"), SETTINGS("settings"), DATE_DETAILS("date details"), DONATE("donate"), - ABOUT("about"); + ABOUT("about"), + CONTACT_PERMISSION_REQUESTED("contact permission"); private final String screenName; diff --git a/mobile/src/main/java/com/alexstyl/specialdates/contact/ContactsQuery.java b/mobile/src/main/java/com/alexstyl/specialdates/contact/ContactsQuery.java index fcee9c80..74136715 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/contact/ContactsQuery.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/contact/ContactsQuery.java @@ -7,20 +7,16 @@ import android.os.Build; import android.provider.ContactsContract; -import com.alexstyl.specialdates.util.Utils; - @TargetApi(Build.VERSION_CODES.HONEYCOMB) class ContactsQuery { public final static Uri CONTENT_URI = ContactsContract.Data.CONTENT_URI; - public static String COL_DISPLAY_NAME = Utils.hasHoneycomb() ? ContactsContract.Contacts.DISPLAY_NAME_PRIMARY //3 - : ContactsContract.Contacts.DISPLAY_NAME; + public static String COL_DISPLAY_NAME = ContactsContract.Contacts.DISPLAY_NAME_PRIMARY; public static String COL_LOOKUP = ContactsContract.Contacts.LOOKUP_KEY; @SuppressLint("InlinedApi") - public final static String SORT_ORDER = Utils.hasHoneycomb() ? - ContactsContract.Contacts.DISPLAY_NAME_PRIMARY : ContactsContract.Contacts.DISPLAY_NAME; + public final static String SORT_ORDER = ContactsContract.Contacts.DISPLAY_NAME_PRIMARY; @SuppressLint("InlinedApi") public static final String[] PROJECTION = { diff --git a/mobile/src/main/java/com/alexstyl/specialdates/dailyreminder/DailyReminderDebugPreferences.java b/mobile/src/main/java/com/alexstyl/specialdates/dailyreminder/DailyReminderDebugPreferences.java new file mode 100644 index 00000000..5864e64b --- /dev/null +++ b/mobile/src/main/java/com/alexstyl/specialdates/dailyreminder/DailyReminderDebugPreferences.java @@ -0,0 +1,43 @@ +package com.alexstyl.specialdates.dailyreminder; + +import android.content.Context; +import android.support.v4.util.Pair; + +import com.alexstyl.specialdates.EasyPreferences; +import com.alexstyl.specialdates.R; +import com.alexstyl.specialdates.date.DayDate; + +public final class DailyReminderDebugPreferences { + + private final EasyPreferences preferences; + + public static DailyReminderDebugPreferences newInstance(Context context) { + return new DailyReminderDebugPreferences(EasyPreferences.createForPrivatePreferences(context, R.string.pref_dailyreminder_debug)); + } + + private DailyReminderDebugPreferences(EasyPreferences preferences) { + this.preferences = preferences; + } + + public DayDate getSelectedDate() { + int dayOfMonth = preferences.getInt(R.string.key_debug_daily_reminder_date_fake_day, 1); + int month = preferences.getInt(R.string.key_debug_daily_reminder_date_fake_month, DayDate.JANUARY); + int year = preferences.getInt(R.string.key_debug_daily_reminder_date_fake_year, 2016); + return DayDate.newInstance(dayOfMonth, month, year); + } + + public boolean isFakeDateEnabled() { + return preferences.getBoolean(R.string.key_debug_daily_reminder_date_enable, false); + } + + public void setSelectedDate(int dayOfMonth, int month, int year) { + Pair dayPair = new Pair<>(R.string.key_debug_daily_reminder_date_fake_day, dayOfMonth); + Pair monthPair = new Pair<>(R.string.key_debug_daily_reminder_date_fake_month, month); + Pair yearPair = new Pair<>(R.string.key_debug_daily_reminder_date_fake_year, year); + preferences.setIntegers(dayPair, monthPair, yearPair); + } + + public void setEnabled(boolean newValue) { + preferences.setBoolean(R.string.key_debug_daily_reminder_date_enable, newValue); + } +} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/date/ContactEvent.java b/mobile/src/main/java/com/alexstyl/specialdates/date/ContactEvent.java index c10465e8..ca4f3ca1 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/date/ContactEvent.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/date/ContactEvent.java @@ -1,12 +1,16 @@ package com.alexstyl.specialdates.date; +import android.content.res.Resources; + +import com.alexstyl.specialdates.R; +import com.alexstyl.specialdates.contact.Birthday; import com.alexstyl.specialdates.contact.Contact; import com.alexstyl.specialdates.events.EventType; /** * A representation of an event, affiliated to a contact */ -public class ContactEvent { +public final class ContactEvent { private final DayDate date; private final Contact contact; @@ -18,9 +22,19 @@ public ContactEvent(EventType eventType, DayDate date, Contact contact) { this.contact = contact; } - /** - * Returns the date of the event - */ + public String getLabel(Resources resources) { + if (eventType == EventType.BIRTHDAY) { + Birthday birthday = contact.getBirthday(); + if (birthday.includesYear()) { + int age = birthday.getAgeOnYear(getYear()); + if (age > 0) { + return resources.getString(R.string.turns_age, age); + } + } + } + return resources.getString(eventType.nameRes()); + } + public DayDate getDate() { return date; } @@ -29,24 +43,11 @@ public EventType getType() { return eventType; } - public int getDayOfMonth() { - return date.getDayOfMonth(); - } - - public int getMonth() { - return date.getMonth(); - } - public int getYear() { return date.getYear(); } - /** - * Returns the contact associated with this event - */ - public Contact getContact() { return contact; } - } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/date/DateDisplayStringCreator.java b/mobile/src/main/java/com/alexstyl/specialdates/date/DateDisplayStringCreator.java index 0018decf..1ca46da0 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/date/DateDisplayStringCreator.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/date/DateDisplayStringCreator.java @@ -61,7 +61,7 @@ private boolean isSingleDigit(int number) { public String fullyFormattedBirthday(Birthday birthday) { DayDate dayDate = DayDate.newInstance(birthday.getDayOfMonth(), birthday.getMonth(), birthday.getYear()); - Context appContext = MementoApplication.getAppContext(); + Context appContext = MementoApplication.getContext(); int format_flags = DateUtils.FORMAT_NO_NOON_MIDNIGHT | DateUtils.FORMAT_CAP_AMPM | DateUtils.FORMAT_SHOW_DATE; @@ -76,7 +76,7 @@ public String fullyFormattedBirthday(Birthday birthday) { public String fullyFormattedDate(Date date) { DayDate dayDate = DayDate.newInstance(date.getDayOfMonth(), date.getMonth(), date.getYear()); - Context appContext = MementoApplication.getAppContext(); + Context appContext = MementoApplication.getContext(); int format_flags = DateUtils.FORMAT_NO_NOON_MIDNIGHT | DateUtils.FORMAT_CAP_AMPM | DateUtils.FORMAT_SHOW_DATE; diff --git a/mobile/src/main/java/com/alexstyl/specialdates/datedetails/DateDetailsActivity.java b/mobile/src/main/java/com/alexstyl/specialdates/datedetails/DateDetailsActivity.java index f4f418fa..d0eed518 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/datedetails/DateDetailsActivity.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/datedetails/DateDetailsActivity.java @@ -23,7 +23,6 @@ public class DateDetailsActivity extends ThemedActivity { private static final String EXTRA_DAY = BuildConfig.APPLICATION_ID + ".dayOfMonth"; private static final String EXTRA_MONTH = BuildConfig.APPLICATION_ID + ".month"; private static final String EXTRA_YEAR = BuildConfig.APPLICATION_ID + ".year"; - private static final String EXTRA_SOURCE = BuildConfig.APPLICATION_ID + ".source"; /** * The activity was lauched because the user tapped on a notification @@ -32,25 +31,18 @@ public class DateDetailsActivity extends ThemedActivity { private DayDate displayingDate; - private void setHeaderTitle(String str) { - if (getSupportActionBar() != null) { - getSupportActionBar().setTitle(str); - } - } - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_date_details); MementoToolbar toolbar = Views.findById(this, R.id.memento_toolbar); - toolbar.displayAsUp(); setSupportActionBar(toolbar); Optional receivedDate = getCalendarFromIntent(getIntent()); if (!receivedDate.isPresent()) { finish(); - ErrorTracker.track(new RuntimeException("Tried to open DateDetails with no date in the intent:[" + getIntent() + "]")); + ErrorTracker.track(new NullPointerException("Tried to open DateDetails with no date in the intent:[" + getIntent() + "]")); return; } this.displayingDate = receivedDate.get(); @@ -76,6 +68,11 @@ public void onCreate(Bundle savedInstanceState) { } + String titleDate = DateFormatUtils.formatTimeStampString( + DateDetailsActivity.this, displayingDate.toMillis(), + true + ); + setTitle(titleDate); } private Optional getCalendarFromIntent(Intent intent) { @@ -107,18 +104,6 @@ public boolean onOptionsItemSelected(MenuItem item) { return super.onOptionsItemSelected(item); } - @Override - protected void onResume() { - super.onResume(); - // update date? - String titleDate = DateFormatUtils.formatTimeStampString( - DateDetailsActivity.this, displayingDate.toMillis(), - true - ); - - setHeaderTitle(titleDate); - } - /** * Starts the {@link DateDetailsActivity}, that will display details about * the given date @@ -127,7 +112,8 @@ protected void onResume() { */ public static void startActivity(Context context, int month, int dayOfMonth, int year) { - context.startActivity(getStartIntent(context, dayOfMonth, month, year)); + Intent startIntent = getStartIntent(context, dayOfMonth, month, year); + context.startActivity(startIntent); } /** @@ -151,11 +137,4 @@ public static Intent getStartIntentFromExternal(Context context, int dayOfMonth, return startIntent; } - public static void startActivity(Context context, DayDate date) { - startActivity( - context, date.getMonth(), date.getDayOfMonth(), - date.getYear() - ); - - } } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/datedetails/DateDetailsFragment.java b/mobile/src/main/java/com/alexstyl/specialdates/datedetails/DateDetailsFragment.java index f2ebabf1..eee686d9 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/datedetails/DateDetailsFragment.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/datedetails/DateDetailsFragment.java @@ -27,18 +27,20 @@ import com.alexstyl.specialdates.R; import com.alexstyl.specialdates.analytics.Analytics; import com.alexstyl.specialdates.analytics.ActionWithParameters; -import com.alexstyl.specialdates.analytics.Firebase; +import com.alexstyl.specialdates.analytics.AnalyticsProvider; import com.alexstyl.specialdates.contact.Contact; import com.alexstyl.specialdates.contact.actions.LabeledAction; import com.alexstyl.specialdates.date.ContactEvent; import com.alexstyl.specialdates.date.DateDisplayStringCreator; import com.alexstyl.specialdates.date.DayDate; import com.alexstyl.specialdates.events.namedays.NamesInADate; +import com.alexstyl.specialdates.permissions.PermissionChecker; import com.alexstyl.specialdates.support.AskForSupport; import com.alexstyl.specialdates.support.OnSupportCardClickListener; import com.alexstyl.specialdates.ui.base.MementoFragment; import com.alexstyl.specialdates.analytics.Action; import com.alexstyl.specialdates.ui.dialog.ProgressFragmentDialog; +import com.alexstyl.specialdates.permissions.ContactPermissionRequest; import com.alexstyl.specialdates.util.ShareNamedaysIntentCreator; import java.util.List; @@ -60,6 +62,7 @@ public class DateDetailsFragment extends MementoFragment implements LoaderManage private GridWithHeaderSpacesItemDecoration spacingDecoration; private Navigator navigator; + private ContactPermissionRequest permissions; public static Fragment newInstance(int year, int month, int dayofMonth) { Fragment fragment = new DateDetailsFragment(); @@ -99,14 +102,12 @@ public void onContactActionsMenuClicked(View v, Contact contact) { i++; } - popup.setOnMenuItemClickListener( - new PopupMenu.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem menuItem) { - return actions.get(menuItem.getItemId()).fire(getActivity()); - } - } - ); + popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem menuItem) { + return actions.get(menuItem.getItemId()).fire(getActivity()); + } + }); popup.show(); } @@ -117,18 +118,16 @@ public void onActionClicked(View v, LabeledAction action) { // display the Loading progress if (action.hasSlowStart()) { Handler handler = new Handler(); - handler.postDelayed( - new Runnable() { - @Override - public void run() { - if (isVisible() && isResumed()) { - // if after a second the activity is still showing, show the loading dialog - ProgressFragmentDialog dialog = ProgressFragmentDialog.newInstance(getString(R.string.loading), true); - dialog.show(getFragmentManager(), FM_TAG_ACTIONS); - } - } - }, DateUtils.SECOND_IN_MILLIS - ); + handler.postDelayed(new Runnable() { + @Override + public void run() { + if (isVisible() && isResumed()) { + // if after a second the activity is still showing, show the loading dialog + ProgressFragmentDialog dialog = ProgressFragmentDialog.newInstance(getString(R.string.loading), true); + dialog.show(getFragmentManager(), FM_TAG_ACTIONS); + } + } + }, DateUtils.SECOND_IN_MILLIS); } action.fire(getActivity()); ActionWithParameters actionWithParameters = new ActionWithParameters(Action.INTERACT_CONTACT, "source", action.getAction().getName()); @@ -149,16 +148,40 @@ public void onSaveInstanceState(Bundle outState) { public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - getLoaderManager().initLoader(LOADER_ID_EVENTS, null, this); + if (permissions.permissionIsPresent()) { + getLoaderManager().initLoader(LOADER_ID_EVENTS, null, this); + } else { + permissions.requestForPermission(); + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + permissions.onActivityResult(requestCode, resultCode, data); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - navigator = new Navigator(getActivity()); - analytics = Firebase.get(getActivity()); + analytics = AnalyticsProvider.getAnalytics(getActivity()); + navigator = new Navigator(getActivity(), analytics); + PermissionChecker checker = new PermissionChecker(getActivity()); + permissions = new ContactPermissionRequest(navigator, checker, permissionCallbacks); } + private final ContactPermissionRequest.PermissionCallbacks permissionCallbacks = new ContactPermissionRequest.PermissionCallbacks() { + @Override + public void onPermissionGranted() { + getLoaderManager().initLoader(LOADER_ID_EVENTS, null, DateDetailsFragment.this); + } + + @Override + public void onPermissionDenied() { + getActivity().finishAffinity(); + } + }; + @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.menu_upcoming_light, menu); diff --git a/mobile/src/main/java/com/alexstyl/specialdates/datedetails/DateDetailsViewHolder.java b/mobile/src/main/java/com/alexstyl/specialdates/datedetails/DateDetailsViewHolder.java index 2b8dbb05..dbc751e3 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/datedetails/DateDetailsViewHolder.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/datedetails/DateDetailsViewHolder.java @@ -6,10 +6,8 @@ import android.widget.TextView; import com.alexstyl.specialdates.R; -import com.alexstyl.specialdates.contact.Birthday; import com.alexstyl.specialdates.contact.Contact; import com.alexstyl.specialdates.date.ContactEvent; -import com.alexstyl.specialdates.events.EventType; import com.alexstyl.specialdates.images.ImageLoader; import com.alexstyl.specialdates.ui.widget.ColorImageView; @@ -54,24 +52,10 @@ public void onClick(View v) { private void updateEventLabel(ContactEvent event) { eventLabel.setTextColor(resources.getColor(event.getType().getColorRes())); eventLabel.setVisibility(View.VISIBLE); - String label = getLabelFor(event); + String label = event.getLabel(resources); eventLabel.setText(label); } - private String getLabelFor(ContactEvent event) { - EventType eventType = event.getType(); - if (eventType == EventType.BIRTHDAY) { - Birthday birthday = event.getContact().getBirthday(); - if (birthday.includesYear()) { - int age = birthday.getAgeOnYear(event.getYear()); - if (age > 0) { - return resources.getString(R.string.turns_age, age); - } - } - } - return resources.getString(eventType.nameRes()); - } - abstract void clearActions(); } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/BirthdayDatabaseRefresher.java b/mobile/src/main/java/com/alexstyl/specialdates/events/BirthdayDatabaseRefresher.java index 1d1f0ceb..54ca8dea 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/BirthdayDatabaseRefresher.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/BirthdayDatabaseRefresher.java @@ -19,7 +19,6 @@ import com.alexstyl.specialdates.date.DayDate; import com.alexstyl.specialdates.events.database.EventSQLiteOpenHelper; import com.alexstyl.specialdates.util.DateParser; -import com.alexstyl.specialdates.util.Utils; import java.util.ArrayList; import java.util.Collections; @@ -120,8 +119,7 @@ public static Cursor query(ContentResolver cr) { } private static final Uri CONTENT_URI = ContactsContract.Data.CONTENT_URI; - private static final String COL_DISPLAY_NAME = Utils.hasHoneycomb() ? ContactsContract.Contacts.DISPLAY_NAME_PRIMARY - : ContactsContract.Contacts.DISPLAY_NAME; + private static final String COL_DISPLAY_NAME = ContactsContract.Contacts.DISPLAY_NAME_PRIMARY; public static final String WHERE = "(" + ContactsContract.Data.MIMETYPE + " = ? AND " + diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/EventType.java b/mobile/src/main/java/com/alexstyl/specialdates/events/EventType.java index 7d8a5f45..84fd49e5 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/EventType.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/EventType.java @@ -4,20 +4,38 @@ import android.support.annotation.StringRes; import com.alexstyl.specialdates.R; -import com.novoda.notils.exception.DeveloperError; + +import java.util.HashMap; +import java.util.Map; public enum EventType { BIRTHDAY(EventColumns.TYPE_BIRTHDAY, R.string.birthday, R.color.birthday_red), NAMEDAY(EventColumns.TYPE_NAMEDAY, R.string.nameday, R.color.nameday_blue); - private final int id; + private final int eventTypeId; private final int eventNameRes; private final int eventColorRes; - EventType(int id, @StringRes int eventNameRes, @ColorRes int eventColorRes) { - this.id = id; - this.eventNameRes = eventNameRes; - this.eventColorRes = eventColorRes; + EventType(@EventTypeId int eventTypeId, @StringRes int nameResId, @ColorRes int colorResId) { + this.eventTypeId = eventTypeId; + this.eventNameRes = nameResId; + this.eventColorRes = colorResId; + } + + private static final Map map; + + static { + map = new HashMap<>(); + for (EventType eventType : values()) { + map.put(eventType.eventTypeId, eventType); + } + } + + public static EventType fromId(@EventTypeId int eventTypeId) { + if (map.containsKey(eventTypeId)) { + return map.get(eventTypeId); + } + throw new IllegalArgumentException("No event type with eventTypeId " + eventTypeId); } @StringRes @@ -29,13 +47,4 @@ public int nameRes() { public int getColorRes() { return eventColorRes; } - - public static EventType fromId(int eventTypeId) { - for (EventType eventType : values()) { - if (eventType.id == eventTypeId) { - return eventType; - } - } - throw new DeveloperError("No event type with id " + eventTypeId); - } } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/EventTypeId.java b/mobile/src/main/java/com/alexstyl/specialdates/events/EventTypeId.java new file mode 100644 index 00000000..6f45438d --- /dev/null +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/EventTypeId.java @@ -0,0 +1,11 @@ +package com.alexstyl.specialdates.events; + +import android.support.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.CLASS) +@IntDef({EventColumns.TYPE_BIRTHDAY, EventColumns.TYPE_NAMEDAY}) +public @interface EventTypeId { +} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/PeopleEventsContract.java b/mobile/src/main/java/com/alexstyl/specialdates/events/PeopleEventsContract.java index 43deb506..52e47237 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/PeopleEventsContract.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/PeopleEventsContract.java @@ -35,8 +35,8 @@ public static long getContactIdFrom(Cursor cursor) { public static EventType getEventType(Cursor cursor) { int eventTypeIndex = cursor.getColumnIndexOrThrow(PeopleEvents.EVENT_TYPE); - int anInt = cursor.getInt(eventTypeIndex); - return EventType.fromId(anInt); + @EventTypeId int rawEventType = cursor.getInt(eventTypeIndex); + return EventType.fromId(rawEventType); } private static final String SEPARATOR = "-"; diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/PeopleEventsUpdater.java b/mobile/src/main/java/com/alexstyl/specialdates/events/PeopleEventsUpdater.java index 160aeae4..bfb799a0 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/PeopleEventsUpdater.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/PeopleEventsUpdater.java @@ -56,7 +56,7 @@ private void updateEventsIfSettingsChanged() { boolean wereNamedaysSettingsUpdated = namedayMonitor.dataWasUpdated(); if (wereNamedaysSettingsUpdated) { - namedayDatabaseRefresher = NamedayDatabaseRefresher.newInstance(MementoApplication.getAppContext()); + namedayDatabaseRefresher = NamedayDatabaseRefresher.newInstance(MementoApplication.getContext()); } if (wereContactsUpdated) { birthdayDatabaseRefresher.refreshBirthdays(); diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/database/EventsDBContract.java b/mobile/src/main/java/com/alexstyl/specialdates/events/database/EventsDBContract.java index af79aa96..faadd832 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/database/EventsDBContract.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/database/EventsDBContract.java @@ -5,14 +5,14 @@ import com.alexstyl.specialdates.events.ContactColumns; import com.alexstyl.specialdates.events.EventColumns; -public class EventsDBContract { +public final class EventsDBContract { - public static class AnnualEvents implements BaseColumns, ContactColumns, EventColumns { + public static final class AnnualEvents implements BaseColumns, ContactColumns, EventColumns { public static final String TABLE_NAME = "annual_events"; } - public static class DynamicEvents implements BaseColumns, ContactColumns, EventColumns { + public static final class DynamicEvents implements BaseColumns, ContactColumns, EventColumns { public static final String TABLE_NAME = "dynamic_events"; } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/NamedayDatabaseRefresher.java b/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/NamedayDatabaseRefresher.java index 1936da13..c47679b0 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/NamedayDatabaseRefresher.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/NamedayDatabaseRefresher.java @@ -22,7 +22,6 @@ import com.alexstyl.specialdates.events.database.EventSQLiteOpenHelper; import com.alexstyl.specialdates.events.namedays.calendar.NamedayCalendar; import com.alexstyl.specialdates.events.namedays.calendar.NamedayCalendarProvider; -import com.alexstyl.specialdates.util.Utils; import java.util.ArrayList; import java.util.Collections; @@ -230,14 +229,12 @@ private static class DeviceContactsQuery { }; @SuppressLint("InlinedApi") - public final static String SORT_ORDER = - Utils.hasHoneycomb() ? ContactsContract.Contacts.DISPLAY_NAME_PRIMARY - : ContactsContract.Contacts.DISPLAY_NAME; + public final static String SORT_ORDER = ContactsContract.Contacts.DISPLAY_NAME_PRIMARY; @SuppressLint("InlinedApi") public static final String[] PROJECTION = { ContactsContract.Data.CONTACT_ID, - Utils.hasHoneycomb() ? ContactsContract.Contacts.DISPLAY_NAME_PRIMARY : ContactsContract.Contacts.DISPLAY_NAME, + ContactsContract.Contacts.DISPLAY_NAME_PRIMARY }; public static final int ID = 0; diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/NoSpecialNamedaysStrategy.java b/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/NoSpecialNamedaysStrategy.java index d62f4377..ddc84bd5 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/NoSpecialNamedaysStrategy.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/NoSpecialNamedaysStrategy.java @@ -7,7 +7,6 @@ import java.util.ArrayList; public enum NoSpecialNamedaysStrategy implements SpecialNamedaysStrategy { - INSTANCE; @Override diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/SpecialGreekNamedaysCalculator.java b/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/SpecialGreekNamedaysCalculator.java index 157b41a3..70048c5a 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/SpecialGreekNamedaysCalculator.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/SpecialGreekNamedaysCalculator.java @@ -18,7 +18,7 @@ public class SpecialGreekNamedaysCalculator { public SpecialGreekNamedaysCalculator(List easternNamedays) { this.easternNamedays = easternNamedays; - this.context = MementoApplication.getAppContext(); + this.context = MementoApplication.getContext(); } public NamedayBundle calculateForEasterDate(DayDate easter) { diff --git a/mobile/src/main/java/com/alexstyl/specialdates/images/ImageDownloader.java b/mobile/src/main/java/com/alexstyl/specialdates/images/ImageDownloader.java index 3b60d3d5..176c9ea7 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/images/ImageDownloader.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/images/ImageDownloader.java @@ -7,7 +7,6 @@ import android.os.Build; import android.provider.ContactsContract; -import com.alexstyl.specialdates.util.Utils; import com.nostra13.universalimageloader.core.download.BaseImageDownloader; import java.io.FileNotFoundException; @@ -33,11 +32,7 @@ protected InputStream getStreamFromContent(String imageUri, Object extra) throws Uri uri = Uri.parse(imageUri); if (imageUri.startsWith("content://com.android.contacts/")) { - if (Utils.hasICS()) { - return ContactsContract.Contacts.openContactPhotoInputStream(res, uri, true); - } else { - return ContactsContract.Contacts.openContactPhotoInputStream(res, uri); - } + return ContactsContract.Contacts.openContactPhotoInputStream(res, uri, true); } return res.openInputStream(uri); diff --git a/mobile/src/main/java/com/alexstyl/specialdates/permissions/ContactPermissionActivity.java b/mobile/src/main/java/com/alexstyl/specialdates/permissions/ContactPermissionActivity.java new file mode 100644 index 00000000..4e2f2e15 --- /dev/null +++ b/mobile/src/main/java/com/alexstyl/specialdates/permissions/ContactPermissionActivity.java @@ -0,0 +1,50 @@ +package com.alexstyl.specialdates.permissions; + +import android.Manifest; +import android.annotation.TargetApi; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.view.View; +import android.widget.Button; + +import com.alexstyl.specialdates.R; +import com.alexstyl.specialdates.ui.base.ThemedActivity; +import com.novoda.notils.caster.Views; + +@TargetApi(Build.VERSION_CODES.M) // Runtime permissions were added in M (SDK 23) +public class ContactPermissionActivity extends ThemedActivity { + + private static final int REQUEST_CONTACT_PERMISSION = 5; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_contact_permission_request); + setResult(RESULT_CANCELED); + + Button grantButton = Views.findById(this, R.id.contact_permission_grant_button); + grantButton.setOnClickListener(onGrantPermissionButtonClicked); + } + + private final View.OnClickListener onGrantPermissionButtonClicked = new View.OnClickListener() { + @Override + public void onClick(View v) { + requestContactPermission(); + } + }; + + private void requestContactPermission() { + requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_CONTACT_PERMISSION); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (requestCode == REQUEST_CONTACT_PERMISSION && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + setResult(RESULT_OK); + finish(); + } + } +} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/permissions/ContactPermissionRequest.java b/mobile/src/main/java/com/alexstyl/specialdates/permissions/ContactPermissionRequest.java new file mode 100644 index 00000000..d610b18a --- /dev/null +++ b/mobile/src/main/java/com/alexstyl/specialdates/permissions/ContactPermissionRequest.java @@ -0,0 +1,48 @@ +package com.alexstyl.specialdates.permissions; + +import android.app.Activity; +import android.content.Intent; + +import com.alexstyl.specialdates.Navigator; + +import static android.Manifest.permission.READ_CONTACTS; + +public class ContactPermissionRequest { + + public static final int CONTACT_REQUEST = 1990; + + private final Navigator navigator; + private final PermissionCallbacks callbacks; + private final PermissionChecker checker; + + public ContactPermissionRequest(Navigator navigator, PermissionChecker checker, PermissionCallbacks callbacks) { + this.navigator = navigator; + this.checker = checker; + this.callbacks = callbacks; + } + + public boolean permissionIsPresent() { + return checker.hasPermission(READ_CONTACTS); + } + + public void requestForPermission() { + navigator.toContactPermissionRequired(CONTACT_REQUEST); + } + + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == CONTACT_REQUEST) { + if (resultCode == Activity.RESULT_OK) { + callbacks.onPermissionGranted(); + } else { + callbacks.onPermissionDenied(); + } + } + } + + public interface PermissionCallbacks { + void onPermissionGranted(); + + void onPermissionDenied(); + } +} + diff --git a/mobile/src/main/java/com/alexstyl/specialdates/permissions/MementoPermissions.java b/mobile/src/main/java/com/alexstyl/specialdates/permissions/MementoPermissions.java new file mode 100644 index 00000000..a6f2caf6 --- /dev/null +++ b/mobile/src/main/java/com/alexstyl/specialdates/permissions/MementoPermissions.java @@ -0,0 +1,12 @@ +package com.alexstyl.specialdates.permissions; + +import android.Manifest; +import android.support.annotation.StringDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.CLASS) +@StringDef({Manifest.permission.READ_CONTACTS, Manifest.permission.READ_EXTERNAL_STORAGE}) +public @interface MementoPermissions { +} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/permissions/PermissionChecker.java b/mobile/src/main/java/com/alexstyl/specialdates/permissions/PermissionChecker.java new file mode 100644 index 00000000..6f2e1ab3 --- /dev/null +++ b/mobile/src/main/java/com/alexstyl/specialdates/permissions/PermissionChecker.java @@ -0,0 +1,17 @@ +package com.alexstyl.specialdates.permissions; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.support.v4.app.ActivityCompat; + +public class PermissionChecker { + private final Context context; + + public PermissionChecker(Context context) { + this.context = context; + } + + public boolean hasPermission(@MementoPermissions String permission) { + return ActivityCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED; + } +} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/search/BackKeyEditText.java b/mobile/src/main/java/com/alexstyl/specialdates/search/BackKeyEditText.java new file mode 100644 index 00000000..968b80a9 --- /dev/null +++ b/mobile/src/main/java/com/alexstyl/specialdates/search/BackKeyEditText.java @@ -0,0 +1,26 @@ +package com.alexstyl.specialdates.search; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.widget.EditText; + +public class BackKeyEditText extends EditText { + public BackKeyEditText(Context context, AttributeSet attrs) { + super(context, attrs); + } + + private OnBackKeyPressedListener listener; + + public void setOnBackKeyPressedListener(OnBackKeyPressedListener listener) { + this.listener = listener; + } + + @Override + public boolean onKeyPreIme(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK && listener.onBackButtonPressed()) { + return true; + } + return super.onKeyPreIme(keyCode, event); + } +} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/search/DelayedTextWatcher.java b/mobile/src/main/java/com/alexstyl/specialdates/search/DelayedTextWatcher.java index e6545ae4..1abbeb0f 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/search/DelayedTextWatcher.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/search/DelayedTextWatcher.java @@ -2,7 +2,6 @@ import android.os.Handler; import android.text.Editable; -import android.text.TextUtils; import android.text.TextWatcher; public class DelayedTextWatcher implements TextWatcher { @@ -23,13 +22,13 @@ public DelayedTextWatcher(TextUpdatedCallback textWatchTextUpdatedCallback, Hand this.handler = handler; } - private Runnable timeEndRunnable = new Runnable() { + private final Runnable timeEndRunnable = new Runnable() { @Override public void run() { - if (TextUtils.isEmpty(text)) { - textWatchTextUpdatedCallback.onEmptyTextEntered(); - } else { + if (text.length() > 0) { textWatchTextUpdatedCallback.onTextConfirmed(text); + } else { + textWatchTextUpdatedCallback.onEmptyTextConfirmed(); } } }; @@ -55,7 +54,7 @@ public void afterTextChanged(Editable s) { public interface TextUpdatedCallback { void onTextChanged(String text); - void onEmptyTextEntered(); + void onEmptyTextConfirmed(); void onTextConfirmed(String text); } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/search/DeviceSearchFragment.java b/mobile/src/main/java/com/alexstyl/specialdates/search/DeviceSearchFragment.java deleted file mode 100644 index ecbd54df..00000000 --- a/mobile/src/main/java/com/alexstyl/specialdates/search/DeviceSearchFragment.java +++ /dev/null @@ -1,319 +0,0 @@ -package com.alexstyl.specialdates.search; - -import android.content.Context; -import android.os.Bundle; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.Loader; -import android.support.v7.widget.GridLayoutManager; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.text.InputType; -import android.text.TextUtils; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodManager; -import android.widget.EditText; -import android.widget.ImageButton; -import android.widget.TextView; - -import com.alexstyl.specialdates.R; -import com.alexstyl.specialdates.contact.Contact; -import com.alexstyl.specialdates.datedetails.DateDetailsActivity; -import com.alexstyl.specialdates.date.DayDate; -import com.alexstyl.specialdates.events.namedays.NamedayPreferences; -import com.alexstyl.specialdates.events.namedays.NameCelebrations; -import com.alexstyl.specialdates.ui.base.MementoFragment; -import com.alexstyl.specialdates.ui.widget.SpacesItemDecoration; - -/** - * A fragment in which the user can search for namedays and their contact's birthdays. - *
The fragment has a different logic for when the user has enabled namedays for any language. - * If the user has enabled to display Namedays, the search EditText will give no suggestions. Instead a custom - * suggestion bar on top of the keyboard is going to be given to the user with names. - *

Created by alexstyl on 20/04/15.

- */ -public class DeviceSearchFragment extends MementoFragment implements NameSuggestionsAdapter.OnNameSelectedListener { - - private static final String KEY_QUERY = "alexstyl:key_query"; - - private static final int ID_CONTACTS = 31; - private static final int ID_NAMEDAYS = 32; - - private static final int INITAL_COUNT = 5; - - private int searchCounter = INITAL_COUNT; - - private EditText searchField; - - private ImageButton clearButton; - private RecyclerView resultView; - - private RecyclerView namesSuggestionsView; - private SearchResultAdapter adapter; - - private NameSuggestionsAdapter namesAdapter; - - private boolean displayNamedays; - - private String searchQuery; - - @Override - public void onStart() { - super.onStart(); - searchField.requestFocus(); - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putString(KEY_QUERY, searchQuery); - } - - private LoaderManager.LoaderCallbacks contactSearchCallbacks - = new LoaderManager.LoaderCallbacks() { - - @Override - public Loader onCreateLoader(int id, Bundle args) { - adapter.notifyIsLoadingMore(); - return new SearchLoader(getActivity(), searchQuery, searchCounter); - } - - @Override - public void onLoadFinished(Loader loader, SearchResults searchResults) { - if (loader.getId() == ID_CONTACTS) { - adapter.updateSearchResults(searchResults); - } - } - - @Override - public void onLoaderReset(Loader loader) { - if (loader.getId() == ID_CONTACTS) { - adapter.notifyIsLoadingMore(); - } - } - }; - - private LoaderManager.LoaderCallbacks namedayLoaderCallbacks = new LoaderManager.LoaderCallbacks() { - - @Override - public Loader onCreateLoader(int id, Bundle args) { - return NamedaysLoader.newInstance(getActivity(), searchQuery); - } - - @Override - public void onLoadFinished(Loader loader, NameCelebrations results) { - adapter.setNamedays(results); - } - - @Override - public void onLoaderReset(Loader loader) { - adapter.setNamedays(NameCelebrations.EMPTY); - } - }; - - private void startSearching() { - getLoaderManager().restartLoader(ID_CONTACTS, null, contactSearchCallbacks); - getLoaderManager().restartLoader(ID_NAMEDAYS, null, namedayLoaderCallbacks); - } - - private void clearResults() { - adapter.clearResults(); - if (displayNamedays) { - namesAdapter.clearNames(); - } - } - - private void hideClearButton() { - clearButton.setVisibility(View.GONE); - } - - private void showClearButton() { - clearButton.setVisibility(View.VISIBLE); - } - - private void resetSearchCounter() { - searchCounter = INITAL_COUNT; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - displayNamedays = shouldIncludeNamedays(); - if (savedInstanceState != null) { - searchQuery = savedInstanceState.getString(KEY_QUERY); - } - } - - private boolean shouldIncludeNamedays() { - NamedayPreferences namedayPreferences = NamedayPreferences.newInstance(getActivity()); - return namedayPreferences.isEnabled(); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_search, container, false); - } - - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - searchField = (EditText) view.findViewById(R.id.text_search_query); - setupSearchField(); - ImageButton closeButton = (ImageButton) view.findViewById(R.id.btn_close); - clearButton = (ImageButton) view.findViewById(R.id.btn_clear); - resultView = (RecyclerView) view.findViewById(android.R.id.list); - resultView.setHasFixedSize(false); - namesSuggestionsView = (RecyclerView) view.findViewById(R.id.nameday_suggestions); - - closeButton.setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - getActivity().finish(); - } - } - ); - clearButton.setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - searchField.setText(null); - searchField.requestFocus(); - InputMethodManager imm = (InputMethodManager) getActivity() - .getSystemService(Context.INPUT_METHOD_SERVICE); - imm.showSoftInput(searchField, InputMethodManager.SHOW_IMPLICIT); - - } - } - ); - - adapter = SearchResultAdapter.newInstance(getActivity(), displayNamedays); - adapter.setSearchResultClickListener(listener); - - resultView.setHasFixedSize(true); - RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getActivity()); - int spacingInPixels = getResources().getDimensionPixelSize(R.dimen.card_spacing_between); - resultView.addItemDecoration(new SpacesItemDecoration(spacingInPixels, 3)); - resultView.setLayoutManager(mLayoutManager); - resultView.setAdapter(adapter); - - if (displayNamedays) { - // we are loading namedays as well - GridLayoutManager namedayManager = new GridLayoutManager(getActivity(), 1, RecyclerView.HORIZONTAL, false); - namesAdapter = NameSuggestionsAdapter.newInstance(getActivity()); - namesAdapter.setOnNameSelectedListener(this); - namesSuggestionsView.setHasFixedSize(true); - namesSuggestionsView.setLayoutManager(namedayManager); - namesSuggestionsView.setAdapter(namesAdapter); - - searchField.setInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); - - searchField.setOnFocusChangeListener( - new View.OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - if (hasFocus) { - namesSuggestionsView.setVisibility(View.VISIBLE); - } else { - namesSuggestionsView.setVisibility(View.GONE); - } - } - } - ); - } else { - namesSuggestionsView.setVisibility(View.GONE); - } - } - - private final DelayedTextWatcher.TextUpdatedCallback textUpdatedTextUpdatedCallback = new DelayedTextWatcher.TextUpdatedCallback() { - @Override - public void onTextChanged(String text) { - searchQuery = text; - updateNameSuggestions(text); - resetSearchCounter(); - } - - @Override - public void onEmptyTextEntered() { - clearResults(); - hideClearButton(); - } - - @Override - public void onTextConfirmed(String text) { - startSearching(); - showClearButton(); - } - }; - - private void updateNameSuggestions(String text) { - if (displayNamedays) { - namesAdapter.getFilter().filter(text); - } - } - - private void setupSearchField() { - searchField.addTextChangedListener(DelayedTextWatcher.newInstance(textUpdatedTextUpdatedCallback)); - searchField.setOnEditorActionListener( - new TextView.OnEditorActionListener() { - @Override - public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - if (actionId == EditorInfo.IME_ACTION_SEARCH) { - InputMethodManager imm = (InputMethodManager) getActivity() - .getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(searchField.getWindowToken(), 0); - resultView.requestFocus(); - return true; - } - return false; - } - } - ); - } - - @Override - public void onNameSelected(View view, final String name) { - onNameSet(name); - } - - private void onNameSet(String name) { - // setting the text to the EditText will trigger the search for the name - searchField.setText(name); - namesAdapter.clearNames(); - - InputMethodManager imm = (InputMethodManager) getActivity() - .getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(searchField.getWindowToken(), 0); - searchField.clearFocus(); - - } - - @Override - public void onResume() { - super.onResume(); - if (TextUtils.isEmpty(searchQuery)) { - hideClearButton(); - } else { - showClearButton(); - } - } - - private SearchResultAdapter.SearchResultClickListener listener = new SearchResultAdapter.SearchResultClickListener() { - - @Override - public void onContactClicked(View v, Contact contact) { - contact.displayQuickInfo(getActivity(), v); - } - - @Override - public void onNamedayClicked(View v, int month, int day) { - DayDate dayDate = DayDate.today(); - DateDetailsActivity.startActivity(getActivity(), month, day, dayDate.getYear()); - } - - }; - -} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/search/OnBackKeyPressedListener.java b/mobile/src/main/java/com/alexstyl/specialdates/search/OnBackKeyPressedListener.java new file mode 100644 index 00000000..8bf8b18c --- /dev/null +++ b/mobile/src/main/java/com/alexstyl/specialdates/search/OnBackKeyPressedListener.java @@ -0,0 +1,8 @@ +package com.alexstyl.specialdates.search; + +public interface OnBackKeyPressedListener { + /** + * @return Return true if the event has been handled + */ + boolean onBackButtonPressed(); +} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/search/SearchActivity.java b/mobile/src/main/java/com/alexstyl/specialdates/search/SearchActivity.java index cdea9c5a..de48acc8 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/search/SearchActivity.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/search/SearchActivity.java @@ -1,16 +1,387 @@ package com.alexstyl.specialdates.search; +import android.content.Intent; import android.os.Bundle; +import android.support.transition.Fade; +import android.support.transition.Transition; +import android.support.transition.TransitionManager; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.Loader; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.text.InputType; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import com.alexstyl.specialdates.Navigator; import com.alexstyl.specialdates.R; +import com.alexstyl.specialdates.analytics.Analytics; +import com.alexstyl.specialdates.analytics.AnalyticsProvider; +import com.alexstyl.specialdates.analytics.Screen; +import com.alexstyl.specialdates.contact.Contact; +import com.alexstyl.specialdates.date.DayDate; +import com.alexstyl.specialdates.datedetails.DateDetailsActivity; +import com.alexstyl.specialdates.events.namedays.NameCelebrations; +import com.alexstyl.specialdates.events.namedays.NamedayLocale; +import com.alexstyl.specialdates.events.namedays.NamedayPreferences; +import com.alexstyl.specialdates.events.namedays.calendar.NamedayCalendar; +import com.alexstyl.specialdates.events.namedays.calendar.NamedayCalendarProvider; +import com.alexstyl.specialdates.images.ImageLoader; +import com.alexstyl.specialdates.permissions.PermissionChecker; +import com.alexstyl.specialdates.transition.FadeInTransition; +import com.alexstyl.specialdates.transition.FadeOutTransition; +import com.alexstyl.specialdates.transition.SimpleTransitionListener; +import com.alexstyl.specialdates.ui.ViewFader; import com.alexstyl.specialdates.ui.base.ThemedActivity; +import com.alexstyl.specialdates.ui.widget.SpacesItemDecoration; +import com.alexstyl.specialdates.permissions.ContactPermissionRequest; +import com.novoda.notils.caster.Views; +import com.novoda.notils.logger.simple.Log; +import com.novoda.notils.meta.AndroidUtils; +import static android.view.View.GONE; +import static com.alexstyl.specialdates.permissions.ContactPermissionRequest.PermissionCallbacks; + +/** + * A fragment in which the user can search for namedays and their contact's birthdays. + *
The fragment has a different logic for when the user has enabled namedays for any language. + * If the user has enabled to display Namedays, the search EditText will give no suggestions. Instead a custom + * suggestion bar on top of the keyboard is going to be given to the user with names. + *

Created by alexstyl on 20/04/15.

+ */ public class SearchActivity extends ThemedActivity { + private static final String KEY_QUERY = "alexstyl:key_query"; + private static final int ID_CONTACTS = 31; + private static final int ID_NAMEDAYS = 32; + private static final int INITAL_COUNT = 5; + + private int searchCounter = INITAL_COUNT; + private SearchBar searchbar; + private RecyclerView resultView; + private RecyclerView namesSuggestionsView; + private SearchResultAdapter adapter; + private NameSuggestionsAdapter namesAdapter; + private String searchQuery; + + private ViewFader fader = new ViewFader(); + private ViewGroup content; + private NamedayPreferences namedayPreferences; + private ContactPermissionRequest permissions; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_search); + Analytics analytics = AnalyticsProvider.getAnalytics(this); + analytics.trackScreen(Screen.SEARCH); + searchbar = Views.findById(this, R.id.search_searchbar); + setSupportActionBar(searchbar); + content = Views.findById(this, R.id.search_content); + resultView = Views.findById(this, android.R.id.list); + resultView.setHasFixedSize(false); + namesSuggestionsView = Views.findById(this, R.id.nameday_suggestions); + PermissionChecker checker = new PermissionChecker(this); + Navigator navigator = new Navigator(this, analytics); + permissions = new ContactPermissionRequest(navigator, checker, permissionCallbacks); + + if (savedInstanceState != null) { + searchQuery = savedInstanceState.getString(KEY_QUERY); + } + + setupSearchField(); + + ImageLoader imageLoader = ImageLoader.createSquareThumbnailLoader(getResources()); + int year = DayDate.today().getYear(); + NamedayLocale locale = NamedayPreferences.newInstance(this).getSelectedLanguage(); + NamedayCalendar namedayCalendar = NamedayCalendarProvider.newInstance(this).loadNamedayCalendarForLocale(locale, year); + + adapter = new SearchResultAdapter(imageLoader, namedayCalendar); + adapter.setSearchResultClickListener(listener); + + resultView.setHasFixedSize(true); + RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(context()); + int spacingInPixels = getResources().getDimensionPixelSize(R.dimen.card_spacing_between); + resultView.addItemDecoration(new SpacesItemDecoration(spacingInPixels, 3)); + resultView.setLayoutManager(mLayoutManager); + resultView.setAdapter(adapter); + + searchbar.setOnBackKeyPressedListener(onBackKeyPressedListener); + + namedayPreferences = NamedayPreferences.newInstance(this); + setupSearchbarHint(namedayPreferences); + + if (namedayPreferences.isEnabled()) { + // we are loading namedays as well + GridLayoutManager namedayManager = new GridLayoutManager(context(), 1, RecyclerView.HORIZONTAL, false); + namesAdapter = NameSuggestionsAdapter.newInstance(context()); + namesAdapter.setOnNameSelectedListener(onNameSelectedListener); + namesSuggestionsView.setHasFixedSize(true); + namesSuggestionsView.setLayoutManager(namedayManager); + namesSuggestionsView.setAdapter(namesAdapter); + + searchbar.setInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); + searchbar.setOnFocusChangeListener(new ToggleVisibilityOnFocus(namesSuggestionsView)); + } else { + namesSuggestionsView.setVisibility(GONE); + } + + if (savedInstanceState == null) { + fader.hideContentOf(searchbar); + ViewTreeObserver viewTreeObserver = searchbar.getViewTreeObserver(); + viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + searchbar.getViewTreeObserver().removeOnGlobalLayoutListener(this); + animateShowSearch(); + } + + private void animateShowSearch() { + TransitionManager.beginDelayedTransition(searchbar, FadeInTransition.createTransition()); + fader.showContent(searchbar); + } + }); + } + + if (!permissions.permissionIsPresent()) { + Log.d("requesting permission"); + permissions.requestForPermission(); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + permissions.onActivityResult(requestCode, resultCode, data); + } + + private void setupSearchbarHint(NamedayPreferences preferences) { + SearchHintCreator searchHintCreator = new SearchHintCreator(getResources(), preferences); + searchbar.setHint(searchHintCreator.createHint()); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_search, menu); + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + MenuItem clearMenuItem = menu.findItem(R.id.action_clear); + clearMenuItem.setVisible(searchbar.hasText()); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + finish(); + return true; + case R.id.action_clear: + onClearPressed(); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void finish() { + if (supportsTransitions()) { + AndroidUtils.requestHideKeyboard(this, searchbar); + exitTransitionWithAction(new Runnable() { + @Override + public void run() { + SearchActivity.super.finish(); + overridePendingTransition(0, 0); + } + }); + } else { + super.finish(); + } + } + + private void exitTransitionWithAction(final Runnable endingAction) { + Transition transition = FadeOutTransition.withAction(new SimpleTransitionListener() { + @Override + public void onTransitionEnd(Transition transition) { + endingAction.run(); + } + }); + + TransitionManager.beginDelayedTransition(searchbar, transition); + fader.hideContentOf(searchbar); + + TransitionManager.beginDelayedTransition(content, new Fade(Fade.OUT)); } + private void onClearPressed() { + searchbar.clearText(); + AndroidUtils.toggleKeyboard(this); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putString(KEY_QUERY, searchQuery); + } + + private void startSearching() { + getSupportLoaderManager().restartLoader(ID_CONTACTS, null, contactSearchCallbacks); + getSupportLoaderManager().restartLoader(ID_NAMEDAYS, null, namedayLoaderCallbacks); + } + + private void clearResults() { + adapter.clearResults(); + if (namedayPreferences.isEnabled()) { + namesAdapter.clearNames(); + } + } + + private void resetSearchCounter() { + searchCounter = INITAL_COUNT; + } + + private void updateNameSuggestions(String text) { + if (namedayPreferences.isEnabled()) { + namesAdapter.getFilter().filter(text); + } + } + + private void setupSearchField() { + searchbar.addTextWatcher(DelayedTextWatcher.newInstance(textUpdatedTextUpdatedCallback)); + } + + private void onNameSet(String name) { + // setting the text to the EditText will trigger the search for the name + AndroidUtils.requestHideKeyboard(this, searchbar); + searchbar.setText(name); + searchbar.clearFocus(); + if (namedayPreferences.isEnabled()) { + namesAdapter.clearNames(); + } + } + + private final NameSuggestionsAdapter.OnNameSelectedListener onNameSelectedListener = new NameSuggestionsAdapter.OnNameSelectedListener() { + @Override + public void onNameSelected(View view, String name) { + onNameSet(name); + } + }; + + private final SearchResultAdapter.SearchResultClickListener listener = new SearchResultAdapter.SearchResultClickListener() { + + @Override + public void onContactClicked(View v, Contact contact) { + contact.displayQuickInfo(context(), v); + } + + @Override + public void onNamedayClicked(View v, int month, int day) { + DayDate dayDate = DayDate.today(); + DateDetailsActivity.startActivity(context(), month, day, dayDate.getYear()); + } + + }; + + private final LoaderManager.LoaderCallbacks namedayLoaderCallbacks = new LoaderManager.LoaderCallbacks() { + + @Override + public Loader onCreateLoader(int id, Bundle args) { + return NamedaysLoader.newInstance(context(), searchQuery); + } + + @Override + public void onLoadFinished(Loader loader, NameCelebrations results) { + adapter.setNamedays(results); + } + + @Override + public void onLoaderReset(Loader loader) { + adapter.setNamedays(NameCelebrations.EMPTY); + } + }; + + private final DelayedTextWatcher.TextUpdatedCallback textUpdatedTextUpdatedCallback = new DelayedTextWatcher.TextUpdatedCallback() { + @Override + public void onTextChanged(String text) { + searchQuery = text; + updateNameSuggestions(text); + resetSearchCounter(); + invalidateOptionsMenu(); + } + + @Override + public void onEmptyTextConfirmed() { + clearResults(); + invalidateOptionsMenu(); + } + + @Override + public void onTextConfirmed(String text) { + startSearching(); + } + }; + + private final LoaderManager.LoaderCallbacks contactSearchCallbacks = new LoaderManager.LoaderCallbacks() { + + @Override + public Loader onCreateLoader(int id, Bundle args) { + adapter.notifyIsLoadingMore(); + return new SearchLoader(context(), searchQuery, searchCounter); + } + + @Override + public void onLoadFinished(Loader loader, SearchResults searchResults) { + if (loader.getId() == ID_CONTACTS) { + adapter.updateSearchResults(searchResults); + } + } + + @Override + public void onLoaderReset(Loader loader) { + if (loader.getId() == ID_CONTACTS) { + adapter.notifyIsLoadingMore(); + } + } + }; + + private final OnBackKeyPressedListener onBackKeyPressedListener = new OnBackKeyPressedListener() { + @Override + public boolean onBackButtonPressed() { + if (searchbar.hasText()) { + // do nothing + return false; + } else { + finish(); + return true; + } + } + }; + + private final PermissionCallbacks permissionCallbacks = new PermissionCallbacks() { + @Override + public void onPermissionGranted() { + // do nothing. + Log.d("permission granted"); + } + + @Override + public void onPermissionDenied() { + Log.d("permission denied"); + namesSuggestionsView.post(new Runnable() { + @Override + public void run() { + finishAffinity(); + } + }); + } + }; + } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/search/SearchBar.java b/mobile/src/main/java/com/alexstyl/specialdates/search/SearchBar.java new file mode 100644 index 00000000..79c0e3aa --- /dev/null +++ b/mobile/src/main/java/com/alexstyl/specialdates/search/SearchBar.java @@ -0,0 +1,81 @@ +package com.alexstyl.specialdates.search; + +import android.content.Context; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.support.annotation.Nullable; +import android.support.v7.widget.Toolbar; +import android.text.TextWatcher; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.TextView; + +import com.alexstyl.specialdates.R; +import com.alexstyl.specialdates.theming.AttributeExtractor; +import com.alexstyl.specialdates.theming.ViewTinter; +import com.novoda.notils.caster.Views; + +public class SearchBar extends Toolbar { + + private BackKeyEditText editText; + + public SearchBar(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + inflate(getContext(), R.layout.merge_searchbar, this); + editText = Views.findById(this, R.id.toolbar_search_edittext); + setBackgroundColor(Color.WHITE); + addTintedUpNavigation(); + + editText.setOnEditorActionListener(onEditorActionListener); + } + + private void addTintedUpNavigation() { + ViewTinter viewTinter = new ViewTinter(new AttributeExtractor()); + Drawable backDrawable = getResources().getDrawable(R.drawable.ic_action_arrow_light_back).mutate(); + Drawable tintedUpDrawable = viewTinter.createAccentTintedDrawable(backDrawable, getContext()); + setNavigationIcon(tintedUpDrawable); + } + + public void addTextWatcher(TextWatcher textWatcher) { + editText.addTextChangedListener(textWatcher); + } + + public void setInputType(int typeTextFlag) { + editText.setInputType(typeTextFlag); + } + + private final TextView.OnEditorActionListener onEditorActionListener = new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if (actionId == EditorInfo.IME_ACTION_SEARCH) { + InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(editText.getWindowToken(), 0); + editText.requestFocus(); + return true; + } + return false; + } + }; + + public void setOnBackKeyPressedListener(OnBackKeyPressedListener listener) { + editText.setOnBackKeyPressedListener(listener); + } + + public void setText(String text) { + editText.setText(text); + } + + public void clearText() { + editText.setText(null); + } + + public boolean hasText() { + return editText.length() > 0; + } + + public void setHint(String hint) { + editText.setHint(hint); + } +} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/search/SearchHintCreator.java b/mobile/src/main/java/com/alexstyl/specialdates/search/SearchHintCreator.java new file mode 100644 index 00000000..c2ae9702 --- /dev/null +++ b/mobile/src/main/java/com/alexstyl/specialdates/search/SearchHintCreator.java @@ -0,0 +1,26 @@ +package com.alexstyl.specialdates.search; + +import android.content.res.Resources; + +import com.alexstyl.specialdates.R; +import com.alexstyl.specialdates.events.namedays.NamedayPreferences; + +public class SearchHintCreator { + + private final Resources resources; + private final NamedayPreferences namedayPreferences; + + public SearchHintCreator(Resources resources, NamedayPreferences namedayPreferences) { + this.resources = resources; + this.namedayPreferences = namedayPreferences; + } + + public String createHint() { + boolean enabled = namedayPreferences.isEnabled(); + if (enabled) { + return resources.getString(R.string.search_hint_contacts_and_namedays); + } else { + return resources.getString(R.string.search_hint_contacts); + } + } +} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/search/SearchResultAdapter.java b/mobile/src/main/java/com/alexstyl/specialdates/search/SearchResultAdapter.java index 53a17d7f..6cff3770 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/search/SearchResultAdapter.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/search/SearchResultAdapter.java @@ -1,51 +1,32 @@ package com.alexstyl.specialdates.search; -import android.content.Context; -import android.content.res.Resources; import android.support.v7.widget.RecyclerView; import android.text.TextUtils; import android.view.View; import android.view.ViewGroup; import com.alexstyl.specialdates.contact.Contact; -import com.alexstyl.specialdates.date.DayDate; +import com.alexstyl.specialdates.events.namedays.NameCelebrations; import com.alexstyl.specialdates.events.namedays.calendar.NamedayCalendar; -import com.alexstyl.specialdates.events.namedays.calendar.NamedayCalendarProvider; -import com.alexstyl.specialdates.events.namedays.NamedayLocale; -import com.alexstyl.specialdates.events.namedays.NamedayPreferences; import com.alexstyl.specialdates.images.ImageLoader; -import com.alexstyl.specialdates.events.namedays.NameCelebrations; import com.novoda.notils.exception.DeveloperError; import java.util.ArrayList; import java.util.List; -public class SearchResultAdapter extends RecyclerView.Adapter { +public final class SearchResultAdapter extends RecyclerView.Adapter { private final List contacts = new ArrayList<>(); private NamedayCard namedayCard = new NamedayCard(); private boolean isLoadingMore; - private final boolean namedayEnabled; private boolean canLoadMore = false; private final ImageLoader imageLoader; private final NamedayCalendar namedayCalendar; private String searchQuery; - public static SearchResultAdapter newInstance(Context context, boolean loadNamedays) { - Resources resources = context.getResources(); - ImageLoader imageLoader = ImageLoader.createSquareThumbnailLoader(resources); - - int year = DayDate.today().getYear(); - NamedayLocale locale = NamedayPreferences.newInstance(context).getSelectedLanguage(); - NamedayCalendar namedayCalendar = NamedayCalendarProvider.newInstance(context) - .loadNamedayCalendarForLocale(locale, year); - return new SearchResultAdapter(loadNamedays, imageLoader, namedayCalendar); - } - - private SearchResultAdapter(boolean loadNamedays, ImageLoader imageLoader, NamedayCalendar namedayCalendar) { - this.namedayEnabled = loadNamedays; + SearchResultAdapter(ImageLoader imageLoader, NamedayCalendar namedayCalendar) { this.imageLoader = imageLoader; this.namedayCalendar = namedayCalendar; } @@ -141,7 +122,7 @@ private boolean containsNoResults() { public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == VIEWTYPE_CONTACTVIEW) { - return SearchResultContactViewHolder.createFor(parent, namedayCalendar, imageLoader, namedayEnabled); + return SearchResultContactViewHolder.createFor(parent, namedayCalendar, imageLoader); } if (viewType == VIEWTYPE_NAMEDAYS_VIEW) { return SearchResultNamedayViewHolder.createFor(parent); diff --git a/mobile/src/main/java/com/alexstyl/specialdates/search/SearchResultContactViewHolder.java b/mobile/src/main/java/com/alexstyl/specialdates/search/SearchResultContactViewHolder.java index aca93e8a..5d272da4 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/search/SearchResultContactViewHolder.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/search/SearchResultContactViewHolder.java @@ -25,22 +25,19 @@ class SearchResultContactViewHolder extends RecyclerView.ViewHolder { private final TextView nameday; private final ImageLoader imageLoader; - private boolean namedayEnabled; public static SearchResultContactViewHolder createFor(ViewGroup parent, NamedayCalendar namedayCalendar, - ImageLoader imageLoader, - boolean namedayEnabled) { + ImageLoader imageLoader) { LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); View view = layoutInflater.inflate(R.layout.row_search_result_contact, parent, false); - return new SearchResultContactViewHolder(view, namedayCalendar, imageLoader, namedayEnabled); + return new SearchResultContactViewHolder(view, namedayCalendar, imageLoader); } - SearchResultContactViewHolder(View convertView, NamedayCalendar namedayCalendar, ImageLoader imageLoader, boolean namedayEnabled) { + SearchResultContactViewHolder(View convertView, NamedayCalendar namedayCalendar, ImageLoader imageLoader) { super(convertView); this.namedayCalendar = namedayCalendar; this.imageLoader = imageLoader; - this.namedayEnabled = namedayEnabled; this.displayName = (TextView) convertView.findViewById(R.id.contact_name); this.birthday = (TextView) convertView.findViewById(R.id.birthday_label); this.nameday = (TextView) convertView.findViewById(R.id.nameday_label); @@ -59,15 +56,12 @@ void bind(final Contact contact, final SearchResultAdapter.SearchResultClickList bindNamedays(contact); itemView.setOnClickListener( - new View.OnClickListener() - - { + new View.OnClickListener() { @Override public void onClick(View v) { mListener.onContactClicked(v, contact); } } - ); } @@ -122,7 +116,7 @@ private static String getEventString(Context context, int stringRes, DayDate dat } private boolean noEventsToDisplay(NameCelebrations namedays) { - return !namedayEnabled || namedays.containsNoDate(); + return namedays.containsNoDate(); } private Context getContext() { diff --git a/mobile/src/main/java/com/alexstyl/specialdates/search/ToggleVisibilityOnFocus.java b/mobile/src/main/java/com/alexstyl/specialdates/search/ToggleVisibilityOnFocus.java new file mode 100644 index 00000000..be59a052 --- /dev/null +++ b/mobile/src/main/java/com/alexstyl/specialdates/search/ToggleVisibilityOnFocus.java @@ -0,0 +1,23 @@ +package com.alexstyl.specialdates.search; + +import android.view.View; + +import static android.view.View.GONE; +import static android.view.View.VISIBLE; + +public class ToggleVisibilityOnFocus implements View.OnFocusChangeListener { + private final View view; + + public ToggleVisibilityOnFocus(View view) { + this.view = view; + } + + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (hasFocus) { + view.setVisibility(VISIBLE); + } else { + view.setVisibility(GONE); + } + } +} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/service/DailyReminderIntentService.java b/mobile/src/main/java/com/alexstyl/specialdates/service/DailyReminderIntentService.java index fa8c311f..e82b1301 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/service/DailyReminderIntentService.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/service/DailyReminderIntentService.java @@ -1,43 +1,46 @@ package com.alexstyl.specialdates.service; +import android.Manifest; import android.app.AlarmManager; import android.app.IntentService; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; -import com.alexstyl.specialdates.events.ContactEvents; +import com.alexstyl.specialdates.BuildConfig; import com.alexstyl.specialdates.date.DayDate; +import com.alexstyl.specialdates.dailyreminder.DailyReminderDebugPreferences; +import com.alexstyl.specialdates.events.ContactEvents; import com.alexstyl.specialdates.events.bankholidays.BankHoliday; import com.alexstyl.specialdates.events.bankholidays.BankHolidaysPreferences; import com.alexstyl.specialdates.events.bankholidays.GreekBankHolidays; -import com.alexstyl.specialdates.events.namedays.calendar.EasterCalculator; -import com.alexstyl.specialdates.events.namedays.calendar.NamedayCalendar; -import com.alexstyl.specialdates.events.namedays.calendar.NamedayCalendarProvider; import com.alexstyl.specialdates.events.namedays.NamedayLocale; import com.alexstyl.specialdates.events.namedays.NamedayPreferences; import com.alexstyl.specialdates.events.namedays.NamesInADate; +import com.alexstyl.specialdates.events.namedays.calendar.EasterCalculator; +import com.alexstyl.specialdates.events.namedays.calendar.NamedayCalendar; +import com.alexstyl.specialdates.events.namedays.calendar.NamedayCalendarProvider; +import com.alexstyl.specialdates.permissions.PermissionChecker; import com.alexstyl.specialdates.receiver.EventReceiver; import com.alexstyl.specialdates.settings.MainPreferenceActivity; import com.alexstyl.specialdates.util.Notifier; +import com.novoda.notils.logger.simple.Log; import java.util.Calendar; import java.util.List; /** - * Finds all contacts that celebrate the day, and creates a status bar - * notification to notify the user - * - * @author Alex + * A service that looks up all events on the specified date and notifies the user about it + *

NOTE: The DailyReminder service will display

*/ public class DailyReminderIntentService extends IntentService { private static final int REQUEST_CODE = 0; - private PeopleEventsProvider provider; private NamedayPreferences namedayPreferences; private NamedayCalendarProvider namedayCalendarProvider; private BankHolidaysPreferences bankHolidaysPreferences; + private PermissionChecker checker; public DailyReminderIntentService() { super("DailyReminder"); @@ -51,18 +54,26 @@ public static void startService(Context context) { } @Override - protected void onHandleIntent(Intent intent) { - rescheduleAlarm(this); - provider = PeopleEventsProvider.newInstance(this); + public void onCreate() { + super.onCreate(); notifier = Notifier.newInstance(this); namedayPreferences = NamedayPreferences.newInstance(this); namedayCalendarProvider = NamedayCalendarProvider.newInstance(this); bankHolidaysPreferences = BankHolidaysPreferences.newInstance(this); + checker = new PermissionChecker(this); + } - DayDate today = DayDate.today(); - ContactEvents celebrationDate = provider.getCelebrationDateFor(today); - if (containsAnyContactEvents(celebrationDate)) { - notifier.forDailyReminder(celebrationDate); + @Override + protected void onHandleIntent(Intent intent) { + rescheduleAlarm(this); + PeopleEventsProvider provider = PeopleEventsProvider.newInstance(this); + DayDate today = getDayDateToDisplay(); + + if (hasContactPermission()) { + ContactEvents celebrationDate = provider.getCelebrationDateFor(today); + if (containsAnyContactEvents(celebrationDate)) { + notifier.forDailyReminder(celebrationDate); + } } if (namedaysAreEnabledForAllCases()) { notifyForNamedaysFor(today); @@ -70,7 +81,22 @@ protected void onHandleIntent(Intent intent) { if (bankholidaysAreEnabled()) { notifyForBankholidaysFor(today); } + } + + private boolean hasContactPermission() { + return checker.hasPermission(Manifest.permission.READ_CONTACTS); + } + private DayDate getDayDateToDisplay() { + if (BuildConfig.DEBUG) { + DailyReminderDebugPreferences preferences = DailyReminderDebugPreferences.newInstance(this); + if (preferences.isFakeDateEnabled()) { + DayDate selectedDate = preferences.getSelectedDate(); + Log.d("Using DEBUG date to display: " + selectedDate); + return selectedDate; + } + } + return DayDate.today(); } private boolean containsAnyContactEvents(ContactEvents celebrationDate) { diff --git a/mobile/src/main/java/com/alexstyl/specialdates/settings/DailyReminderFragment.java b/mobile/src/main/java/com/alexstyl/specialdates/settings/DailyReminderFragment.java index e0d3cee5..d294e667 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/settings/DailyReminderFragment.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/settings/DailyReminderFragment.java @@ -1,14 +1,20 @@ package com.alexstyl.specialdates.settings; +import android.Manifest; +import android.annotation.TargetApi; +import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.media.Ringtone; import android.media.RingtoneManager; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.preference.CheckBoxPreference; import android.preference.Preference; import android.preference.Preference.OnPreferenceChangeListener; +import android.support.v4.app.ActivityCompat; import android.text.TextUtils; import android.text.format.DateFormat; import android.view.Menu; @@ -16,33 +22,34 @@ import com.alexstyl.specialdates.R; import com.alexstyl.specialdates.analytics.Action; -import com.alexstyl.specialdates.analytics.Analytics; import com.alexstyl.specialdates.analytics.ActionWithParameters; -import com.alexstyl.specialdates.analytics.Firebase; +import com.alexstyl.specialdates.analytics.Analytics; +import com.alexstyl.specialdates.analytics.AnalyticsProvider; import com.alexstyl.specialdates.service.DailyReminderIntentService; +import com.alexstyl.specialdates.ui.base.MementoPreferenceFragment; import com.alexstyl.specialdates.ui.widget.TimePreference; import com.alexstyl.specialdates.util.Utils; import java.util.Calendar; -public class DailyReminderFragment extends MyPreferenceFragment { +import static android.Manifest.permission.READ_EXTERNAL_STORAGE; + +public class DailyReminderFragment extends MementoPreferenceFragment { + private static final int EXTERNAL_STORAGE_REQUEST_CODE = 15; private CheckBoxPreference enablePreference; - private Preference ringtonePreference; - private Preference vibratePreference; + private RingtonePreference ringtonePreference; private TimePreference timePreference; private Analytics analytics; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - analytics = Firebase.get(getActivity()); + analytics = AnalyticsProvider.getAnalytics(getActivity()); setHasOptionsMenu(true); addPreferencesFromResource(R.xml.preference_dailyreminder); - // the switch is controlled by the activity - - enablePreference = (CheckBoxPreference) findPreference(getString(R.string.key_daily_reminder)); + enablePreference = findPreference(R.string.key_daily_reminder); enablePreference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { @@ -61,7 +68,24 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { }); - ringtonePreference = findPreference(getString(R.string.key_daily_reminder_ringtone)); + ringtonePreference = findPreference(R.string.key_daily_reminder_ringtone); + ringtonePreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @TargetApi(Build.VERSION_CODES.M) + @Override + public boolean onPreferenceClick(Preference preference) { + if (isExternalStoragePermissionPressent()) { + // the permission exists. Let the system handle the event + return false; + } else { + requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, EXTERNAL_STORAGE_REQUEST_CODE); + return true; + } + } + + public boolean isExternalStoragePermissionPressent() { + return ActivityCompat.checkSelfPermission(getActivity(), READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; + } + }); ringtonePreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @Override @@ -71,7 +95,7 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { } }); - timePreference = (TimePreference) findPreference(getString(R.string.key_daily_reminder_time)); + timePreference = findPreference(R.string.key_daily_reminder_time); timePreference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { @Override @@ -84,14 +108,16 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { } }); - vibratePreference = findPreference(getString(R.string.key_daily_reminder_vibrate_enabled)); + hideVibratorSettingIfNotPresent(); + } + private void hideVibratorSettingIfNotPresent() { + Preference vibratePreference = findPreference(getString(R.string.key_daily_reminder_vibrate_enabled)); if (!Utils.hasVibrator(getActivity())) { // hide the vibrator preference if the device doesn't support // vibration getPreferenceScreen().removePreference(vibratePreference); } - } @Override @@ -152,6 +178,14 @@ public static CharSequence getHour(Context context, Calendar cal) { } + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == EXTERNAL_STORAGE_REQUEST_CODE && resultCode == Activity.RESULT_OK) { + ringtonePreference.onClick(); + } + } + private void updateRingtoneSummary(String uri) { String name = null; if (!TextUtils.isEmpty(uri)) { diff --git a/mobile/src/main/java/com/alexstyl/specialdates/settings/MainPreferenceActivity.java b/mobile/src/main/java/com/alexstyl/specialdates/settings/MainPreferenceActivity.java index e673df7f..3f97282d 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/settings/MainPreferenceActivity.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/settings/MainPreferenceActivity.java @@ -25,7 +25,7 @@ public class MainPreferenceActivity extends MementoPreferenceActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - Themer.get().initialiseActivity(this); + Themer.get(this).initialiseActivity(this); setContentView(R.layout.activity_settings); MementoToolbar toolbar = Views.findById(this, R.id.memento_toolbar); diff --git a/mobile/src/main/java/com/alexstyl/specialdates/settings/MainPreferenceFragment.java b/mobile/src/main/java/com/alexstyl/specialdates/settings/MainPreferenceFragment.java index 97c27a30..6400036d 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/settings/MainPreferenceFragment.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/settings/MainPreferenceFragment.java @@ -9,7 +9,8 @@ import com.alexstyl.specialdates.R; import com.alexstyl.specialdates.analytics.Action; import com.alexstyl.specialdates.analytics.ActionWithParameters; -import com.alexstyl.specialdates.analytics.Firebase; +import com.alexstyl.specialdates.analytics.Analytics; +import com.alexstyl.specialdates.analytics.AnalyticsProvider; import com.alexstyl.specialdates.events.namedays.NamedayLocale; import com.alexstyl.specialdates.events.namedays.NamedayPreferences; import com.alexstyl.specialdates.theming.MementoTheme; @@ -24,12 +25,13 @@ final public class MainPreferenceFragment extends MementoPreferenceFragment { private ListPreference namedayLanguageListPreferences; private NamedayPreferences namedaysPreferences; - private ThemingPreferences themingPreferences = new ThemingPreferences(); + private ThemingPreferences themingPreferences; + private Preference appThemePreference; private MainPreferenceActivity activity; - private Firebase analytics; + private Analytics analytics; @Override public void onAttach(Activity activity) { @@ -41,7 +43,8 @@ public void onAttach(Activity activity) { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.preference_main); - analytics = Firebase.get(getActivity()); + analytics = AnalyticsProvider.getAnalytics(getActivity()); + themingPreferences = ThemingPreferences.newInstance(getActivity()); Preference bankholidaysLanguage = findPreference(R.string.key_bankholidays_language); bankholidaysLanguage.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { @Override diff --git a/mobile/src/main/java/com/alexstyl/specialdates/settings/RingtonePreference.java b/mobile/src/main/java/com/alexstyl/specialdates/settings/RingtonePreference.java new file mode 100644 index 00000000..296ecc08 --- /dev/null +++ b/mobile/src/main/java/com/alexstyl/specialdates/settings/RingtonePreference.java @@ -0,0 +1,25 @@ +package com.alexstyl.specialdates.settings; + +import android.content.Context; +import android.util.AttributeSet; + +public class RingtonePreference extends android.preference.RingtonePreference { + private OnPreferenceClickListener onPreferenceClickListener; + + public RingtonePreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public void setOnPreferenceClickListener(OnPreferenceClickListener onPreferenceClickListener) { + // don't call super. We'll handle the clicks ourselves + this.onPreferenceClickListener = onPreferenceClickListener; + } + + @Override + protected void onClick() { + if (!onPreferenceClickListener.onPreferenceClick(this)) { + super.onClick(); + } + } +} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/support/RateDialog.java b/mobile/src/main/java/com/alexstyl/specialdates/support/RateDialog.java index 74752226..fcfab539 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/support/RateDialog.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/support/RateDialog.java @@ -10,6 +10,7 @@ import com.alexstyl.specialdates.Navigator; import com.alexstyl.specialdates.R; +import com.alexstyl.specialdates.analytics.AnalyticsProvider; import com.alexstyl.specialdates.ui.base.MementoActivity; import com.novoda.notils.caster.Views; @@ -17,17 +18,18 @@ public class RateDialog extends MementoActivity { private final String smiley = " " + Emoticon.SMILEY.asText(); private AskForSupport askForSupport; + private Navigator navigator; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_rate_dialog); askForSupport = new AskForSupport(context()); - + navigator = new Navigator(this, AnalyticsProvider.getAnalytics(this)); Views.findById(this, R.id.support_rate_button).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - new Navigator(v.getContext()).toPlayStore(); + navigator.toPlayStore(); Toast.makeText(context(), R.string.support_thanks_for_rating, Toast.LENGTH_LONG).show(); askForSupport.onRateEnd(); finish(); diff --git a/mobile/src/main/java/com/alexstyl/specialdates/support/SupportDonateDialog.java b/mobile/src/main/java/com/alexstyl/specialdates/support/SupportDonateDialog.java index 7426cdb6..5347bc75 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/support/SupportDonateDialog.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/support/SupportDonateDialog.java @@ -15,7 +15,7 @@ import com.alexstyl.specialdates.analytics.Action; import com.alexstyl.specialdates.analytics.Analytics; import com.alexstyl.specialdates.analytics.ActionWithParameters; -import com.alexstyl.specialdates.analytics.Firebase; +import com.alexstyl.specialdates.analytics.AnalyticsProvider; import com.alexstyl.specialdates.billing.util.IabHelper; import com.alexstyl.specialdates.billing.util.IabResult; import com.alexstyl.specialdates.billing.util.Inventory; @@ -80,7 +80,7 @@ public void onQueryInventoryFinished(IabResult result, public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setTitle(R.string.donate); - analytics = Firebase.get(this); + analytics = AnalyticsProvider.getAnalytics(this); mTokens = new HashMap<>(); if (BuildConfig.DEBUG) { mTokens.put("1", ITEM_TEST); @@ -216,8 +216,4 @@ public void onDestroy() { } } - public static void displayDialog(Context context) { - Intent intent = new Intent(context, SupportDonateDialog.class); - context.startActivity(intent); - } } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/support/SupportTranslateDialog.java b/mobile/src/main/java/com/alexstyl/specialdates/support/SupportTranslateDialog.java index eac9b76a..ebb00911 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/support/SupportTranslateDialog.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/support/SupportTranslateDialog.java @@ -1,8 +1,6 @@ package com.alexstyl.specialdates.support; -import android.annotation.TargetApi; import android.content.Context; -import android.os.Build; import android.os.Bundle; import android.view.View; import android.widget.Toast; @@ -10,7 +8,6 @@ import com.alexstyl.specialdates.R; import com.alexstyl.specialdates.theming.Themer; import com.alexstyl.specialdates.ui.base.ThemedActivity; -import com.alexstyl.specialdates.util.Utils; import com.novoda.notils.caster.Views; public class SupportTranslateDialog extends ThemedActivity { @@ -19,7 +16,7 @@ public class SupportTranslateDialog extends ThemedActivity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.dialog_translate, Themer.get().getCurrentTheme()); + setContentView(R.layout.dialog_translate, Themer.get(this).getCurrentTheme()); Views.findById(this, android.R.id.content).setBackgroundColor(getResources().getColor(android.R.color.transparent)); Views.findById(this, R.id.button_ok).setOnClickListener(new View.OnClickListener() { @@ -38,18 +35,10 @@ public void onClick(View v) { } - @TargetApi(Build.VERSION_CODES.HONEYCOMB) public boolean copyLinkToClipboard() { - if (Utils.hasHoneycomb()) { - android.content.ClipboardManager clipboard = (android.content.ClipboardManager) - getSystemService(Context.CLIPBOARD_SERVICE); - android.content.ClipData clip = android.content.ClipData - .newPlainText(getResources().getString(R.string.app_name), getString(R.string.link_translate_short)); - clipboard.setPrimaryClip(clip); - } else { - android.text.ClipboardManager clipboard = (android.text.ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); - clipboard.setText(getString(R.string.link_translate_short)); - } + android.content.ClipboardManager clipboard = (android.content.ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); + android.content.ClipData clip = android.content.ClipData.newPlainText(getResources().getString(R.string.app_name), getString(R.string.link_translate_short)); + clipboard.setPrimaryClip(clip); return true; } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/theming/MementoTheme.java b/mobile/src/main/java/com/alexstyl/specialdates/theming/MementoTheme.java index b2276ee6..461fb936 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/theming/MementoTheme.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/theming/MementoTheme.java @@ -15,8 +15,14 @@ public enum MementoTheme { EGGPLANT_GREEN(withThemeName(R.string.theme_Eggplant), R.style.Theme_Memento_Eggplant), MONOCHROME(withThemeName(R.string.theme_Monochrome), R.style.Theme_Memento_Monochrome), SYSTEMO(withThemeName(R.string.theme_Systemo), R.style.Theme_Memento_Systemo), - AMBER(withThemeName(R.string.theme_Amber), R.style.Theme_Memento_Amber), - ; + AMBER(withThemeName(R.string.theme_Amber), R.style.Theme_Memento_Amber); + private final String themeName; + @StyleRes + private final int styleResId; + + private static String withThemeName(@StringRes int themNameResId) { + return MementoApplication.getContext().getString(themNameResId); + } public static MementoTheme fromName(@NonNull String themeName) { for (MementoTheme mementoTheme : values()) { @@ -28,14 +34,6 @@ public static MementoTheme fromName(@NonNull String themeName) { throw new DeveloperError("No theme exists with the name [%s]", themeName); } - private static String withThemeName(@StringRes int themNameResId) { - return MementoApplication.getAppContext().getString(themNameResId); - } - - private final String themeName; - @StyleRes - private final int styleResId; - MementoTheme(@NonNull String themeName, @StyleRes int styleResId) { this.themeName = themeName; this.styleResId = styleResId; diff --git a/mobile/src/main/java/com/alexstyl/specialdates/theming/Themer.java b/mobile/src/main/java/com/alexstyl/specialdates/theming/Themer.java index 1c135be0..7531406e 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/theming/Themer.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/theming/Themer.java @@ -14,9 +14,10 @@ public class Themer { private static Themer INSTANCE; - public static Themer get() { + public static Themer get(Context context) { if (INSTANCE == null) { - INSTANCE = new Themer(new ThemingPreferences(), new AttributeExtractor()); + ThemingPreferences preferences = ThemingPreferences.newInstance(context); + INSTANCE = new Themer(preferences, new AttributeExtractor()); } return INSTANCE; } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/theming/ThemingPreferences.java b/mobile/src/main/java/com/alexstyl/specialdates/theming/ThemingPreferences.java index fc55ace3..47f1ce9d 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/theming/ThemingPreferences.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/theming/ThemingPreferences.java @@ -1,16 +1,21 @@ package com.alexstyl.specialdates.theming; +import android.content.Context; + import com.alexstyl.specialdates.EasyPreferences; -import com.alexstyl.specialdates.MementoApplication; import com.alexstyl.specialdates.R; -public class ThemingPreferences { +public final class ThemingPreferences { private static final String DEFAULT_THEME = MementoTheme.CHERRY_RED.getThemeName(); private final EasyPreferences preferences; - public ThemingPreferences() { - this.preferences = EasyPreferences.createForDefaultPreferences(MementoApplication.getAppContext()); + public static ThemingPreferences newInstance(Context context) { + return new ThemingPreferences(EasyPreferences.createForDefaultPreferences(context)); + } + + private ThemingPreferences(EasyPreferences defaultPreferences) { + this.preferences = defaultPreferences; } public MementoTheme getSelectedTheme() { diff --git a/mobile/src/main/java/com/alexstyl/specialdates/theming/ViewTinter.java b/mobile/src/main/java/com/alexstyl/specialdates/theming/ViewTinter.java new file mode 100644 index 00000000..5bab4e53 --- /dev/null +++ b/mobile/src/main/java/com/alexstyl/specialdates/theming/ViewTinter.java @@ -0,0 +1,19 @@ +package com.alexstyl.specialdates.theming; + +import android.content.Context; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; + +public class ViewTinter { + private final AttributeExtractor extractor; + + public ViewTinter(AttributeExtractor extractor) { + this.extractor = extractor; + } + + public Drawable createAccentTintedDrawable(Drawable mutate, Context context) { + int accentColor = extractor.extractAccentColorFrom(context); + mutate.setColorFilter(accentColor, PorterDuff.Mode.SRC_IN); + return mutate; + } +} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/transition/FadeInTransition.java b/mobile/src/main/java/com/alexstyl/specialdates/transition/FadeInTransition.java new file mode 100644 index 00000000..eb363f12 --- /dev/null +++ b/mobile/src/main/java/com/alexstyl/specialdates/transition/FadeInTransition.java @@ -0,0 +1,19 @@ +package com.alexstyl.specialdates.transition; + +import android.support.transition.AutoTransition; +import android.support.transition.Transition; + +public class FadeInTransition extends AutoTransition { + + private static final int FADE_IN_DURATION = 200; + + private FadeInTransition() { + // force callers to call the factory method to instantiate this class + } + + public static Transition createTransition() { + AutoTransition transition = new AutoTransition(); + transition.setDuration(FADE_IN_DURATION); + return transition; + } +} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/transition/FadeOutTransition.java b/mobile/src/main/java/com/alexstyl/specialdates/transition/FadeOutTransition.java new file mode 100644 index 00000000..04632563 --- /dev/null +++ b/mobile/src/main/java/com/alexstyl/specialdates/transition/FadeOutTransition.java @@ -0,0 +1,25 @@ +package com.alexstyl.specialdates.transition; + +import android.support.transition.AutoTransition; +import android.support.transition.Transition; + +public class FadeOutTransition extends AutoTransition { + + private FadeOutTransition() { + // force callers to call the factory method to instantiate this class + } + + private static final int FADE_OUT_DURATION = 250; + + /** + * Creates a AutoTransition that calls the {@linkplain Transition.TransitionListener#onTransitionEnd(Transition)} + * of the passing Listener when complete + */ + public static Transition withAction(TransitionListener finishingAction) { + AutoTransition transition = new AutoTransition(); + transition.setDuration(FADE_OUT_DURATION); + transition.addListener(finishingAction); + return transition; + } + +} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/transition/SimpleTransitionListener.java b/mobile/src/main/java/com/alexstyl/specialdates/transition/SimpleTransitionListener.java new file mode 100644 index 00000000..570412cf --- /dev/null +++ b/mobile/src/main/java/com/alexstyl/specialdates/transition/SimpleTransitionListener.java @@ -0,0 +1,32 @@ +package com.alexstyl.specialdates.transition; + +import android.support.transition.Transition; + +import static android.support.transition.Transition.TransitionListener; + +public class SimpleTransitionListener implements TransitionListener { + @Override + public void onTransitionStart(Transition transition) { + // do nothing + } + + @Override + public void onTransitionPause(Transition transition) { + // do nothing + } + + @Override + public void onTransitionResume(Transition transition) { + // do nothing + } + + @Override + public void onTransitionEnd(Transition transition) { + // do nothing + } + + @Override + public void onTransitionCancel(Transition transition) { + // do nothing + } +} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/ui/ThemeMonitor.java b/mobile/src/main/java/com/alexstyl/specialdates/ui/ThemeMonitor.java new file mode 100644 index 00000000..0876953d --- /dev/null +++ b/mobile/src/main/java/com/alexstyl/specialdates/ui/ThemeMonitor.java @@ -0,0 +1,25 @@ +package com.alexstyl.specialdates.ui; + +import com.alexstyl.specialdates.theming.MementoTheme; +import com.alexstyl.specialdates.theming.ThemingPreferences; + +public final class ThemeMonitor { + + private final MementoTheme theme; + private final ThemingPreferences preferences; + + public static ThemeMonitor startMonitoring(ThemingPreferences preferences) { + MementoTheme currentTheme = preferences.getSelectedTheme(); + return new ThemeMonitor(currentTheme, preferences); + } + + private ThemeMonitor(MementoTheme theme, ThemingPreferences preferences) { + this.theme = theme; + this.preferences = preferences; + } + + public boolean hasThemeChanged() { + MementoTheme newTheme = preferences.getSelectedTheme(); + return newTheme != theme; + } +} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/ui/ThemeReapplier.java b/mobile/src/main/java/com/alexstyl/specialdates/ui/ThemeReapplier.java deleted file mode 100644 index 4d1106ae..00000000 --- a/mobile/src/main/java/com/alexstyl/specialdates/ui/ThemeReapplier.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.alexstyl.specialdates.ui; - -import android.content.Context; - -import com.alexstyl.specialdates.theming.MementoTheme; -import com.alexstyl.specialdates.theming.ThemingPreferences; - -public class ThemeReapplier { - - private final Context context; - - private final MementoTheme theme; - private final ThemingPreferences themingPreferences; - - public ThemeReapplier(Context context) { - this.context = context.getApplicationContext(); - this.themingPreferences = new ThemingPreferences(); - this.theme = themingPreferences.getSelectedTheme(); - } - - public boolean hasThemeChanged() { - MementoTheme newTheme = themingPreferences.getSelectedTheme(); - return newTheme != theme; - } -} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/ui/ViewFader.java b/mobile/src/main/java/com/alexstyl/specialdates/ui/ViewFader.java new file mode 100644 index 00000000..1f7a78c8 --- /dev/null +++ b/mobile/src/main/java/com/alexstyl/specialdates/ui/ViewFader.java @@ -0,0 +1,22 @@ +package com.alexstyl.specialdates.ui; + +import android.view.ViewGroup; + +import static android.view.View.GONE; +import static android.view.View.VISIBLE; + +public final class ViewFader { + + public void hideContentOf(ViewGroup viewGroup) { + for (int i = 0; i < viewGroup.getChildCount(); i++) { + viewGroup.getChildAt(i).setVisibility(GONE); + } + } + + public void showContent(ViewGroup viewGroup) { + for (int i = 0; i < viewGroup.getChildCount(); i++) { + viewGroup.getChildAt(i).setVisibility(VISIBLE); + } + } + +} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/ui/activity/MainActivity.java b/mobile/src/main/java/com/alexstyl/specialdates/ui/activity/MainActivity.java index 3d65b75e..d0bedcea 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/ui/activity/MainActivity.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/ui/activity/MainActivity.java @@ -1,28 +1,33 @@ package com.alexstyl.specialdates.ui.activity; -import android.content.Intent; import android.os.Bundle; import android.support.design.widget.FloatingActionButton; -import android.support.v7.widget.Toolbar; import android.view.Menu; import android.view.MenuItem; import android.view.View; +import android.view.ViewGroup; +import com.alexstyl.specialdates.Navigator; import com.alexstyl.specialdates.R; -import com.alexstyl.specialdates.about.AboutActivity; -import com.alexstyl.specialdates.addevent.AddBirthdayActivity; import com.alexstyl.specialdates.analytics.Analytics; -import com.alexstyl.specialdates.analytics.Firebase; +import com.alexstyl.specialdates.analytics.AnalyticsProvider; import com.alexstyl.specialdates.analytics.Screen; -import com.alexstyl.specialdates.settings.MainPreferenceActivity; +import com.alexstyl.specialdates.events.namedays.NamedayPreferences; +import com.alexstyl.specialdates.search.SearchHintCreator; import com.alexstyl.specialdates.support.AskForSupport; import com.alexstyl.specialdates.support.SupportDonateDialog; -import com.alexstyl.specialdates.ui.ThemeReapplier; +import com.alexstyl.specialdates.theming.ThemingPreferences; +import com.alexstyl.specialdates.ui.ThemeMonitor; +import com.alexstyl.specialdates.ui.ViewFader; import com.alexstyl.specialdates.ui.base.ThemedActivity; -import com.alexstyl.specialdates.upcoming.UpcomingEventsFragment; +import com.alexstyl.specialdates.upcoming.ExposedSearchToolbar; +import com.alexstyl.specialdates.upcoming.SearchTransitioner; import com.alexstyl.specialdates.util.Notifier; import com.alexstyl.specialdates.widgetprovider.TodayWidgetProvider; import com.novoda.notils.caster.Views; +import com.novoda.notils.meta.AndroidUtils; + +import static android.view.View.OnClickListener; /* * The activity was first launched with MainActivity being in package.ui.activity @@ -31,49 +36,49 @@ public class MainActivity extends ThemedActivity { private Notifier notifier; - private UpcomingEventsFragment upcomingEventsFragment; private AskForSupport askForSupport; - private ThemeReapplier reapplier; - private Analytics analytics; + private ThemeMonitor themeMonitor; + + private Navigator navigator; + + private SearchTransitioner searchTransitioner; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - reapplier = new ThemeReapplier(this); - analytics = Firebase.get(this); + themeMonitor = ThemeMonitor.startMonitoring(ThemingPreferences.newInstance(this)); + Analytics analytics = AnalyticsProvider.getAnalytics(this); analytics.trackScreen(Screen.HOME); - Toolbar toolbar = Views.findById(this, R.id.memento_toolbar); + navigator = new Navigator(this, analytics); + + ExposedSearchToolbar toolbar = Views.findById(this, R.id.memento_toolbar); + toolbar.setOnClickListener(onToolbarClickListener); setSupportActionBar(toolbar); + ViewGroup activityContent = Views.findById(this, R.id.main_content); + searchTransitioner = new SearchTransitioner(this, navigator, activityContent, toolbar, new ViewFader()); + notifier = Notifier.newInstance(this); - FloatingActionButton floatingActionButton = Views.findById(this, R.id.fab_add); - floatingActionButton.setOnClickListener(startAddBirthdayOnClick); + FloatingActionButton addBirthdayFAB = Views.findById(this, R.id.main_birthday_add_fab); + addBirthdayFAB.setOnClickListener(startAddBirthdayOnClick); askForSupport = new AskForSupport(this); - upcomingEventsFragment = (UpcomingEventsFragment) getSupportFragmentManager().findFragmentById(R.id.upcoming); + SearchHintCreator hintCreator = new SearchHintCreator(getResources(), NamedayPreferences.newInstance(this)); + setTitle(hintCreator.createHint()); } @Override protected void onResume() { super.onResume(); - if (reapplier.hasThemeChanged()) { + if (themeMonitor.hasThemeChanged()) { reapplyTheme(); } else if (askForSupport.shouldAskForRating()) { askForSupport.askForRatingFromUser(this); } - } - - @Override - protected void onPostResume() { - super.onPostResume(); - } - - private void startAddBirthdayActivity() { - Intent intent = new Intent(this, AddBirthdayActivity.class); - startActivity(intent); + searchTransitioner.onActivityResumed(); } @Override @@ -89,37 +94,22 @@ public boolean onCreateOptionsMenu(Menu menu) { public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_settings: - openSettings(); + navigator.toSettings(); break; case R.id.action_about: - openAboutScreen(); + navigator.toAbout(); break; case R.id.action_donate: - openDonateDialog(); + navigator.toDonateDialog(); break; } return super.onOptionsItemSelected(item); } - private void openDonateDialog() { - analytics.trackScreen(Screen.DONATE); - SupportDonateDialog.displayDialog(this); - } - - private void openAboutScreen() { - analytics.trackScreen(Screen.ABOUT); - startActivity(new Intent(this, AboutActivity.class)); - - } - - private void openSettings() { - analytics.trackScreen(Screen.SETTINGS); - startActivity(new Intent(this, MainPreferenceActivity.class)); - } - @Override public boolean onSearchRequested() { - return upcomingEventsFragment.onSearchRequested(); + searchTransitioner.transitionToSearch(); + return true; } @Override @@ -136,10 +126,19 @@ protected void onDestroy() { } } - private final View.OnClickListener startAddBirthdayOnClick = new View.OnClickListener() { + private final OnClickListener onToolbarClickListener = new OnClickListener() { + @Override + public void onClick(View v) { + AndroidUtils.toggleKeyboard(v.getContext()); + onSearchRequested(); + } + + }; + + private final OnClickListener startAddBirthdayOnClick = new OnClickListener() { @Override public void onClick(View v) { - startAddBirthdayActivity(); + navigator.toAddBirthday(); } }; } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/ui/base/MementoActivity.java b/mobile/src/main/java/com/alexstyl/specialdates/ui/base/MementoActivity.java index ec2c5c07..dace1112 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/ui/base/MementoActivity.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/ui/base/MementoActivity.java @@ -3,12 +3,16 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; +import android.support.v4.app.Fragment; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; import android.view.MenuItem; +import com.alexstyl.specialdates.util.Utils; import com.novoda.notils.exception.DeveloperError; +import java.util.List; + public class MementoActivity extends AppCompatActivity { /** @@ -40,6 +44,17 @@ public boolean onOptionsItemSelected(MenuItem item) { return super.onOptionsItemSelected(item); } + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + List fragments = getSupportFragmentManager().getFragments(); + if (fragments != null) { + for (Fragment fragment : fragments) { + fragment.onActivityResult(requestCode, resultCode, data); + } + } + } + private boolean handleUp() { if (!shouldUseHomeAsUp()) { return false; @@ -61,4 +76,8 @@ protected Context context() { return this; } + protected boolean supportsTransitions() { + return Utils.hasKitKat(); + } + } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/ui/base/ThemedActivity.java b/mobile/src/main/java/com/alexstyl/specialdates/ui/base/ThemedActivity.java index db0fe1c0..411f3313 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/ui/base/ThemedActivity.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/ui/base/ThemedActivity.java @@ -13,7 +13,7 @@ public class ThemedActivity extends MementoActivity { @Override public void setContentView(@LayoutRes int layoutResID) { - Themer.get().initialiseActivity(this); + Themer.get(this).initialiseActivity(this); super.setContentView(layoutResID); } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/ui/widget/MementoToolbar.java b/mobile/src/main/java/com/alexstyl/specialdates/ui/widget/MementoToolbar.java index 2f187911..3a7697fb 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/ui/widget/MementoToolbar.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/ui/widget/MementoToolbar.java @@ -24,7 +24,7 @@ public MementoToolbar(Context context, @Nullable AttributeSet attrs) { if (isInEditMode()) { return; } - themer = Themer.get(); + themer = Themer.get(context); int toolbarColor = fetchAccentColor(context); float toolbarElevation = getToolbarElevation(); @@ -50,7 +50,7 @@ public float getToolbarElevation() { public void displayAsUp() { if (themer.isActivityUsingDarkIcons(getContext())) { - setNavigationIcon(R.drawable.ic_action_arrow_dark_back); + setNavigationIcon(R.drawable.ic_action_left_semitransparent); } else { setNavigationIcon(R.drawable.ic_action_arrow_light_back); } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/ExposedSearchToolbar.java b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/ExposedSearchToolbar.java new file mode 100644 index 00000000..2f119a19 --- /dev/null +++ b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/ExposedSearchToolbar.java @@ -0,0 +1,18 @@ +package com.alexstyl.specialdates.upcoming; + +import android.content.Context; +import android.support.v7.widget.Toolbar; +import android.util.AttributeSet; + +import com.alexstyl.specialdates.R; + +public class ExposedSearchToolbar extends Toolbar { + + public ExposedSearchToolbar(Context context, AttributeSet attrs) { + super(context, attrs); + + setBackgroundResource(R.drawable.card_noshadow); + setNavigationIcon(R.drawable.ic_action_search); + } + +} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/MonthTitleSetter.java b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/MonthTitleSetter.java deleted file mode 100644 index 8ec4c9f9..00000000 --- a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/MonthTitleSetter.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.alexstyl.specialdates.upcoming; - -import android.app.Activity; - -import java.util.Locale; - -class MonthTitleSetter { - - private final Activity activity; - private final MonthLabels monthLabels; - - - public static MonthTitleSetter createSetterFor(Activity activity) { - MonthLabels monthLabels = MonthLabels.forLocale(Locale.getDefault()); - - return new MonthTitleSetter(activity, monthLabels); - } - - MonthTitleSetter(Activity activity, MonthLabels monthLabels) { - this.activity = activity; - this.monthLabels = monthLabels; - } - - - void updateWithMonth(int monthToDisplay) { - String monthLabel = monthLabels.getMonthOfYear(monthToDisplay); - activity.setTitle(monthLabel); - } - -} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/SearchTransitioner.java b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/SearchTransitioner.java new file mode 100644 index 00000000..068465f5 --- /dev/null +++ b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/SearchTransitioner.java @@ -0,0 +1,102 @@ +package com.alexstyl.specialdates.upcoming; + +import android.app.Activity; +import android.support.transition.Fade; +import android.support.transition.Transition; +import android.support.transition.TransitionManager; +import android.view.ViewGroup; +import android.widget.FrameLayout; + +import com.alexstyl.specialdates.Navigator; +import com.alexstyl.specialdates.R; +import com.alexstyl.specialdates.transition.FadeInTransition; +import com.alexstyl.specialdates.transition.FadeOutTransition; +import com.alexstyl.specialdates.transition.SimpleTransitionListener; +import com.alexstyl.specialdates.ui.ViewFader; +import com.alexstyl.specialdates.util.Utils; + +import static android.view.View.GONE; +import static android.view.View.VISIBLE; + +public final class SearchTransitioner { + + private final Activity activity; + private final Navigator navigator; + private final ViewGroup activityContent; + private final ExposedSearchToolbar toolbar; + private final ViewFader viewFader; + + private final int toolbarMargin; + private boolean transitioning; + + public SearchTransitioner(Activity activity, + Navigator navigator, + ViewGroup activityContent, + ExposedSearchToolbar toolbar, + ViewFader viewFader) { + this.activity = activity; + this.navigator = navigator; + this.activityContent = activityContent; + this.toolbar = toolbar; + this.viewFader = viewFader; + this.toolbarMargin = activity.getResources().getDimensionPixelSize(R.dimen.padding_tight); + } + + public void transitionToSearch() { + if (transitioning) { + return; + } + if (supportsTransitions()) { + + Transition transition = FadeOutTransition.withAction(navigateToSearchWhenDone()); + TransitionManager.beginDelayedTransition(toolbar, transition); + expandToolbar(); + viewFader.hideContentOf(toolbar); + + TransitionManager.beginDelayedTransition(activityContent, new Fade(Fade.OUT)); + activityContent.setVisibility(GONE); + } else { + navigator.toSearch(); + } + } + + private void expandToolbar() { + FrameLayout.LayoutParams frameLP = (FrameLayout.LayoutParams) toolbar.getLayoutParams(); + frameLP.setMargins(0, 0, 0, 0); + toolbar.setLayoutParams(frameLP); + } + + private Transition.TransitionListener navigateToSearchWhenDone() { + return new SimpleTransitionListener() { + + @Override + public void onTransitionStart(Transition transition) { + transitioning = true; + } + + @Override + public void onTransitionEnd(Transition transition) { + transitioning = false; + navigator.toSearch(); + activity.overridePendingTransition(0, 0); + } + }; + } + + private static boolean supportsTransitions() { + return Utils.hasKitKat(); + } + + public void onActivityResumed() { + if (supportsTransitions()) { + TransitionManager.beginDelayedTransition(toolbar, FadeInTransition.createTransition()); + FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) toolbar.getLayoutParams(); + layoutParams.setMargins(toolbarMargin, toolbarMargin, toolbarMargin, toolbarMargin); + viewFader.showContent(toolbar); + toolbar.setLayoutParams(layoutParams); + + TransitionManager.beginDelayedTransition(activityContent, new Fade(Fade.IN)); + activityContent.setVisibility(VISIBLE); + } + } +} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/UpcomingEventsFragment.java b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/UpcomingEventsFragment.java index 9ddbd96d..298ee207 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/UpcomingEventsFragment.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/UpcomingEventsFragment.java @@ -11,20 +11,23 @@ import android.widget.ProgressBar; import android.widget.TextView; +import com.alexstyl.specialdates.Navigator; import com.alexstyl.specialdates.R; import com.alexstyl.specialdates.analytics.Action; -import com.alexstyl.specialdates.analytics.Analytics; import com.alexstyl.specialdates.analytics.ActionWithParameters; -import com.alexstyl.specialdates.analytics.Firebase; +import com.alexstyl.specialdates.analytics.Analytics; +import com.alexstyl.specialdates.analytics.AnalyticsProvider; import com.alexstyl.specialdates.analytics.Screen; import com.alexstyl.specialdates.date.CelebrationDate; import com.alexstyl.specialdates.date.ContactEvent; import com.alexstyl.specialdates.date.DayDate; import com.alexstyl.specialdates.datedetails.DateDetailsActivity; -import com.alexstyl.specialdates.search.SearchActivity; -import com.alexstyl.specialdates.theming.Themer; +import com.alexstyl.specialdates.permissions.ContactPermissionRequest; +import com.alexstyl.specialdates.permissions.PermissionChecker; import com.alexstyl.specialdates.ui.base.MementoFragment; -import com.alexstyl.specialdates.upcoming.ui.UpcomingEventsListView; +import com.alexstyl.specialdates.permissions.ContactPermissionRequest.PermissionCallbacks; +import com.alexstyl.specialdates.upcoming.view.OnUpcomingEventClickedListener; +import com.alexstyl.specialdates.upcoming.view.UpcomingEventsListView; import com.alexstyl.specialdates.views.FabPaddingSetter; import com.novoda.notils.caster.Views; @@ -38,51 +41,55 @@ public class UpcomingEventsFragment extends MementoFragment { private ProgressBar progressBar; private TextView emptyView; private SettingsMonitor monitor; - private MonthTitleSetter titleSetter; private UpcomingEventsProvider upcomingEventsProvider; private boolean mustScrollToPosition = true; private GoToTodayEnabler goToTodayEnabler; - private Themer themer; - private Analytics firebase; + private Analytics analytics; + private Navigator navigator; + private ContactPermissionRequest permissions; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); - themer = Themer.get(); - firebase = Firebase.get(getActivity()); + analytics = AnalyticsProvider.getAnalytics(getActivity()); + navigator = new Navigator(getActivity(), analytics); monitor = SettingsMonitor.newInstance(getActivity()); monitor.initialise(); - titleSetter = MonthTitleSetter.createSetterFor(getActivity()); goToTodayEnabler = new GoToTodayEnabler(getMementoActivity()); upcomingEventsProvider = UpcomingEventsProvider.newInstance(getActivity(), onEventsLoadedListener); + PermissionChecker checker = new PermissionChecker(getActivity()); + permissions = new ContactPermissionRequest(navigator, checker, callbacks); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_upcoming_events, container, false); + progressBar = Views.findById(view, R.id.upcoming_events_progress); + upcomingEventsListView = Views.findById(view, R.id.upcoming_eventslist); + emptyView = Views.findById(view, R.id.upcoming_events_emptyview); + return view; + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + new FabPaddingSetter().setBottomPaddingTo(upcomingEventsListView); + upcomingEventsListView.setHasFixedSize(true); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); - if (isUsingDarkIcons()) { - inflater.inflate(R.menu.menu_upcoming_dark, menu); - } else { - inflater.inflate(R.menu.menu_upcoming_light, menu); - } + inflater.inflate(R.menu.menu_upcoming_dark, menu); goToTodayEnabler.reattachTo(menu); } - private boolean isUsingDarkIcons() { - return themer.isActivityUsingDarkIcons(getActivity()); - } - @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_today: onGoToTodayRequested(); return true; - case R.id.action_search: { - onSearchRequested(); - return true; - } default: break; } @@ -90,50 +97,66 @@ public boolean onOptionsItemSelected(MenuItem item) { } private void onGoToTodayRequested() { - Firebase.get(getActivity()).trackAction(Action.GO_TO_TODAY); + analytics.trackAction(Action.GO_TO_TODAY); upcomingEventsListView.scrollToToday(true); } - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_upcoming_events, container, false); - progressBar = Views.findById(view, R.id.upcoming_events_progress); - upcomingEventsListView = Views.findById(view, R.id.upcoming_eventslist); - emptyView = Views.findById(view, R.id.upcoming_events_emptyview); - return view; - } + private final PermissionCallbacks callbacks = new PermissionCallbacks() { + @Override + public void onPermissionGranted() { + startLoadingData(); + } - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - new FabPaddingSetter().setBottomPaddingTo(upcomingEventsListView); - } + @Override + public void onPermissionDenied() { + getActivity().finishAffinity(); + } + }; @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - showLoading(); - refreshData(); - } - - private void refreshData() { - upcomingEventsProvider.reloadData(); + if (permissions.permissionIsPresent()) { + startLoadingData(); + } } @Override public void onResume() { super.onResume(); checkIfUserSettingsChanged(); + if (permissions.permissionIsPresent()) { + showData(); + } else { + permissions.requestForPermission(); + } } private void checkIfUserSettingsChanged() { if (monitor.dataWasUpdated()) { mustScrollToPosition = true; - showLoading(); - refreshData(); + startLoadingData(); monitor.refreshData(); } } + private void startLoadingData() { + showLoading(); + upcomingEventsProvider.reloadData(); + } + + private void showLoading() { + progressBar.setVisibility(View.VISIBLE); + upcomingEventsListView.setVisibility(View.GONE); + emptyView.setVisibility(View.GONE); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + permissions.onActivityResult(requestCode, resultCode, data); + } + private void hideLoading() { progressBar.setVisibility(View.GONE); } @@ -149,40 +172,21 @@ private void showData() { } } - private void showLoading() { - progressBar.setVisibility(View.VISIBLE); - upcomingEventsListView.setVisibility(View.GONE); - emptyView.setVisibility(View.GONE); - } - - private final UpcomingEventsListView.Listener listClickListener = new UpcomingEventsListView.Listener() { - + private final OnUpcomingEventClickedListener listClickListener = new OnUpcomingEventClickedListener() { @Override public void onContactEventPressed(View view, ContactEvent contact) { - firebase.trackAction(action); + analytics.trackAction(action); contact.getContact().displayQuickInfo(getActivity(), view); } @Override public void onCardPressed(DayDate date) { - firebase.trackScreen(Screen.DATE_DETAILS); + analytics.trackScreen(Screen.DATE_DETAILS); Intent intent = DateDetailsActivity.getStartIntent(getActivity(), date.getDayOfMonth(), date.getMonth(), date.getYear()); startActivity(intent); } - - @Override - public void onDifferentMonthScrolled(int month) { - titleSetter.updateWithMonth(month); - } }; - public boolean onSearchRequested() { - Firebase.get(getActivity()).trackScreen(Screen.SEARCH); - Intent intent = new Intent(getActivity(), SearchActivity.class); - startActivity(intent); - return true; - } - private final UpcomingEventsProvider.LoadingListener onEventsLoadedListener = new UpcomingEventsProvider.LoadingListener() { @Override public void onUpcomingEventsLoaded(List dates) { diff --git a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/UpcomingEventsLoader.java b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/UpcomingEventsLoader.java index d1ec68bc..1e3d9f62 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/UpcomingEventsLoader.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/UpcomingEventsLoader.java @@ -8,15 +8,15 @@ import com.alexstyl.specialdates.date.ContactEvent; import com.alexstyl.specialdates.date.DayDate; import com.alexstyl.specialdates.events.bankholidays.BankHoliday; -import com.alexstyl.specialdates.events.bankholidays.BankholidayCalendar; import com.alexstyl.specialdates.events.bankholidays.BankHolidaysPreferences; +import com.alexstyl.specialdates.events.bankholidays.BankholidayCalendar; import com.alexstyl.specialdates.events.bankholidays.GreekBankHolidays; -import com.alexstyl.specialdates.events.namedays.calendar.EasterCalculator; -import com.alexstyl.specialdates.events.namedays.calendar.NamedayCalendar; -import com.alexstyl.specialdates.events.namedays.calendar.NamedayCalendarProvider; import com.alexstyl.specialdates.events.namedays.NamedayLocale; import com.alexstyl.specialdates.events.namedays.NamedayPreferences; import com.alexstyl.specialdates.events.namedays.NamesInADate; +import com.alexstyl.specialdates.events.namedays.calendar.EasterCalculator; +import com.alexstyl.specialdates.events.namedays.calendar.NamedayCalendar; +import com.alexstyl.specialdates.events.namedays.calendar.NamedayCalendarProvider; import com.alexstyl.specialdates.service.PeopleEventsProvider; import com.alexstyl.specialdates.ui.loader.SimpleAsyncTaskLoader; import com.alexstyl.specialdates.util.ContactsObserver; diff --git a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/UpcomingEventsPresenter.java b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/UpcomingEventsPresenter.java new file mode 100644 index 00000000..30c5cf81 --- /dev/null +++ b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/UpcomingEventsPresenter.java @@ -0,0 +1,4 @@ +package com.alexstyl.specialdates.upcoming; + +public final class UpcomingEventsPresenter { +} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/ui/MonthHeaderViewHolder.java b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/ui/MonthHeaderViewHolder.java index deffc6c4..38d17493 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/ui/MonthHeaderViewHolder.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/ui/MonthHeaderViewHolder.java @@ -10,12 +10,13 @@ import com.alexstyl.specialdates.upcoming.MonthLabels; import com.alexstyl.specialdates.upcoming.MonthOfYear; -public class MonthHeaderViewHolder extends RecyclerView.ViewHolder { +public final class MonthHeaderViewHolder extends RecyclerView.ViewHolder { private final MonthLabels monthLabels; private final TextView header; public static MonthHeaderViewHolder createFor(ViewGroup parent, MonthLabels monthLabels) { - View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_header_month, parent, false); + LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); + View view = layoutInflater.inflate(R.layout.row_header_month, parent, false); return new MonthHeaderViewHolder(view, monthLabels); } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/ui/UpcomingEventsAdapter.java b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/ui/UpcomingEventsAdapter.java index 2d17fb2d..718def97 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/ui/UpcomingEventsAdapter.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/ui/UpcomingEventsAdapter.java @@ -9,6 +9,7 @@ import com.alexstyl.specialdates.images.ImageLoader; import com.alexstyl.specialdates.upcoming.MonthLabels; import com.alexstyl.specialdates.upcoming.MonthOfYear; +import com.alexstyl.specialdates.upcoming.view.OnUpcomingEventClickedListener; import com.novoda.notils.exception.DeveloperError; import java.util.ArrayList; @@ -118,7 +119,7 @@ public int getMonthAt(int position) { return integer == null ? -1 : integer; } - public void setUpcomingEvents(List upcomingEvents, UpcomingEventsListView.Listener listener) { + public void setUpcomingEvents(List upcomingEvents, OnUpcomingEventClickedListener listener) { this.listener = listener; if (this.celebrationDates == upcomingEvents) { return; diff --git a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/ui/UpcomingEventsViewHolder.java b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/ui/UpcomingEventsViewHolder.java index d2baf079..1652ad7b 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/ui/UpcomingEventsViewHolder.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/ui/UpcomingEventsViewHolder.java @@ -18,6 +18,8 @@ import com.alexstyl.specialdates.date.DayDate; import com.alexstyl.specialdates.images.ImageLoader; import com.alexstyl.specialdates.theming.AttributeExtractor; +import com.alexstyl.specialdates.upcoming.view.OnUpcomingEventClickedListener; +import com.alexstyl.specialdates.upcoming.view.UpcomingEventsView; import com.novoda.notils.caster.Views; public class UpcomingEventsViewHolder extends RecyclerView.ViewHolder { diff --git a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/ui/ContactEventView.java b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/view/ContactEventView.java similarity index 73% rename from mobile/src/main/java/com/alexstyl/specialdates/upcoming/ui/ContactEventView.java rename to mobile/src/main/java/com/alexstyl/specialdates/upcoming/view/ContactEventView.java index 37869f7e..dd2a113f 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/ui/ContactEventView.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/view/ContactEventView.java @@ -1,4 +1,4 @@ -package com.alexstyl.specialdates.upcoming.ui; +package com.alexstyl.specialdates.upcoming.view; import android.content.Context; import android.content.res.Resources; @@ -10,7 +10,6 @@ import android.widget.TextView; import com.alexstyl.specialdates.R; -import com.alexstyl.specialdates.contact.Birthday; import com.alexstyl.specialdates.contact.Contact; import com.alexstyl.specialdates.date.ContactEvent; import com.alexstyl.specialdates.events.EventType; @@ -50,23 +49,9 @@ public void displayEvent(ContactEvent event, ImageLoader imageLoader) { private void displayEventFor(ContactEvent event) { EventType eventType = event.getType(); - String eventLabel = getLabelFor(event); + String eventLabel = event.getLabel(resources); eventTypeView.setText(eventLabel); - eventTypeView.setTextColor(getResources().getColor(eventType.getColorRes())); - } - - private String getLabelFor(ContactEvent event) { - EventType eventType = event.getType(); - if (eventType == EventType.BIRTHDAY) { - Birthday birthday = event.getContact().getBirthday(); - if (birthday.includesYear()) { - int age = birthday.getAgeOnYear(event.getYear()); - if (age > 0) { - return resources.getString(R.string.turns_age, age); - } - } - } - return resources.getString(eventType.nameRes()); + eventTypeView.setTextColor(resources.getColor(eventType.getColorRes())); } public void setNameTypeface(Typeface typeface) { diff --git a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/ui/OnUpcomingEventClickedListener.java b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/view/OnUpcomingEventClickedListener.java similarity index 85% rename from mobile/src/main/java/com/alexstyl/specialdates/upcoming/ui/OnUpcomingEventClickedListener.java rename to mobile/src/main/java/com/alexstyl/specialdates/upcoming/view/OnUpcomingEventClickedListener.java index 65382300..699791b1 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/ui/OnUpcomingEventClickedListener.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/view/OnUpcomingEventClickedListener.java @@ -1,4 +1,4 @@ -package com.alexstyl.specialdates.upcoming.ui; +package com.alexstyl.specialdates.upcoming.view; import android.view.View; diff --git a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/ui/TitledTextView.java b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/view/TitledTextView.java similarity index 96% rename from mobile/src/main/java/com/alexstyl/specialdates/upcoming/ui/TitledTextView.java rename to mobile/src/main/java/com/alexstyl/specialdates/upcoming/view/TitledTextView.java index e18d5ebf..16b8bf39 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/ui/TitledTextView.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/view/TitledTextView.java @@ -1,4 +1,4 @@ -package com.alexstyl.specialdates.upcoming.ui; +package com.alexstyl.specialdates.upcoming.view; import android.content.Context; import android.content.res.TypedArray; diff --git a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/ui/UpcomingEventsListView.java b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/view/UpcomingEventsListView.java similarity index 81% rename from mobile/src/main/java/com/alexstyl/specialdates/upcoming/ui/UpcomingEventsListView.java rename to mobile/src/main/java/com/alexstyl/specialdates/upcoming/view/UpcomingEventsListView.java index 604847b3..73a72ee0 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/ui/UpcomingEventsListView.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/view/UpcomingEventsListView.java @@ -1,4 +1,4 @@ -package com.alexstyl.specialdates.upcoming.ui; +package com.alexstyl.specialdates.upcoming.view; import android.content.Context; import android.content.res.Resources; @@ -11,7 +11,8 @@ import com.alexstyl.specialdates.images.ImageLoader; import com.alexstyl.specialdates.images.PauseImageLoadingScrollListener; import com.alexstyl.specialdates.ui.widget.ScrollingLinearLayoutManager; -import com.alexstyl.specialdates.upcoming.MonthSectionScrollListener; +import com.alexstyl.specialdates.upcoming.ui.UpcomingEventsAdapter; +import com.alexstyl.specialdates.upcoming.ui.UpcomingEventsViewHolder; import java.util.List; @@ -20,12 +21,9 @@ public class UpcomingEventsListView extends RecyclerView { private UpcomingEventsAdapter adapter; private ScrollingLinearLayoutManager layoutManager; - private Listener listener; - public UpcomingEventsListView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); - setHasFixedSize(true); layoutManager = new ScrollingLinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false, 600); setLayoutManager(layoutManager); @@ -39,19 +37,10 @@ public UpcomingEventsListView(Context context, @Nullable AttributeSet attrs) { setAdapter(adapter); addOnScrollListener(PauseImageLoadingScrollListener.newInstance(imageLoader)); - - final MonthSectionScrollListener monthSectionScrollListener = new MonthSectionScrollListener(adapter, layoutManager) { - @Override - protected void onDifferentMonthScrolled(int month) { - listener.onDifferentMonthScrolled(month); - } - }; - addOnScrollListener(monthSectionScrollListener); } - public void updateWith(List dates, Listener listener) { - this.listener = listener; - this.adapter.setUpcomingEvents(dates, listener); + public void updateWith(List dates, OnUpcomingEventClickedListener listener) { + adapter.setUpcomingEvents(dates, listener); } public void scrollToToday(final boolean smoothScroll) { @@ -120,7 +109,4 @@ public boolean isDisplayingEvents() { return adapter.getItemCount() > 0; } - public interface Listener extends OnUpcomingEventClickedListener { - void onDifferentMonthScrolled(int month); - } } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/ui/UpcomingEventsView.java b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/view/UpcomingEventsView.java similarity index 98% rename from mobile/src/main/java/com/alexstyl/specialdates/upcoming/ui/UpcomingEventsView.java rename to mobile/src/main/java/com/alexstyl/specialdates/upcoming/view/UpcomingEventsView.java index 204ff919..b9ef8a3f 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/ui/UpcomingEventsView.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/view/UpcomingEventsView.java @@ -1,4 +1,4 @@ -package com.alexstyl.specialdates.upcoming.ui; +package com.alexstyl.specialdates.upcoming.view; import android.content.Context; import android.graphics.Typeface; diff --git a/mobile/src/main/java/com/alexstyl/specialdates/util/Notifier.java b/mobile/src/main/java/com/alexstyl/specialdates/util/Notifier.java index f465bb8b..224231f9 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/util/Notifier.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/util/Notifier.java @@ -14,17 +14,21 @@ import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.RectF; +import android.graphics.Typeface; import android.net.Uri; import android.support.v4.app.NotificationCompat; +import android.text.Spannable; +import android.text.SpannableString; import android.text.TextUtils; +import android.text.style.StyleSpan; import com.alexstyl.specialdates.R; import com.alexstyl.specialdates.contact.Contact; +import com.alexstyl.specialdates.date.ContactEvent; import com.alexstyl.specialdates.date.Date; import com.alexstyl.specialdates.datedetails.DateDetailsActivity; import com.alexstyl.specialdates.events.ContactEvents; import com.alexstyl.specialdates.events.bankholidays.BankHoliday; -import com.alexstyl.specialdates.events.namedays.NamedayPreferences; import com.alexstyl.specialdates.images.ImageLoader; import com.alexstyl.specialdates.settings.MainPreferenceActivity; import com.novoda.notils.logger.simple.Log; @@ -40,19 +44,18 @@ public class Notifier { private static final String TAG = "Notifier"; private final Context context; + private final Resources resources; private final ImageLoader imageLoader; - private final NamedayPreferences namedayPreferences; public static Notifier newInstance(Context context) { Resources resources = context.getResources(); ImageLoader imageLoader = ImageLoader.createSquareThumbnailLoader(resources); - NamedayPreferences namedayPreferences = NamedayPreferences.newInstance(context); - return new Notifier(context, imageLoader, namedayPreferences); + return new Notifier(context, resources, imageLoader); } - public Notifier(Context context, ImageLoader imageLoader, NamedayPreferences namedayPreferences) { + public Notifier(Context context, Resources resources, ImageLoader imageLoader) { + this.resources = resources; this.imageLoader = imageLoader; - this.namedayPreferences = namedayPreferences; this.context = context.getApplicationContext(); } @@ -63,14 +66,13 @@ public Notifier(Context context, ImageLoader imageLoader, NamedayPreferences nam */ public void forDailyReminder(ContactEvents events) { Bitmap largeIcon = null; - Resources res = context.getResources(); Date date = events.getDate(); int contactCount = events.size(); if (shouldDisplayContactImage(contactCount)) { // Large Icons were introduced in Honeycomb // and we are only displaying one if it is one contact - int size = res.getDimensionPixelSize(android.R.dimen.notification_large_icon_width); + int size = resources.getDimensionPixelSize(android.R.dimen.notification_large_icon_width); Contact displayingContact = events.getContacts().iterator().next(); largeIcon = loadImageAsync(displayingContact, size, size); if (Utils.hasLollipop() && largeIcon != null) { @@ -89,19 +91,48 @@ public void forDailyReminder(ContactEvents events) { ); String title = NaturalLanguageUtils.joinContacts(context, events.getContacts(), 3); - String fullText = TextUtils.join(", ", events.getContacts()); NotificationCompat.Builder builder = new NotificationCompat.Builder(context) .setSmallIcon(R.drawable.ic_stat_contact_event) .setContentTitle(title) .setLargeIcon(largeIcon) - .setStyle(new NotificationCompat.BigTextStyle().bigText(fullText)) .setVisibility(NotificationCompat.VISIBILITY_PRIVATE) .setAutoCancel(true) - .setContentText(fullText) .setContentIntent(intent) + .setNumber(events.size()) .setColor(context.getResources().getColor(R.color.main_red)); + if (events.size() == 1) { + ContactEvent event = events.getEvent(0); + String msg = event.getLabel(resources); + + NotificationCompat.BigTextStyle bigTextStyle = new NotificationCompat.BigTextStyle().bigText(msg); + bigTextStyle.setBigContentTitle(title); + builder.setContentText(msg); + + builder.setStyle(bigTextStyle); + + } else if (events.getContacts().size() > 1) { + NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle(); + inboxStyle.setBigContentTitle(title); + + for (int i = 0; i < events.size(); ++i) { + + ContactEvent event = events.getEvent(i); + Contact contact = event.getContact(); + String name = contact.getDisplayName().toString(); + + String lineFormatted = name + "\t\t" + event.getLabel(resources); + + Spannable sb = new SpannableString(lineFormatted); + sb.setSpan(new StyleSpan(Typeface.BOLD), 0, name.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + inboxStyle.addLine(sb); + } + + builder.setStyle(inboxStyle); + builder.setContentText(TextUtils.join(", ", events.getContacts())); + } + if (supportsPublicNotifications()) { String publicTitle = context.getString(R.string.contact_celebration_count, contactCount); NotificationCompat.Builder publicNotification = new NotificationCompat.Builder(context) @@ -109,13 +140,11 @@ public void forDailyReminder(ContactEvents events) { .setAutoCancel(true) .setContentIntent(intent) .setContentTitle(publicTitle) - .setColor(context.getResources().getColor(R.color.main_red)); + .setColor(resources.getColor(R.color.main_red)); builder.setPublicVersion(publicNotification.build()); } - builder.setNumber(contactCount); - for (Contact contact : events.getContacts()) { Uri uri = contact.getLookupUri(); if (uri != null) { @@ -168,7 +197,7 @@ private boolean supportsPublicNotifications() { } private boolean shouldDisplayContactImage(int contactCount) { - return Utils.hasHoneycomb() && contactCount == 1; + return contactCount == 1; } public void forNamedays(List names, Date date) { @@ -188,11 +217,11 @@ public void forNamedays(List names, Date date) { NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context) .setSmallIcon(R.drawable.ic_stat_namedays) .setStyle(new NotificationCompat.BigTextStyle().bigText(fullsubtitle)) - .setContentTitle(context.getResources().getQuantityString(R.plurals.todays_nameday, names.size())) + .setContentTitle(resources.getQuantityString(R.plurals.todays_nameday, names.size())) .setContentText(subtitle) .setAutoCancel(true) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) - .setColor(context.getResources().getColor(R.color.nameday_blue)) + .setColor(resources.getColor(R.color.nameday_blue)) .setContentIntent(intent); if (names.size() > 1) { mBuilder.setNumber(names.size()); @@ -225,7 +254,7 @@ public void forBankholiday(Date date, BankHoliday bankHoliday) { .setContentText(subtitle) .setAutoCancel(true) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) - .setColor(context.getResources().getColor(R.color.bankholiday_green)) + .setColor(resources.getColor(R.color.bankholiday_green)) .setContentIntent(intent); NotificationManager mngr = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); mngr.notify(NOTIFICATION_ID_DAILY_REMINDER_BANKHOLIDAYS, builder.build()); diff --git a/mobile/src/main/java/com/alexstyl/specialdates/util/Utils.java b/mobile/src/main/java/com/alexstyl/specialdates/util/Utils.java index 45d68b8b..02be5b06 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/util/Utils.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/util/Utils.java @@ -16,15 +16,10 @@ package com.alexstyl.specialdates.util; -import android.annotation.SuppressLint; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.content.res.Resources; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; import android.net.Uri; import android.os.Build; import android.os.Vibrator; @@ -34,17 +29,14 @@ import android.view.View; import android.widget.Toast; -import com.alexstyl.specialdates.BuildConfig; import com.alexstyl.specialdates.ErrorTracker; import com.alexstyl.specialdates.MementoApplication; -import com.alexstyl.specialdates.PayPal; import com.alexstyl.specialdates.R; import com.alexstyl.specialdates.contact.actions.IntentAction; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import java.util.List; import org.json.JSONException; import org.json.JSONObject; @@ -58,22 +50,6 @@ public class Utils { private Utils() { } - /** - * Uses static final constants to detect if the device's platform version is - * Honeycomb or later. - */ - public static boolean hasHoneycomb() { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB; - } - - /** - * Uses static final constants to detect if the device's platform version is - * ICS or later. - */ - public static boolean hasICS() { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH; - } - public static boolean hasJellyBean() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN; } @@ -97,10 +73,6 @@ public static boolean hasKitKat() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; } - public static boolean isRunningKitKat() { - return Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT; - } - /** * Uses static final constants to detect if the device's platform version is * Lollipop or later. @@ -164,52 +136,11 @@ public static String getAndroidVersion() { return android.os.Build.VERSION.RELEASE; } - /** - * Returns whether the device has - * - * @param context The context to use - * @return - */ - @SuppressLint("NewApi") public static boolean hasVibrator(Context context) { Vibrator vibr = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); - if (!hasHoneycomb()) { - return vibr != null; - } return vibr.hasVibrator(); } - /** - * Checks if the device is currently connected to the webz! - * - * @param context The context to use - * @return Whether the device is online or not... duh - */ - public static boolean isOnline(Context context) { - ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo ni = cm.getActiveNetworkInfo(); - if (ni == null) { - // There are no active networks. - return false; - } - return ni.isConnectedOrConnecting(); - } - - /** - * Returns the height of the navigation bars height - * - * @param context - * @return - */ - final public static int getNavigationBarHeight(Context context) { - Resources resources = context.getResources(); - int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android"); - if (resourceId > 0) { - return resources.getDimensionPixelSize(resourceId); - } - return 0; - } - /** * Starts a {@link IntentAction}. A Toast is displayed if the action throws an ActivityNotFoundException * @@ -251,27 +182,11 @@ public String getName() { ); } - /** - * Checks if the running device has any installed applications that can handle the given intent - * - * @param context - * @param intent - * @return - */ - public static boolean isCallable(Context context, Intent intent) { - List list = context.getPackageManager().queryIntentActivities( - intent, - PackageManager.MATCH_DEFAULT_ONLY - ); - return list.size() > 0; - } - /** * Returns a JSON value from the raw folder, of the given resID * * @param context The context to use * @param resID The resource ID of the JSON file - * @return * @throws android.content.res.Resources.NotFoundException if the resource is not a JSON */ public static JSONObject getJSON(@NonNull Context context, @RawRes int resID) { @@ -296,36 +211,4 @@ public static JSONObject getJSON(@NonNull Context context, @RawRes int resID) { } } - /** - * Compares whether one is equal with at least one of the others - * - * @param one - * @param others - * @return - */ - public static boolean equalsTo(Object one, Object... others) { - for (Object other : others) { - if (one.equals(other)) { - return true; - } - } - return false; - } - - public static boolean openPayPalDonation(Context context) { - try { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(PayPal.URL_DONATIONS)); - context.startActivity(intent); - return true; - } catch (ActivityNotFoundException e) { - if (BuildConfig.DEBUG) { - // do nothing if we there is no browser installed - Toast.makeText(context, "Exception thrown!", Toast.LENGTH_SHORT).show(); - e.printStackTrace(); - } - } - return false; - } - } diff --git a/mobile/src/main/res/drawable-hdpi/ic_action_arrow_dark_back.png b/mobile/src/main/res/drawable-hdpi/ic_action_left_semitransparent.png similarity index 100% rename from mobile/src/main/res/drawable-hdpi/ic_action_arrow_dark_back.png rename to mobile/src/main/res/drawable-hdpi/ic_action_left_semitransparent.png diff --git a/mobile/src/main/res/drawable-hdpi/ic_action_navigation_arrow_back.png b/mobile/src/main/res/drawable-hdpi/ic_action_navigation_arrow_back.png new file mode 100755 index 00000000..a9df1796 Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/ic_action_navigation_arrow_back.png differ diff --git a/mobile/src/main/res/drawable-hdpi/ic_action_search.png b/mobile/src/main/res/drawable-hdpi/ic_action_search.png old mode 100644 new mode 100755 index 857135d2..9810302b Binary files a/mobile/src/main/res/drawable-hdpi/ic_action_search.png and b/mobile/src/main/res/drawable-hdpi/ic_action_search.png differ diff --git a/mobile/src/main/res/drawable-hdpi/ic_action_today.png b/mobile/src/main/res/drawable-hdpi/ic_action_today.png old mode 100644 new mode 100755 index fe825d98..eaf359f4 Binary files a/mobile/src/main/res/drawable-hdpi/ic_action_today.png and b/mobile/src/main/res/drawable-hdpi/ic_action_today.png differ diff --git a/mobile/src/main/res/drawable-hdpi/ic_exit_to_app_white_24dp.png b/mobile/src/main/res/drawable-hdpi/ic_exit_to_app_white_24dp.png deleted file mode 100644 index 52565d09..00000000 Binary files a/mobile/src/main/res/drawable-hdpi/ic_exit_to_app_white_24dp.png and /dev/null differ diff --git a/mobile/src/main/res/drawable-hdpi/ic_search_black_24dp.png b/mobile/src/main/res/drawable-hdpi/ic_search_black_24dp.png deleted file mode 100644 index c593e7ad..00000000 Binary files a/mobile/src/main/res/drawable-hdpi/ic_search_black_24dp.png and /dev/null differ diff --git a/mobile/src/main/res/drawable-hdpi/ic_today_black_24dp.png b/mobile/src/main/res/drawable-hdpi/ic_today_black_24dp.png deleted file mode 100644 index 03680a76..00000000 Binary files a/mobile/src/main/res/drawable-hdpi/ic_today_black_24dp.png and /dev/null differ diff --git a/mobile/src/main/res/drawable-mdpi/ic_action_arrow_dark_back.png b/mobile/src/main/res/drawable-mdpi/ic_action_left_semitransparent.png similarity index 100% rename from mobile/src/main/res/drawable-mdpi/ic_action_arrow_dark_back.png rename to mobile/src/main/res/drawable-mdpi/ic_action_left_semitransparent.png diff --git a/mobile/src/main/res/drawable-mdpi/ic_action_navigation_arrow_back.png b/mobile/src/main/res/drawable-mdpi/ic_action_navigation_arrow_back.png new file mode 100755 index 00000000..6ae1c1d0 Binary files /dev/null and b/mobile/src/main/res/drawable-mdpi/ic_action_navigation_arrow_back.png differ diff --git a/mobile/src/main/res/drawable-mdpi/ic_action_search.png b/mobile/src/main/res/drawable-mdpi/ic_action_search.png old mode 100644 new mode 100755 index 8d57e2ba..f70bef02 Binary files a/mobile/src/main/res/drawable-mdpi/ic_action_search.png and b/mobile/src/main/res/drawable-mdpi/ic_action_search.png differ diff --git a/mobile/src/main/res/drawable-mdpi/ic_action_today.png b/mobile/src/main/res/drawable-mdpi/ic_action_today.png old mode 100644 new mode 100755 index 82a86d8d..c7c57fd2 Binary files a/mobile/src/main/res/drawable-mdpi/ic_action_today.png and b/mobile/src/main/res/drawable-mdpi/ic_action_today.png differ diff --git a/mobile/src/main/res/drawable-mdpi/ic_exit_to_app_white_24dp.png b/mobile/src/main/res/drawable-mdpi/ic_exit_to_app_white_24dp.png deleted file mode 100644 index 461be00a..00000000 Binary files a/mobile/src/main/res/drawable-mdpi/ic_exit_to_app_white_24dp.png and /dev/null differ diff --git a/mobile/src/main/res/drawable-mdpi/ic_search_black_24dp.png b/mobile/src/main/res/drawable-mdpi/ic_search_black_24dp.png deleted file mode 100644 index 6b163432..00000000 Binary files a/mobile/src/main/res/drawable-mdpi/ic_search_black_24dp.png and /dev/null differ diff --git a/mobile/src/main/res/drawable-mdpi/ic_today_black_24dp.png b/mobile/src/main/res/drawable-mdpi/ic_today_black_24dp.png deleted file mode 100644 index 227bfca9..00000000 Binary files a/mobile/src/main/res/drawable-mdpi/ic_today_black_24dp.png and /dev/null differ diff --git a/mobile/src/main/res/drawable-xhdpi/ic_action_arrow_dark_back.png b/mobile/src/main/res/drawable-xhdpi/ic_action_left_semitransparent.png similarity index 100% rename from mobile/src/main/res/drawable-xhdpi/ic_action_arrow_dark_back.png rename to mobile/src/main/res/drawable-xhdpi/ic_action_left_semitransparent.png diff --git a/mobile/src/main/res/drawable-xhdpi/ic_action_navigation_arrow_back.png b/mobile/src/main/res/drawable-xhdpi/ic_action_navigation_arrow_back.png new file mode 100755 index 00000000..39dde07a Binary files /dev/null and b/mobile/src/main/res/drawable-xhdpi/ic_action_navigation_arrow_back.png differ diff --git a/mobile/src/main/res/drawable-xhdpi/ic_action_search.png b/mobile/src/main/res/drawable-xhdpi/ic_action_search.png old mode 100644 new mode 100755 index d9fb6338..ccdfb3fd Binary files a/mobile/src/main/res/drawable-xhdpi/ic_action_search.png and b/mobile/src/main/res/drawable-xhdpi/ic_action_search.png differ diff --git a/mobile/src/main/res/drawable-xhdpi/ic_action_today.png b/mobile/src/main/res/drawable-xhdpi/ic_action_today.png old mode 100644 new mode 100755 index 4fe7595f..80b49c4b Binary files a/mobile/src/main/res/drawable-xhdpi/ic_action_today.png and b/mobile/src/main/res/drawable-xhdpi/ic_action_today.png differ diff --git a/mobile/src/main/res/drawable-xhdpi/ic_exit_to_app_white_24dp.png b/mobile/src/main/res/drawable-xhdpi/ic_exit_to_app_white_24dp.png deleted file mode 100644 index 07847bd2..00000000 Binary files a/mobile/src/main/res/drawable-xhdpi/ic_exit_to_app_white_24dp.png and /dev/null differ diff --git a/mobile/src/main/res/drawable-xhdpi/ic_search_black_24dp.png b/mobile/src/main/res/drawable-xhdpi/ic_search_black_24dp.png deleted file mode 100644 index 63819026..00000000 Binary files a/mobile/src/main/res/drawable-xhdpi/ic_search_black_24dp.png and /dev/null differ diff --git a/mobile/src/main/res/drawable-xhdpi/ic_today_black_24dp.png b/mobile/src/main/res/drawable-xhdpi/ic_today_black_24dp.png deleted file mode 100644 index 2d6a849c..00000000 Binary files a/mobile/src/main/res/drawable-xhdpi/ic_today_black_24dp.png and /dev/null differ diff --git a/mobile/src/main/res/drawable-xxhdpi/ic_action_arrow_dark_back.png b/mobile/src/main/res/drawable-xxhdpi/ic_action_left_semitransparent.png similarity index 100% rename from mobile/src/main/res/drawable-xxhdpi/ic_action_arrow_dark_back.png rename to mobile/src/main/res/drawable-xxhdpi/ic_action_left_semitransparent.png diff --git a/mobile/src/main/res/drawable-xxhdpi/ic_action_navigation_arrow_back.png b/mobile/src/main/res/drawable-xxhdpi/ic_action_navigation_arrow_back.png new file mode 100755 index 00000000..e5287823 Binary files /dev/null and b/mobile/src/main/res/drawable-xxhdpi/ic_action_navigation_arrow_back.png differ diff --git a/mobile/src/main/res/drawable-xxhdpi/ic_action_search.png b/mobile/src/main/res/drawable-xxhdpi/ic_action_search.png old mode 100644 new mode 100755 index b1048235..4a85a4cd Binary files a/mobile/src/main/res/drawable-xxhdpi/ic_action_search.png and b/mobile/src/main/res/drawable-xxhdpi/ic_action_search.png differ diff --git a/mobile/src/main/res/drawable-xxhdpi/ic_action_today.png b/mobile/src/main/res/drawable-xxhdpi/ic_action_today.png old mode 100644 new mode 100755 index ad33347b..d5b29841 Binary files a/mobile/src/main/res/drawable-xxhdpi/ic_action_today.png and b/mobile/src/main/res/drawable-xxhdpi/ic_action_today.png differ diff --git a/mobile/src/main/res/drawable-xxhdpi/ic_exit_to_app_white_24dp.png b/mobile/src/main/res/drawable-xxhdpi/ic_exit_to_app_white_24dp.png deleted file mode 100644 index c04fe6e0..00000000 Binary files a/mobile/src/main/res/drawable-xxhdpi/ic_exit_to_app_white_24dp.png and /dev/null differ diff --git a/mobile/src/main/res/drawable-xxhdpi/ic_search_black_24dp.png b/mobile/src/main/res/drawable-xxhdpi/ic_search_black_24dp.png deleted file mode 100644 index 3ae490ef..00000000 Binary files a/mobile/src/main/res/drawable-xxhdpi/ic_search_black_24dp.png and /dev/null differ diff --git a/mobile/src/main/res/drawable-xxhdpi/ic_today_black_24dp.png b/mobile/src/main/res/drawable-xxhdpi/ic_today_black_24dp.png deleted file mode 100644 index 229f0445..00000000 Binary files a/mobile/src/main/res/drawable-xxhdpi/ic_today_black_24dp.png and /dev/null differ diff --git a/mobile/src/main/res/drawable-xxxhdpi/ic_action_navigation_arrow_back.png b/mobile/src/main/res/drawable-xxxhdpi/ic_action_navigation_arrow_back.png new file mode 100755 index 00000000..71d044e5 Binary files /dev/null and b/mobile/src/main/res/drawable-xxxhdpi/ic_action_navigation_arrow_back.png differ diff --git a/mobile/src/main/res/drawable-xxxhdpi/ic_action_search.png b/mobile/src/main/res/drawable-xxxhdpi/ic_action_search.png new file mode 100755 index 00000000..2653c039 Binary files /dev/null and b/mobile/src/main/res/drawable-xxxhdpi/ic_action_search.png differ diff --git a/mobile/src/main/res/drawable-xxxhdpi/ic_action_today.png b/mobile/src/main/res/drawable-xxxhdpi/ic_action_today.png new file mode 100755 index 00000000..e9d5192c Binary files /dev/null and b/mobile/src/main/res/drawable-xxxhdpi/ic_action_today.png differ diff --git a/mobile/src/main/res/drawable-xxxhdpi/ic_exit_to_app_white_24dp.png b/mobile/src/main/res/drawable-xxxhdpi/ic_exit_to_app_white_24dp.png deleted file mode 100644 index 27a9d7b0..00000000 Binary files a/mobile/src/main/res/drawable-xxxhdpi/ic_exit_to_app_white_24dp.png and /dev/null differ diff --git a/mobile/src/main/res/drawable-xxxhdpi/ic_search_black_24dp.png b/mobile/src/main/res/drawable-xxxhdpi/ic_search_black_24dp.png deleted file mode 100644 index 21be5729..00000000 Binary files a/mobile/src/main/res/drawable-xxxhdpi/ic_search_black_24dp.png and /dev/null differ diff --git a/mobile/src/main/res/drawable-xxxhdpi/ic_today_black_24dp.png b/mobile/src/main/res/drawable-xxxhdpi/ic_today_black_24dp.png deleted file mode 100644 index 38704111..00000000 Binary files a/mobile/src/main/res/drawable-xxxhdpi/ic_today_black_24dp.png and /dev/null differ diff --git a/mobile/src/main/res/drawable/animatedbackground.xml b/mobile/src/main/res/drawable/animatedbackground.xml deleted file mode 100644 index 1fc8380a..00000000 --- a/mobile/src/main/res/drawable/animatedbackground.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/mobile/src/main/res/drawable/card_noshadow.xml b/mobile/src/main/res/drawable/card_noshadow.xml new file mode 100644 index 00000000..48d13eb2 --- /dev/null +++ b/mobile/src/main/res/drawable/card_noshadow.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/mobile/src/main/res/drawable/ic_contacts.xml b/mobile/src/main/res/drawable/ic_contacts.xml new file mode 100644 index 00000000..eeee563a --- /dev/null +++ b/mobile/src/main/res/drawable/ic_contacts.xml @@ -0,0 +1,9 @@ + + + diff --git a/mobile/src/main/res/layout/activity_contact_permission_request.xml b/mobile/src/main/res/layout/activity_contact_permission_request.xml new file mode 100644 index 00000000..6f0682fe --- /dev/null +++ b/mobile/src/main/res/layout/activity_contact_permission_request.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + +