diff --git a/.gitignore b/.gitignore index a253eb4e..b218f1b0 100644 --- a/.gitignore +++ b/.gitignore @@ -45,4 +45,4 @@ captures/ # secrets secret.gradle -*google-services.json +google-services.json diff --git a/build.sh b/build.sh index ed341366..03537087 100755 --- a/build.sh +++ b/build.sh @@ -1,3 +1,4 @@ +# Build script taken and altered from: /~https://github.com/firebase/quickstart-android/blob/master/build.sh #!/bin/bash # Exit on error @@ -6,29 +7,40 @@ set -e # Limit memory usage OPTS='-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' - - # Copy mock secret.gradle file if necessary - if [ ! -f secret.gradle ]; then - echo "Secret.gradle not found. Using sample" - cp secret.gradle.sample secret.gradle - fi - - - # Work off travis - if [[ -v TRAVIS_PULL_REQUEST ]]; then - echo "TRAVIS_PULL_REQUEST: $TRAVIS_PULL_REQUEST" - else - echo "TRAVIS_PULL_REQUEST: unset, setting to false" - TRAVIS_PULL_REQUEST=false - fi - - # Build - if [ $TRAVIS_PULL_REQUEST = false ] ; then - # For a merged commit, build all configurations. - GRADLE_OPTS=$OPTS ./gradlew clean build - else - # On a pull request, just build debug which is much faster and catches - # obvious errors. - GRADLE_OPTS=$OPTS ./gradlew clean :mobile:assembleDebug - fi - +# Work off travis +if [[ ! -z TRAVIS_PULL_REQUEST ]]; then + echo "TRAVIS_PULL_REQUEST: $TRAVIS_PULL_REQUEST" +else + echo "TRAVIS_PULL_REQUEST: unset, setting to false" + TRAVIS_PULL_REQUEST=false +fi + +echo "Building ${SAMPLE}" + +# Copy mock secret.gradle file if necessary +if [ ! -f secret.gradle ]; then + echo "Secret.gradle not found. Using sample" + cp secret.gradle.sample secret.gradle +fi + +# Copy mock google-services file if necessary +if [ ! -f ./mobile/src/free/google-services.json ]; then + echo "Using mock google-services.json for free" + mkdir ./mobile/src/free/ + cp mock-google-services.json ./mobile/src/free/google-services.json +fi +if [ ! -f ./mobile/src/pro/google-services.json ]; then + echo "Using mock google-services.json for pro" + mkdir ./mobile/src/pro/ + cp mock-google-services.json ./mobile/src/pro/google-services.json +fi + +# Build +if [ $TRAVIS_PULL_REQUEST = false ] ; then + # For a merged commit, build all configurations. + GRADLE_OPTS=$OPTS ./gradlew clean build +else + # On a pull request, just build debug which is much faster and catches + # obvious errors. + GRADLE_OPTS=$OPTS ./gradlew clean :mobile:assembleDebug +fi diff --git a/mobile/google-services.json b/mobile/google-services.json deleted file mode 100644 index 188551b9..00000000 --- a/mobile/google-services.json +++ /dev/null @@ -1,628 +0,0 @@ -{ - "project_info": { - "project_id": "mockproject-1234", - "project_number": "123456789000", - "name": "FirebaseQuickstarts", - "firebase_url": "https://mockproject-1234.firebaseio.com" - }, - "client": [ - { - "client_info": { - "mobilesdk_app_id": "1:123456789000:android:f1bf012572b04063", - "client_id": "android:com.google.samples.quickstart.admobexample", - "client_type": 1, - "android_client_info": { - "package_name": "com.alexstyl.specialdates", - "certificate_hash": [] - } - }, - "oauth_client": [ - { - "client_id": "123456789000-hjugbg6ud799v4c49dim8ce2usclthar.apps.googleusercontent.com", - "client_type": 1, - "android_info": { - "package_name": "com.alexstyl.specialdates", - "certificate_hash": "4C20644DE36B8F89D25650C7D1FF9FBAE650FDF7" - } - }, - { - "client_id": "123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzbSzCn1N6LWIe6wthYyrgUUSAlUsdqMb-wvTo" - } - ], - "services": { - "analytics_service": { - "status": 1 - }, - "cloud_messaging_service": { - "status": 2, - "apns_config": [] - }, - "appinvite_service": { - "status": 2, - "other_platform_oauth_client": [ - { - "client_id": "123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com", - "client_type": 3 - } - ] - }, - "google_signin_service": { - "status": 2 - }, - "ads_service": { - "status": 2, - "test_banner_ad_unit_id": "ca-app-pub-3940256099942544/6300978111", - "test_interstitial_ad_unit_id": "ca-app-pub-3940256099942544/1033173712" - } - } - }, - { - "client_info": { - "mobilesdk_app_id": "1:123456789000:android:f1bf012572b04063", - "client_id": "android:com.google.firebase.quickstart.analytics", - "client_type": 1, - "android_client_info": { - "package_name": "com.google.firebase.quickstart.analytics", - "certificate_hash": [] - } - }, - "oauth_client": [ - { - "client_id": "123456789000-hjugbg6ud799v4c49dim8ce2usclthar.apps.googleusercontent.com", - "client_type": 1, - "android_info": { - "package_name": "com.google.firebase.quickstart.analytics", - "certificate_hash": "4C20644DE36B8F89D25650C7D1FF9FBAE650FDF7" - } - }, - { - "client_id": "123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzbSzCn1N6LWIe6wthYyrgUUSAlUsdqMb-wvTo" - } - ], - "services": { - "analytics_service": { - "status": 1 - }, - "cloud_messaging_service": { - "status": 2, - "apns_config": [] - }, - "appinvite_service": { - "status": 2, - "other_platform_oauth_client": [ - { - "client_id": "123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com", - "client_type": 3 - } - ] - }, - "google_signin_service": { - "status": 2 - }, - "ads_service": { - "status": 2, - "test_banner_ad_unit_id": "ca-app-pub-3940256099942544/6300978111", - "test_interstitial_ad_unit_id": "ca-app-pub-3940256099942544/1033173712" - } - } - }, - { - "client_info": { - "mobilesdk_app_id": "1:123456789000:android:f1bf012572b04063", - "client_id": "android:com.google.samples.quickstart.app_indexing", - "client_type": 1, - "android_client_info": { - "package_name": "com.google.samples.quickstart.app_indexing", - "certificate_hash": [] - } - }, - "oauth_client": [ - { - "client_id": "123456789000-hjugbg6ud799v4c49dim8ce2usclthar.apps.googleusercontent.com", - "client_type": 1, - "android_info": { - "package_name": "com.google.samples.quickstart.app_indexing", - "certificate_hash": "4C20644DE36B8F89D25650C7D1FF9FBAE650FDF7" - } - }, - { - "client_id": "123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzbSzCn1N6LWIe6wthYyrgUUSAlUsdqMb-wvTo" - } - ], - "services": { - "analytics_service": { - "status": 1 - }, - "cloud_messaging_service": { - "status": 2, - "apns_config": [] - }, - "appinvite_service": { - "status": 2, - "other_platform_oauth_client": [ - { - "client_id": "123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com", - "client_type": 3 - } - ] - }, - "google_signin_service": { - "status": 2 - }, - "ads_service": { - "status": 2, - "test_banner_ad_unit_id": "ca-app-pub-3940256099942544/6300978111", - "test_interstitial_ad_unit_id": "ca-app-pub-3940256099942544/1033173712" - } - } - }, - { - "client_info": { - "mobilesdk_app_id": "1:123456789000:android:f1bf012572b04063", - "client_id": "android:com.google.firebase.quickstart.auth", - "client_type": 1, - "android_client_info": { - "package_name": "com.google.firebase.quickstart.auth", - "certificate_hash": [] - } - }, - "oauth_client": [ - { - "client_id": "123456789000-hjugbg6ud799v4c49dim8ce2usclthar.apps.googleusercontent.com", - "client_type": 1, - "android_info": { - "package_name": "com.google.firebase.quickstart.auth", - "certificate_hash": "4C20644DE36B8F89D25650C7D1FF9FBAE650FDF7" - } - }, - { - "client_id": "123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzbSzCn1N6LWIe6wthYyrgUUSAlUsdqMb-wvTo" - } - ], - "services": { - "analytics_service": { - "status": 1 - }, - "cloud_messaging_service": { - "status": 2, - "apns_config": [] - }, - "appinvite_service": { - "status": 2, - "other_platform_oauth_client": [ - { - "client_id": "123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com", - "client_type": 3 - } - ] - }, - "google_signin_service": { - "status": 2 - }, - "ads_service": { - "status": 2, - "test_banner_ad_unit_id": "ca-app-pub-3940256099942544/6300978111", - "test_interstitial_ad_unit_id": "ca-app-pub-3940256099942544/1033173712" - } - } - }, - { - "client_info": { - "mobilesdk_app_id": "1:123456789000:android:f1bf012572b04063", - "client_id": "android:com.google.samples.quickstart.config", - "client_type": 1, - "android_client_info": { - "package_name": "com.google.samples.quickstart.config", - "certificate_hash": [] - } - }, - "oauth_client": [ - { - "client_id": "123456789000-hjugbg6ud799v4c49dim8ce2usclthar.apps.googleusercontent.com", - "client_type": 1, - "android_info": { - "package_name": "com.google.samples.quickstart.config", - "certificate_hash": "4C20644DE36B8F89D25650C7D1FF9FBAE650FDF7" - } - }, - { - "client_id": "123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzbSzCn1N6LWIe6wthYyrgUUSAlUsdqMb-wvTo" - } - ], - "services": { - "analytics_service": { - "status": 1 - }, - "cloud_messaging_service": { - "status": 2, - "apns_config": [] - }, - "appinvite_service": { - "status": 2, - "other_platform_oauth_client": [ - { - "client_id": "123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com", - "client_type": 3 - } - ] - }, - "google_signin_service": { - "status": 2 - }, - "ads_service": { - "status": 2, - "test_banner_ad_unit_id": "ca-app-pub-3940256099942544/6300978111", - "test_interstitial_ad_unit_id": "ca-app-pub-3940256099942544/1033173712" - } - } - }, - { - "client_info": { - "mobilesdk_app_id": "1:123456789000:android:f1bf012572b04063", - "client_id": "android:com.google.samples.quickstart.crash", - "client_type": 1, - "android_client_info": { - "package_name": "com.google.samples.quickstart.crash", - "certificate_hash": [] - } - }, - "oauth_client": [ - { - "client_id": "123456789000-hjugbg6ud799v4c49dim8ce2usclthar.apps.googleusercontent.com", - "client_type": 1, - "android_info": { - "package_name": "com.google.samples.quickstart.crash", - "certificate_hash": "4C20644DE36B8F89D25650C7D1FF9FBAE650FDF7" - } - }, - { - "client_id": "123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzbSzCn1N6LWIe6wthYyrgUUSAlUsdqMb-wvTo" - } - ], - "services": { - "analytics_service": { - "status": 1 - }, - "cloud_messaging_service": { - "status": 2, - "apns_config": [] - }, - "appinvite_service": { - "status": 2, - "other_platform_oauth_client": [ - { - "client_id": "123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com", - "client_type": 3 - } - ] - }, - "google_signin_service": { - "status": 2 - }, - "ads_service": { - "status": 2, - "test_banner_ad_unit_id": "ca-app-pub-3940256099942544/6300978111", - "test_interstitial_ad_unit_id": "ca-app-pub-3940256099942544/1033173712" - } - } - }, - { - "client_info": { - "mobilesdk_app_id": "1:123456789000:android:f1bf012572b04063", - "client_id": "android:com.google.firebase.quickstart.database", - "client_type": 1, - "android_client_info": { - "package_name": "com.google.firebase.quickstart.database", - "certificate_hash": [] - } - }, - "oauth_client": [ - { - "client_id": "123456789000-hjugbg6ud799v4c49dim8ce2usclthar.apps.googleusercontent.com", - "client_type": 1, - "android_info": { - "package_name": "com.google.firebase.quickstart.database", - "certificate_hash": "4C20644DE36B8F89D25650C7D1FF9FBAE650FDF7" - } - }, - { - "client_id": "123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzbSzCn1N6LWIe6wthYyrgUUSAlUsdqMb-wvTo" - } - ], - "services": { - "analytics_service": { - "status": 1 - }, - "cloud_messaging_service": { - "status": 2, - "apns_config": [] - }, - "appinvite_service": { - "status": 2, - "other_platform_oauth_client": [ - { - "client_id": "123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com", - "client_type": 3 - } - ] - }, - "google_signin_service": { - "status": 2 - }, - "ads_service": { - "status": 2, - "test_banner_ad_unit_id": "ca-app-pub-3940256099942544/6300978111", - "test_interstitial_ad_unit_id": "ca-app-pub-3940256099942544/1033173712" - } - } - }, - { - "client_info": { - "mobilesdk_app_id": "1:123456789000:android:f1bf012572b04063", - "client_id": "android:com.google.firebase.quickstart.deeplinks", - "client_type": 1, - "android_client_info": { - "package_name": "com.google.firebase.quickstart.deeplinks", - "certificate_hash": [] - } - }, - "oauth_client": [ - { - "client_id": "123456789000-hjugbg6ud799v4c49dim8ce2usclthar.apps.googleusercontent.com", - "client_type": 1, - "android_info": { - "package_name": "com.google.firebase.quickstart.deeplinks", - "certificate_hash": "4C20644DE36B8F89D25650C7D1FF9FBAE650FDF7" - } - }, - { - "client_id": "123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzbSzCn1N6LWIe6wthYyrgUUSAlUsdqMb-wvTo" - } - ], - "services": { - "analytics_service": { - "status": 1 - }, - "cloud_messaging_service": { - "status": 2, - "apns_config": [] - }, - "appinvite_service": { - "status": 2, - "other_platform_oauth_client": [ - { - "client_id": "123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com", - "client_type": 3 - } - ] - }, - "google_signin_service": { - "status": 2 - }, - "ads_service": { - "status": 2, - "test_banner_ad_unit_id": "ca-app-pub-3940256099942544/6300978111", - "test_interstitial_ad_unit_id": "ca-app-pub-3940256099942544/1033173712" - } - } - }, - { - "client_info": { - "mobilesdk_app_id": "1:123456789000:android:f1bf012572b04063", - "client_id": "android:com.google.firebase.quickstart.invites", - "client_type": 1, - "android_client_info": { - "package_name": "com.google.firebase.quickstart.invites", - "certificate_hash": [] - } - }, - "oauth_client": [ - { - "client_id": "123456789000-hjugbg6ud799v4c49dim8ce2usclthar.apps.googleusercontent.com", - "client_type": 1, - "android_info": { - "package_name": "com.google.firebase.quickstart.invites", - "certificate_hash": "4C20644DE36B8F89D25650C7D1FF9FBAE650FDF7" - } - }, - { - "client_id": "123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzbSzCn1N6LWIe6wthYyrgUUSAlUsdqMb-wvTo" - } - ], - "services": { - "analytics_service": { - "status": 1 - }, - "cloud_messaging_service": { - "status": 2, - "apns_config": [] - }, - "appinvite_service": { - "status": 2, - "other_platform_oauth_client": [ - { - "client_id": "123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com", - "client_type": 3 - } - ] - }, - "google_signin_service": { - "status": 2 - }, - "ads_service": { - "status": 2, - "test_banner_ad_unit_id": "ca-app-pub-3940256099942544/6300978111", - "test_interstitial_ad_unit_id": "ca-app-pub-3940256099942544/1033173712" - } - } - }, - { - "client_info": { - "mobilesdk_app_id": "1:123456789000:android:f1bf012572b04063", - "client_id": "android:com.google.firebase.quickstart.fcm", - "client_type": 1, - "android_client_info": { - "package_name": "com.google.firebase.quickstart.fcm", - "certificate_hash": [] - } - }, - "oauth_client": [ - { - "client_id": "123456789000-hjugbg6ud799v4c49dim8ce2usclthar.apps.googleusercontent.com", - "client_type": 1, - "android_info": { - "package_name": "com.google.firebase.quickstart.fcm", - "certificate_hash": "4C20644DE36B8F89D25650C7D1FF9FBAE650FDF7" - } - }, - { - "client_id": "123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzbSzCn1N6LWIe6wthYyrgUUSAlUsdqMb-wvTo" - } - ], - "services": { - "analytics_service": { - "status": 1 - }, - "cloud_messaging_service": { - "status": 2, - "apns_config": [] - }, - "appinvite_service": { - "status": 2, - "other_platform_oauth_client": [ - { - "client_id": "123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com", - "client_type": 3 - } - ] - }, - "google_signin_service": { - "status": 2 - }, - "ads_service": { - "status": 2, - "test_banner_ad_unit_id": "ca-app-pub-3940256099942544/6300978111", - "test_interstitial_ad_unit_id": "ca-app-pub-3940256099942544/1033173712" - } - } - }, - { - "client_info": { - "mobilesdk_app_id": "1:123456789000:android:f1bf012572b04063", - "client_id": "android:com.google.firebase.quickstart.firebasestorage", - "client_type": 1, - "android_client_info": { - "package_name": "com.google.firebase.quickstart.firebasestorage", - "certificate_hash": [] - } - }, - "oauth_client": [ - { - "client_id": "123456789000-hjugbg6ud799v4c49dim8ce2usclthar.apps.googleusercontent.com", - "client_type": 1, - "android_info": { - "package_name": "com.google.firebase.quickstart.firebasestorage", - "certificate_hash": "4C20644DE36B8F89D25650C7D1FF9FBAE650FDF7" - } - }, - { - "client_id": "123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzbSzCn1N6LWIe6wthYyrgUUSAlUsdqMb-wvTo" - } - ], - "services": { - "analytics_service": { - "status": 1 - }, - "cloud_messaging_service": { - "status": 2, - "apns_config": [] - }, - "appinvite_service": { - "status": 2, - "other_platform_oauth_client": [ - { - "client_id": "123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com", - "client_type": 3 - } - ] - }, - "google_signin_service": { - "status": 2 - }, - "ads_service": { - "status": 2, - "test_banner_ad_unit_id": "ca-app-pub-3940256099942544/6300978111", - "test_interstitial_ad_unit_id": "ca-app-pub-3940256099942544/1033173712" - } - } - } - ], - "client_info": [], - "ARTIFACT_VERSION": "1" -} diff --git a/mobile/src/debug/java/com/alexstyl/specialdates/events/peopleevents/DebugPeopleEventsUpdater.java b/mobile/src/debug/java/com/alexstyl/specialdates/events/peopleevents/DebugPeopleEventsUpdater.java index c7e9efc8..b59eb4d9 100644 --- a/mobile/src/debug/java/com/alexstyl/specialdates/events/peopleevents/DebugPeopleEventsUpdater.java +++ b/mobile/src/debug/java/com/alexstyl/specialdates/events/peopleevents/DebugPeopleEventsUpdater.java @@ -2,24 +2,45 @@ import android.content.Context; +import com.alexstyl.specialdates.contact.ContactsProvider; +import com.alexstyl.specialdates.events.database.EventColumns; +import com.alexstyl.specialdates.events.database.EventSQLiteOpenHelper; import com.alexstyl.specialdates.events.namedays.NamedayDatabaseRefresher; +import com.alexstyl.specialdates.events.namedays.NamedayPreferences; +import com.alexstyl.specialdates.events.namedays.calendar.resource.NamedayCalendarProvider; +import com.alexstyl.specialdates.util.DateParser; public class DebugPeopleEventsUpdater { - private final BirthdayDatabaseRefresher birthdayDatabaseRefresher; - private NamedayDatabaseRefresher namedayDatabaseRefresher; + private final PeopleEventsDatabaseRefresher peopleEventsDatabaseRefresher; + private final NamedayDatabaseRefresher namedayDatabaseRefresher; public static DebugPeopleEventsUpdater newInstance(Context context) { - return new DebugPeopleEventsUpdater(BirthdayDatabaseRefresher.newInstance(context), NamedayDatabaseRefresher.newInstance(context)); + ContactsProvider contactsProvider = ContactsProvider.get(context); + PeopleEventsRepository repository = new PeopleEventsRepository(context.getContentResolver(), contactsProvider, DateParser.INSTANCE); + ContactEventsMarshaller deviceMarshaller = new ContactEventsMarshaller(EventColumns.SOURCE_DEVICE); + PeopleEventsPersister databaseProvider = new PeopleEventsPersister(new EventSQLiteOpenHelper(context)); + PeopleEventsDatabaseRefresher refresher = new PeopleEventsDatabaseRefresher(repository, deviceMarshaller, databaseProvider); + + NamedayPreferences namedayPreferences = NamedayPreferences.newInstance(context); + NamedayCalendarProvider namedayCalendarProvider = NamedayCalendarProvider.newInstance(context.getResources()); + PeopleNamedaysCalculator peopleNamedaysCalculator = new PeopleNamedaysCalculator(namedayPreferences, namedayCalendarProvider, contactsProvider); + ContactEventsMarshaller mementoMarshaller = new ContactEventsMarshaller(EventColumns.SOURCE_MEMENTO); + return new DebugPeopleEventsUpdater(refresher, new NamedayDatabaseRefresher( + namedayPreferences, + databaseProvider, + mementoMarshaller, + peopleNamedaysCalculator + )); } - public DebugPeopleEventsUpdater(BirthdayDatabaseRefresher birthdayDatabaseRefresher, NamedayDatabaseRefresher namedayDatabaseRefresher) { - this.birthdayDatabaseRefresher = birthdayDatabaseRefresher; + private DebugPeopleEventsUpdater(PeopleEventsDatabaseRefresher peopleEventsDatabaseRefresher, NamedayDatabaseRefresher namedayDatabaseRefresher) { + this.peopleEventsDatabaseRefresher = peopleEventsDatabaseRefresher; this.namedayDatabaseRefresher = namedayDatabaseRefresher; } public void refresh() { - birthdayDatabaseRefresher.refreshBirthdays(); + peopleEventsDatabaseRefresher.refreshEvents(); namedayDatabaseRefresher.refreshNamedaysIfEnabled(); } } diff --git a/mobile/src/debug/java/com/alexstyl/specialdates/events/peopleevents/EventType.java b/mobile/src/debug/java/com/alexstyl/specialdates/events/peopleevents/EventType.java new file mode 100644 index 00000000..cb0f4fb4 --- /dev/null +++ b/mobile/src/debug/java/com/alexstyl/specialdates/events/peopleevents/EventType.java @@ -0,0 +1,17 @@ +package com.alexstyl.specialdates.events.peopleevents; + +import android.content.res.Resources; +import android.support.annotation.ColorRes; + +import com.alexstyl.specialdates.events.database.EventTypeId; + +public interface EventType { + + String getEventName(Resources resources); + + @ColorRes + int getColorRes(); + + @EventTypeId + int getId(); +} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/addevent/BirthdayFormPresenter.java b/mobile/src/main/java/com/alexstyl/specialdates/addevent/BirthdayFormPresenter.java index dc5a7016..1d8e16c6 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/addevent/BirthdayFormPresenter.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/addevent/BirthdayFormPresenter.java @@ -27,14 +27,15 @@ public BirthdayFormPresenter(ContactHeroView contactHeroView, BirthdayLabelView public void displayContactAsSelected(final Contact contact) { selectedContact = contact; contactHeroView.displayAvatarFor(contact); - if (contact.hasDateOfBirth()) { - birthdayLabel.displayBirthday(contact.getDateOfBirth()); - addButton.setText(R.string.add_birthday_store_button_update_label); - validator = new BirthdayCreationValidator(contact.getDateOfBirth()); - } else { - birthdayLabel.displayNoBirthday(); - addButton.setText(R.string.add_birthday_store_button_add_label); - } + // TODO +// if (contact.hasDateOfBirth()) { +// birthdayLabel.displayBirthday(contact.getDateOfBirth()); +// addButton.setText(R.string.add_birthday_store_button_update_label); +// validator = new BirthdayCreationValidator(contact.getDateOfBirth()); +// } else { + birthdayLabel.displayNoBirthday(); + addButton.setText(R.string.add_birthday_store_button_add_label); +// } updateAddButton(); } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/addevent/BirthdayLabelView.java b/mobile/src/main/java/com/alexstyl/specialdates/addevent/BirthdayLabelView.java index 03a9ffd5..55318881 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/addevent/BirthdayLabelView.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/addevent/BirthdayLabelView.java @@ -14,7 +14,7 @@ public class BirthdayLabelView extends LinearLayout { - private static final DateDisplayStringCreator DATE_DISPLAY_STRING_CREATOR = DateDisplayStringCreator.getInstance(); + private static final DateDisplayStringCreator DATE_DISPLAY_STRING_CREATOR = DateDisplayStringCreator.INSTANCE; private TextView label; private Date birthday; diff --git a/mobile/src/main/java/com/alexstyl/specialdates/addevent/BirthdayPickerDialog.java b/mobile/src/main/java/com/alexstyl/specialdates/addevent/BirthdayPickerDialog.java index f945bbf7..cf53aa5c 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/addevent/BirthdayPickerDialog.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/addevent/BirthdayPickerDialog.java @@ -24,7 +24,6 @@ public class BirthdayPickerDialog extends MementoDialog { public static final String TAG = "fm_tag:birthday"; private static final String KEY_DATE = "key:birthday"; - private DateParser parser = new DateParser(); private OnBirthdaySelectedListener listener; private BirthdayDatePicker datePicker; @@ -63,7 +62,7 @@ private Optional extractBirthday(Bundle arguments) { private Optional extractFrom(String birthday) { try { - Date parsedDate = parser.parse(birthday); + Date parsedDate = DateParser.INSTANCE.parse(birthday); if (parsedDate.hasYear()) { return new Optional<>(Date.on(parsedDate.getDayOfMonth(), parsedDate.getMonth(), parsedDate.getYear())); } else { 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 b2d37155..2f93e398 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/addevent/BirthdayQuery.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/addevent/BirthdayQuery.java @@ -25,8 +25,7 @@ public BirthdayQuery(ContentResolver contentResolver) { } public ContactQuery forContact(Contact contact) { - DateParser dateParser = new DateParser(); - return new ContactQuery(contact, dateParser, contentResolver); + return new ContactQuery(contact, DateParser.INSTANCE, contentResolver); } public static class ContactQuery { diff --git a/mobile/src/main/java/com/alexstyl/specialdates/contact/Contact.java b/mobile/src/main/java/com/alexstyl/specialdates/contact/Contact.java index 64ce362a..0efeba13 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/contact/Contact.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/contact/Contact.java @@ -5,9 +5,7 @@ import android.view.View; import com.alexstyl.specialdates.DisplayName; -import com.alexstyl.specialdates.Optional; import com.alexstyl.specialdates.contact.actions.LabeledAction; -import com.alexstyl.specialdates.date.Date; import java.util.List; @@ -15,13 +13,11 @@ public abstract class Contact { protected final long contactID; protected final DisplayName displayName; - private final Optional dateOfBirth; private List actions; - public Contact(long id, DisplayName displayName, Optional dateOfBirth) { + public Contact(long id, DisplayName displayName) { this.contactID = id; this.displayName = displayName; - this.dateOfBirth = dateOfBirth; } @Override @@ -56,14 +52,6 @@ public List getUserActions(Context context) { */ protected abstract List onBuildActions(Context context); - public Date getDateOfBirth() { - return dateOfBirth.get(); - } - - public boolean hasDateOfBirth() { - return dateOfBirth.isPresent(); - } - public abstract Uri getLookupUri(); /** diff --git a/mobile/src/main/java/com/alexstyl/specialdates/contact/ContactProvider.java b/mobile/src/main/java/com/alexstyl/specialdates/contact/ContactProvider.java deleted file mode 100644 index 4e9f9ab7..00000000 --- a/mobile/src/main/java/com/alexstyl/specialdates/contact/ContactProvider.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.alexstyl.specialdates.contact; - -import android.content.ContentResolver; -import android.content.Context; -import android.os.Handler; - -import com.alexstyl.specialdates.util.ContactsObserver; -import com.alexstyl.specialdates.util.DateParser; - -public class ContactProvider { - - private static final int CACHE_SIZE = 2 * 1024; - private final ContactCache cache; - private final DeviceContactFactory deviceContactFactory; - - private static ContactProvider INSTANCE; - - public static ContactProvider get(Context context) { - if (INSTANCE == null) { - ContentResolver contentResolver = context.getContentResolver(); - DateParser dateParser = new DateParser(); - DeviceContactFactory factory = new DeviceContactFactory(contentResolver, dateParser); - ContactCache contactCache = new ContactCache<>(CACHE_SIZE); - INSTANCE = new ContactProvider(contactCache, factory); - INSTANCE.initialise(contentResolver); - } - return INSTANCE; - } - - private void initialise(ContentResolver contentResolver) { - ContactsObserver observer = new ContactsObserver(contentResolver, new Handler()); - observer.registerWith(evictIfContactChanged); - } - - private ContactProvider(ContactCache cache, DeviceContactFactory deviceContactFactory) { - this.cache = cache; - this.deviceContactFactory = deviceContactFactory; - } - - public DeviceContact getOrCreateContact(long contactID) throws ContactNotFoundException { - DeviceContact deviceContact = cache.getContact(contactID); - if (deviceContact == null) { - deviceContact = deviceContactFactory.createContactWithId(contactID); - cache.addContact(deviceContact); - } - return deviceContact; - } - - private final ContactsObserver.Callback evictIfContactChanged = new ContactsObserver.Callback() { - @Override - public void onContactsUpdated() { - cache.evictAll(); - } - }; - -} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/contact/ContactsProvider.java b/mobile/src/main/java/com/alexstyl/specialdates/contact/ContactsProvider.java new file mode 100644 index 00000000..4b9779d4 --- /dev/null +++ b/mobile/src/main/java/com/alexstyl/specialdates/contact/ContactsProvider.java @@ -0,0 +1,70 @@ +package com.alexstyl.specialdates.contact; + +import android.content.ContentResolver; +import android.content.Context; +import android.os.Handler; + +import com.alexstyl.specialdates.events.peopleevents.DeviceContactsQuery; +import com.alexstyl.specialdates.util.ContactsObserver; + +import java.util.List; + +public class ContactsProvider { + + private static ContactsProvider INSTANCE; + + private static final int CACHE_SIZE = 2 * 1024; + private final ContactCache cache; + private final DeviceContactFactory deviceContactFactory; + private final DeviceContactsQuery deviceContactsQuery; + + public static ContactsProvider get(Context context) { + if (INSTANCE == null) { + ContentResolver contentResolver = context.getContentResolver(); + DeviceContactFactory factory = new DeviceContactFactory(contentResolver); + ContactCache contactCache = new ContactCache<>(CACHE_SIZE); + DeviceContactsQuery deviceContactsQuery = new DeviceContactsQuery(contentResolver); + INSTANCE = new ContactsProvider(contactCache, factory, deviceContactsQuery); + INSTANCE.initialise(contentResolver); + } + return INSTANCE; + } + + private void initialise(ContentResolver contentResolver) { + ContactsObserver observer = new ContactsObserver(contentResolver, new Handler()); + observer.registerWith(evictIfContactChanged); + } + + private ContactsProvider(ContactCache cache, DeviceContactFactory deviceContactFactory, DeviceContactsQuery deviceContactsQuery) { + this.cache = cache; + this.deviceContactFactory = deviceContactFactory; + this.deviceContactsQuery = deviceContactsQuery; + } + + public Contact getOrCreateContact(long contactID) throws ContactNotFoundException { + Contact deviceContact = cache.getContact(contactID); + if (deviceContact == null) { + deviceContact = deviceContactFactory.createContactWithId(contactID); + cache.addContact(deviceContact); + } + return deviceContact; + } + + public List fetchAllDeviceContacts() { + // evict the cache. there is no way of reusing existing objects... + // this might not be the most optimal thing to do + cache.evictAll(); + List allContacts = deviceContactsQuery.getAllContacts(); + for (Contact allContact : allContacts) { + cache.addContact(allContact); + } + return allContacts; + } + + private final ContactsObserver.Callback evictIfContactChanged = new ContactsObserver.Callback() { + @Override + public void onContactsUpdated() { + cache.evictAll(); + } + }; +} 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 74136715..321e3b90 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/contact/ContactsQuery.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/contact/ContactsQuery.java @@ -1,40 +1,20 @@ package com.alexstyl.specialdates.contact; -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.database.Cursor; import android.net.Uri; -import android.os.Build; import android.provider.ContactsContract; +import android.provider.ContactsContract.Contacts; -@TargetApi(Build.VERSION_CODES.HONEYCOMB) -class ContactsQuery { - public final static Uri CONTENT_URI = ContactsContract.Data.CONTENT_URI; +public final class ContactsQuery { - 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 = ContactsContract.Contacts.DISPLAY_NAME_PRIMARY; - - @SuppressLint("InlinedApi") + public final static Uri CONTENT_URI = ContactsContract.Contacts.CONTENT_URI; public static final String[] PROJECTION = { - ContactsContract.Data.MIMETYPE,//0 - ContactsContract.Data.CONTACT_ID,//1 - COL_LOOKUP, //2 - COL_DISPLAY_NAME,//3 - ContactsContract.CommonDataKinds.Event.START_DATE, //4 + Contacts._ID, //0 + Contacts.LOOKUP_KEY,//1 + Contacts.DISPLAY_NAME_PRIMARY,//2 }; + public final static String SORT_ORDER = Contacts._ID; - public static final int ROW_TYPE = 0; - - public static final int ID = 1; - public static final int LOOKUP_KEY = 2; - public static final int DISPLAY_NAME = 3; - public static final int BIRTHDAY = 4; - - public static boolean isBirthdayRow(Cursor cursor) { - return cursor.getString(ROW_TYPE).equals(ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE); - } + public static final int CONTACT_ID = 0; + public static final int LOOKUP_KEY = 1; + public static final int DISPLAY_NAME = 2; } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/contact/DeviceContact.java b/mobile/src/main/java/com/alexstyl/specialdates/contact/DeviceContact.java index 888ffe2b..5d23627d 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/contact/DeviceContact.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/contact/DeviceContact.java @@ -12,11 +12,9 @@ import android.widget.Toast; import com.alexstyl.specialdates.DisplayName; -import com.alexstyl.specialdates.Optional; import com.alexstyl.specialdates.R; import com.alexstyl.specialdates.contact.actions.ContactActionFactory; import com.alexstyl.specialdates.contact.actions.LabeledAction; -import com.alexstyl.specialdates.date.Date; import com.alexstyl.specialdates.util.ContactUtils; import java.util.ArrayList; @@ -26,8 +24,8 @@ public class DeviceContact extends Contact { private final String lookupKey; - DeviceContact(long contactId, DisplayName displayName, String lookupKey, Optional dateOfBirth) { - super(contactId, displayName, dateOfBirth); + public DeviceContact(long contactId, DisplayName displayName, String lookupKey) { + super(contactId, displayName); this.lookupKey = lookupKey; } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/contact/DeviceContactFactory.java b/mobile/src/main/java/com/alexstyl/specialdates/contact/DeviceContactFactory.java index 6ab40eae..77d4b48d 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/contact/DeviceContactFactory.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/contact/DeviceContactFactory.java @@ -2,89 +2,75 @@ import android.content.ContentResolver; import android.database.Cursor; -import android.provider.ContactsContract; +import android.provider.ContactsContract.Contacts; +import android.support.annotation.NonNull; import com.alexstyl.specialdates.DisplayName; -import com.alexstyl.specialdates.ErrorTracker; -import com.alexstyl.specialdates.Optional; -import com.alexstyl.specialdates.date.Date; -import com.alexstyl.specialdates.date.DateParseException; -import com.alexstyl.specialdates.util.DateParser; + +import static com.alexstyl.specialdates.contact.ContactsQuery.SORT_ORDER; class DeviceContactFactory { + private static final String SELECTION_CONTACT_WITH_ID = Contacts._ID + " = ?"; private final ContentResolver resolver; - private final DateParser dateParser; - DeviceContactFactory(ContentResolver contentResolver, DateParser dateParser) { + DeviceContactFactory(ContentResolver contentResolver) { resolver = contentResolver; - this.dateParser = dateParser; } - public DeviceContact createContactWithId(long contactID) throws ContactNotFoundException { - String selection = ContactsContract.Data.CONTACT_ID + " = " + contactID; - String birthdayRow = "(" + ContactsContract.Data.MIMETYPE + " = ? AND " + ContactsContract.CommonDataKinds.Event.TYPE + "=" + - ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY + ")"; - String nameRow = ContactsContract.Data.MIMETYPE + " = ?"; - String str = selection + " AND (" + birthdayRow + " OR " + nameRow + ")"; - - Cursor cursor = resolver.query(ContactsQuery.CONTENT_URI, ContactsQuery.PROJECTION, str, new String[]{ - ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE, - ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE - }, ContactsQuery.SORT_ORDER); + Contact createContactWithId(long contactID) throws ContactNotFoundException { + Cursor cursor = queryContactsWithContactId(contactID); if (isInvalid(cursor)) { throw new RuntimeException("Cursor was invalid"); } - String lookupKey = null; - DisplayName displayName = null; - Optional birthday = Optional.absent(); - boolean found = false; - while (cursor.moveToNext()) { - if (ContactsQuery.isBirthdayRow(cursor)) { - birthday = getBirthdayFrom(cursor); - found = true; - } - - if (displayName == null) { - displayName = getDisplayNameFrom(cursor); - lookupKey = getLookupKeyFrom(cursor); - found = true; + try { + if (cursor.moveToFirst()) { + return createContactFrom(cursor); } + } finally { + cursor.close(); } + throw new ContactNotFoundException(contactID); + } - if (!found) { - throw new ContactNotFoundException(contactID); - } - DeviceContact contact = new DeviceContact(contactID, displayName, lookupKey, birthday); - cursor.close(); - return contact; + public Contact createContactFrom(Cursor cursor) { + long contactID = getContactIdFrom(cursor); + DisplayName displayName = getDisplayNameFrom(cursor); + String lookupKey = getLookupKeyFrom(cursor); + return new DeviceContact(contactID, displayName, lookupKey); } - private boolean isInvalid(Cursor cursor) { - return cursor == null || cursor.isClosed(); + private Cursor queryContactsWithContactId(long contactID) { + return resolver.query( + ContactsQuery.CONTENT_URI, + ContactsQuery.PROJECTION, + SELECTION_CONTACT_WITH_ID, + makeSelectionArgumentsFor(contactID), + SORT_ORDER + " LIMIT 1" + ); } - private String getLookupKeyFrom(Cursor cursor) { - return cursor.getString(ContactsQuery.LOOKUP_KEY); + @NonNull + private String[] makeSelectionArgumentsFor(long contactID) { + return new String[]{ + String.valueOf(contactID) + }; } - private DisplayName getDisplayNameFrom(Cursor cursor) { - return DisplayName.from(cursor.getString(ContactsQuery.DISPLAY_NAME)); + private static boolean isInvalid(Cursor cursor) { + return cursor == null || cursor.isClosed(); } - private Optional getBirthdayFrom(Cursor cursor) { - String birthdayRaw = cursor.getString(ContactsQuery.BIRTHDAY); - try { - Date birthday = getBirthdayFrom(birthdayRaw); - return new Optional<>(birthday); - } catch (DateParseException e) { - ErrorTracker.track(e); - return Optional.absent(); - } + private static long getContactIdFrom(Cursor cursor) { + return cursor.getLong(ContactsQuery.CONTACT_ID); } - private Date getBirthdayFrom(String birthdayRaw) throws DateParseException { - return dateParser.parse(birthdayRaw); + private static DisplayName getDisplayNameFrom(Cursor cursor) { + return DisplayName.from(cursor.getString(ContactsQuery.DISPLAY_NAME)); + } + + private static String getLookupKeyFrom(Cursor cursor) { + return cursor.getString(ContactsQuery.LOOKUP_KEY); } } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/date/CelebrationDate.java b/mobile/src/main/java/com/alexstyl/specialdates/date/CelebrationDate.java index b8dc2a2c..6bb91417 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/date/CelebrationDate.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/date/CelebrationDate.java @@ -24,10 +24,11 @@ public CelebrationDate(Date date, ContactEvents contactEvent, Optional NO_EVENT_ID = Optional.absent(); + + private final Optional deviceEventId; private final EventType eventType; private final Contact contact; private final Date date; - public ContactEvent(EventType eventType, Date date, Contact contact) { + public ContactEvent(Optional deviceEventId, EventType eventType, Date date, Contact contact) { + this.deviceEventId = deviceEventId; this.eventType = eventType; this.date = date; this.contact = contact; } public String getLabel(Resources resources) { - if (eventType == EventType.BIRTHDAY) { - Date birthday = contact.getDateOfBirth(); - if (birthday.hasYear()) { - int age = date.getYear() - birthday.getYear(); + if (eventType == StandardEventType.BIRTHDAY) { + if (date.hasYear()) { + int age = Date.CURRENT_YEAR - date.getYear(); if (age > 0) { return resources.getString(R.string.turns_age, age); } } } - return resources.getString(eventType.nameRes()); + return eventType.getEventName(resources); } public Date getDate() { @@ -42,11 +44,11 @@ public EventType getType() { return eventType; } - public int getYear() { - return date.getYear(); - } - public Contact getContact() { return contact; } + + public Optional getDeviceEventId() { + return deviceEventId; + } } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/date/Date.java b/mobile/src/main/java/com/alexstyl/specialdates/date/Date.java index dd063113..34a6283b 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/date/Date.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/date/Date.java @@ -73,11 +73,6 @@ public int getYear() { return year.get(); } - @Override - public String toString() { - return DateDisplayStringCreator.getInstance().stringOf(this); - } - public long toMillis() { return localDate.toDate().getTime(); } @@ -112,7 +107,7 @@ public int daysDifferenceTo(Date otherEvent) { @Override public String toShortDate() { - return DateDisplayStringCreator.getInstance().stringOf(this); + return DateDisplayStringCreator.INSTANCE.stringOf(this); } public boolean hasYear() { @@ -143,4 +138,9 @@ public int hashCode() { result = 31 * result + year.hashCode(); return result; } + + @Override + public String toString() { + return DateDisplayStringCreator.INSTANCE.stringOf(this); + } } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/date/DateComparator.java b/mobile/src/main/java/com/alexstyl/specialdates/date/DateComparator.java index e260aee6..d6e1c037 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/date/DateComparator.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/date/DateComparator.java @@ -5,10 +5,6 @@ public enum DateComparator implements Comparator { INSTANCE; - public static DateComparator get() { - return INSTANCE; - } - @Override public int compare(Date o1, Date o2) { if (o1.hasYear() && o2.hasYear()) { 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 fbde67de..402abb4d 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/date/DateDisplayStringCreator.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/date/DateDisplayStringCreator.java @@ -5,20 +5,12 @@ import com.alexstyl.specialdates.MementoApplication; -public final class DateDisplayStringCreator { +public enum DateDisplayStringCreator { + INSTANCE; private static final String SEPARATOR = "-"; private static final String ZERO = "0"; - private static DateDisplayStringCreator instance; - - public static DateDisplayStringCreator getInstance() { - if (instance == null) { - instance = new DateDisplayStringCreator(); - } - return instance; - } - public String stringOf(Date date) { StringBuilder str = new StringBuilder(); addYear(date, str); diff --git a/mobile/src/main/java/com/alexstyl/specialdates/datedetails/BankHolidayCardViewHolder.java b/mobile/src/main/java/com/alexstyl/specialdates/datedetails/BankHolidayCardViewHolder.java index d0a20eef..70466852 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/datedetails/BankHolidayCardViewHolder.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/datedetails/BankHolidayCardViewHolder.java @@ -3,20 +3,19 @@ import android.support.v7.widget.RecyclerView; import android.view.View; -import com.alexstyl.specialdates.datedetails.BankholidayCardView; import com.alexstyl.specialdates.events.bankholidays.BankHoliday; class BankHolidayCardViewHolder extends RecyclerView.ViewHolder { - private final BankholidayCardView bankholidayCardView; + private final BankHolidayCardView bankHolidayCardView; - public BankHolidayCardViewHolder(View itemView) { + BankHolidayCardViewHolder(View itemView) { super(itemView); - bankholidayCardView = (BankholidayCardView) itemView; + bankHolidayCardView = (BankHolidayCardView) itemView; } void bind(BankHoliday holiday) { - bankholidayCardView.display(holiday); + bankHolidayCardView.display(holiday); } } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/datedetails/BankholidayCardView.java b/mobile/src/main/java/com/alexstyl/specialdates/datedetails/BankholidayCardView.java index a4370a02..a5c2ac25 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/datedetails/BankholidayCardView.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/datedetails/BankholidayCardView.java @@ -8,15 +8,14 @@ import com.alexstyl.specialdates.events.bankholidays.BankHoliday; import com.alexstyl.specialdates.ui.MementoCardView; -public class BankholidayCardView extends MementoCardView { +class BankHolidayCardView extends MementoCardView { private final TextView text; - public BankholidayCardView(Context context, AttributeSet attrs) { + public BankHolidayCardView(Context context, AttributeSet attrs) { super(context, attrs); inflate(context, R.layout.merge_bankholidaycardview, this); - text = (TextView) findViewById(R.id.bankholiday_text); } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/datedetails/DateDetailsAdapter.java b/mobile/src/main/java/com/alexstyl/specialdates/datedetails/DateDetailsAdapter.java index bd2a00fd..f5f62006 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/datedetails/DateDetailsAdapter.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/datedetails/DateDetailsAdapter.java @@ -12,13 +12,11 @@ import com.alexstyl.specialdates.date.ContactEvent; import com.alexstyl.specialdates.date.Date; 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.namedays.calendar.NamedayCalendar; -import com.alexstyl.specialdates.events.namedays.calendar.resource.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.NamedayCalendar; +import com.alexstyl.specialdates.events.namedays.calendar.resource.NamedayCalendarProvider; import com.alexstyl.specialdates.images.ImageLoader; import com.alexstyl.specialdates.support.AskForSupport; import com.alexstyl.specialdates.support.OnSupportCardClickListener; @@ -50,14 +48,13 @@ public static DateDetailsAdapter newInstance(Context context, Date dateToDisplay, OnSupportCardClickListener supportListener, NamedayCardView.OnShareClickListener namedayListener, - ContactCardListener contactCardListener) { + ContactCardListener contactCardListener, + Optional bankholiday + ) { LayoutInflater layoutInflater = LayoutInflater.from(context); Resources resources = context.getResources(); ImageLoader imageLoader = ImageLoader.createSquareThumbnailLoader(resources); CardActionRecycler cardActionRecycler = new CardActionRecycler(layoutInflater); - - BankHolidaysPreferences bankHolidaysPreferences = BankHolidaysPreferences.newInstance(context); - Optional bankholiday = getBankHolidayOptionalForDate(dateToDisplay, bankHolidaysPreferences); Optional nameday = getNamedayOptionalForDate(dateToDisplay, context); return new DateDetailsAdapter( @@ -81,23 +78,14 @@ private static Optional getNamedayOptionalForDate(Date dateToDispl } - private static Optional getBankHolidayOptionalForDate(Date dateToDisplay, BankHolidaysPreferences bankHolidaysPreferences) { - if (bankHolidaysPreferences.isEnabled()) { - BankholidayCalendar repository = BankholidayCalendar.get(); - return repository.getBankholidayFor(dateToDisplay); - } else { - return Optional.absent(); - } - } - - DateDetailsAdapter(ImageLoader imageLoader, - Optional nameday, - Optional bankholiday, - CardActionRecycler cardActionRecycler, - AskForSupport askForSupport, - ContactCardListener contactCardListener, - NamedayCardView.OnShareClickListener namedayListener, - OnSupportCardClickListener supportListener + private DateDetailsAdapter(ImageLoader imageLoader, + Optional nameday, + Optional bankholiday, + CardActionRecycler cardActionRecycler, + AskForSupport askForSupport, + ContactCardListener contactCardListener, + NamedayCardView.OnShareClickListener namedayListener, + OnSupportCardClickListener supportListener ) { this.nameday = nameday; this.imageLoader = imageLoader; 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 e03910e1..7b10e8bc 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/datedetails/DateDetailsFragment.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/datedetails/DateDetailsFragment.java @@ -24,6 +24,7 @@ import com.alexstyl.specialdates.BuildConfig; import com.alexstyl.specialdates.ExternalNavigator; +import com.alexstyl.specialdates.Optional; import com.alexstyl.specialdates.R; import com.alexstyl.specialdates.analytics.Action; import com.alexstyl.specialdates.analytics.ActionWithParameters; @@ -34,19 +35,29 @@ import com.alexstyl.specialdates.date.ContactEvent; import com.alexstyl.specialdates.date.Date; import com.alexstyl.specialdates.date.DateDisplayStringCreator; +import com.alexstyl.specialdates.date.MonthInt; +import com.alexstyl.specialdates.events.bankholidays.BankHoliday; +import com.alexstyl.specialdates.events.bankholidays.BankHolidayProvider; +import com.alexstyl.specialdates.events.bankholidays.BankHolidaysPreferences; +import com.alexstyl.specialdates.events.bankholidays.GreekBankHolidaysCalculator; import com.alexstyl.specialdates.events.namedays.NamesInADate; +import com.alexstyl.specialdates.events.namedays.calendar.OrthodoxEasterCalculator; import com.alexstyl.specialdates.permissions.ContactPermissionRequest; import com.alexstyl.specialdates.permissions.PermissionNavigator; import com.alexstyl.specialdates.permissions.PermissionChecker; +import com.alexstyl.specialdates.service.PeopleEventsProvider; import com.alexstyl.specialdates.support.AskForSupport; import com.alexstyl.specialdates.support.OnSupportCardClickListener; import com.alexstyl.specialdates.ui.base.MementoFragment; import com.alexstyl.specialdates.ui.dialog.ProgressFragmentDialog; +import com.alexstyl.specialdates.util.ContactsObserver; import com.alexstyl.specialdates.util.ShareNamedaysIntentCreator; import java.util.List; -public class DateDetailsFragment extends MementoFragment implements LoaderManager.LoaderCallbacks> { +import static com.alexstyl.specialdates.Optional.absent; + +public class DateDetailsFragment extends MementoFragment { private static final ActionWithParameters CONTACT_INTERACT_EXTERNAL = new ActionWithParameters(Action.INTERACT_CONTACT, "source", "external"); @@ -150,7 +161,7 @@ public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); if (permissions.permissionIsPresent()) { - getLoaderManager().initLoader(LOADER_ID_EVENTS, null, this); + getLoaderManager().initLoader(LOADER_ID_EVENTS, null, loaderCallbacks); } else { permissions.requestForPermission(); } @@ -175,7 +186,7 @@ public void onCreate(Bundle savedInstanceState) { private final ContactPermissionRequest.PermissionCallbacks permissionCallbacks = new ContactPermissionRequest.PermissionCallbacks() { @Override public void onPermissionGranted() { - getLoaderManager().initLoader(LOADER_ID_EVENTS, null, DateDetailsFragment.this); + getLoaderManager().initLoader(LOADER_ID_EVENTS, null, loaderCallbacks); } @Override @@ -199,7 +210,7 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Override public void onNamedaysShared(NamesInADate namedays) { - Intent intent = new ShareNamedaysIntentCreator(getActivity(), new DateDisplayStringCreator()).createNamedaysShareIntent(namedays); + Intent intent = new ShareNamedaysIntentCreator(getActivity(), DateDisplayStringCreator.INSTANCE).createNamedaysShareIntent(namedays); try { Intent chooserIntent = Intent.createChooser(intent, getString(R.string.share_via)); startActivity(chooserIntent); @@ -245,12 +256,15 @@ public int getSpanSize(int position) { } ); + Optional bankHoliday = getBankHolidayOn(date); + adapter = DateDetailsAdapter.newInstance( getActivity(), date, supportListener, namedayShareListener, - contactCardListener + contactCardListener, + bankHoliday ); recyclerView.setHasFixedSize(true); recyclerView.setLayoutManager(layoutManager); @@ -258,31 +272,45 @@ public int getSpanSize(int position) { recyclerView.addItemDecoration(spacingDecoration = new GridWithHeaderSpacesItemDecoration(getResources().getDimensionPixelSize(R.dimen.card_spacing), adapter)); } - @Override - public Loader> onCreateLoader(int loaderID, Bundle bundle) { - if (loaderID == LOADER_ID_EVENTS) { - return DateDetailsLoader.newInstance(getActivity(), date); + private Optional getBankHolidayOn(Date date) { + BankHolidaysPreferences preferences = BankHolidaysPreferences.newInstance(getActivity()); + if (preferences.isEnabled()) { + BankHolidayProvider bankHolidayProvider = new BankHolidayProvider(new GreekBankHolidaysCalculator(OrthodoxEasterCalculator.INSTANCE)); + return bankHolidayProvider.calculateBankHolidayOn(date); } - return null; + return absent(); } - @Override - public void onLoadFinished(Loader> EventItemLoader, List result) { - adapter.setEvents(result); - if (adapter.isLoadingDetailedCards()) { - layoutManager.setSpanCount(1); // display everything in one row - } else { - layoutManager.setSpanCount(getResources().getInteger(R.integer.grid_card_columns)); + private LoaderManager.LoaderCallbacks> loaderCallbacks = new LoaderManager.LoaderCallbacks>() { + + @Override + public Loader> onCreateLoader(int loaderID, Bundle bundle) { + if (loaderID == LOADER_ID_EVENTS) { + PeopleEventsProvider peopleEventsProvider = PeopleEventsProvider.newInstance(getActivity()); + ContactsObserver contactsObserver = new ContactsObserver(getContentResolver(), new Handler()); + return new DateDetailsLoader(getActivity(), date, peopleEventsProvider, contactsObserver); + } + return null; } - spacingDecoration.setNumberOfColumns(layoutManager.getSpanCount()); - progress.setVisibility(View.GONE); - } + @Override + public void onLoadFinished(Loader> EventItemLoader, List result) { + adapter.setEvents(result); + if (adapter.isLoadingDetailedCards()) { + layoutManager.setSpanCount(1); // display everything in one row + } else { + layoutManager.setSpanCount(getResources().getInteger(R.integer.grid_card_columns)); + } - @Override - public void onLoaderReset(Loader> EventItemLoader) { - adapter.setEvents(null); - } + spacingDecoration.setNumberOfColumns(layoutManager.getSpanCount()); + progress.setVisibility(View.GONE); + } + + @Override + public void onLoaderReset(Loader> EventItemLoader) { + adapter.setEvents(null); + } + }; private final OnSupportCardClickListener supportListener = new OnSupportCardClickListener() { diff --git a/mobile/src/main/java/com/alexstyl/specialdates/datedetails/DateDetailsLoader.java b/mobile/src/main/java/com/alexstyl/specialdates/datedetails/DateDetailsLoader.java index c9531369..61ca339e 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/datedetails/DateDetailsLoader.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/datedetails/DateDetailsLoader.java @@ -1,48 +1,31 @@ package com.alexstyl.specialdates.datedetails; -import android.content.ContentResolver; import android.content.Context; -import android.database.Cursor; -import android.os.Handler; -import android.support.annotation.Nullable; -import com.alexstyl.specialdates.contact.Contact; -import com.alexstyl.specialdates.contact.ContactNotFoundException; -import com.alexstyl.specialdates.contact.ContactProvider; import com.alexstyl.specialdates.date.ContactEvent; import com.alexstyl.specialdates.date.Date; -import com.alexstyl.specialdates.events.peopleevents.EventType; -import com.alexstyl.specialdates.events.database.PeopleEventsContract.PeopleEvents; +import com.alexstyl.specialdates.service.PeopleEventsProvider; import com.alexstyl.specialdates.ui.loader.SimpleAsyncTaskLoader; import com.alexstyl.specialdates.util.ContactsObserver; -import com.novoda.notils.logger.simple.Log; import java.text.Collator; -import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; -public class DateDetailsLoader extends SimpleAsyncTaskLoader> { +class DateDetailsLoader extends SimpleAsyncTaskLoader> { private final Date date; - private final ContactProvider contactProvider; private final ContactsObserver contactsObserver; private final Comparator displayNameComparator = new SpecialDateDisplayNameComparator(); + private final PeopleEventsProvider peopleEventsProvider; - public static DateDetailsLoader newInstance(Context context, Date date) { - context = context.getApplicationContext(); - ContactProvider contactProvider = ContactProvider.get(context); - ContactsObserver contactsObserver = new ContactsObserver(context.getContentResolver(), new Handler()); - return new DateDetailsLoader(context, date, contactProvider, contactsObserver); - } - - DateDetailsLoader(Context context, Date date, ContactProvider contactProvider, ContactsObserver contactsObserver) { + DateDetailsLoader(Context context, Date date, PeopleEventsProvider peopleEventsProvider, ContactsObserver contactsObserver) { super(context); this.date = date; - this.contactProvider = contactProvider; this.contactsObserver = contactsObserver; + this.peopleEventsProvider = peopleEventsProvider; contactsObserver.registerWith( new ContactsObserver.Callback() { @@ -62,42 +45,9 @@ protected void onUnregisterObserver() { @Override public List loadInBackground() { - Cursor cursor = queryEventsOfDate(); - // TODO handle events for multiple people - List result = new ArrayList<>(); - while (cursor.moveToNext()) { - try { - Contact contact = buildContactFrom(cursor); - EventType eventType = PeopleEvents.getEventType(cursor); - ContactEvent contactEvent = new ContactEvent(eventType, date, contact); - - result.add(contactEvent); - } catch (ContactNotFoundException e) { - Log.w(e); - } - } - cursor.close(); - Collections.sort(result, displayNameComparator); - return result; - } - - private Cursor queryEventsOfDate() { - return getContentProvider().query( - PeopleEvents.CONTENT_URI, - null, - PeopleEvents.DATE + " = ?", new String[]{date.toShortDate()}, - PeopleEvents.SORT_ORDER_DEFAULT - ); - } - - @Nullable - private Contact buildContactFrom(Cursor cursor) throws ContactNotFoundException { - long contactId = PeopleEvents.getContactIdFrom(cursor); - return contactProvider.getOrCreateContact(contactId); - } - - private ContentResolver getContentProvider() { - return getContext().getContentResolver(); + List celebrationDates = peopleEventsProvider.getCelebrationDateOn(date); + Collections.sort(celebrationDates, displayNameComparator); + return celebrationDates; } private class SpecialDateDisplayNameComparator implements Comparator { diff --git a/mobile/src/main/java/com/alexstyl/specialdates/datedetails/PeopleEventsQuery.java b/mobile/src/main/java/com/alexstyl/specialdates/datedetails/PeopleEventsQuery.java index 4c2f89ec..0d7210de 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/datedetails/PeopleEventsQuery.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/datedetails/PeopleEventsQuery.java @@ -3,13 +3,8 @@ import com.alexstyl.specialdates.events.database.PeopleEventsContract; public class PeopleEventsQuery { + private static final String BIRTHDAYS_ONLY_AND = PeopleEventsContract.PeopleEvents.EVENT_TYPE + " = " + PeopleEventsContract.PeopleEvents.TYPE_BIRTHDAY + " AND "; - public class Date { - - private static final String BIRTHDAYS_ONLY_AND = PeopleEventsContract.PeopleEvents.EVENT_TYPE + " = " + PeopleEventsContract.PeopleEvents.TYPE_BIRTHDAY + " AND "; - - public static final String SELECT = PeopleEventsContract.PeopleEvents.DATE + " = ?"; - public static final String SELECT_ONLY_BIRTHDAYS = BIRTHDAYS_ONLY_AND + SELECT; - - } + public static final String SELECT = PeopleEventsContract.PeopleEvents.DATE + " = ?"; + public static final String SELECT_ONLY_BIRTHDAYS = BIRTHDAYS_ONLY_AND + SELECT; } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/bankholidays/BankHoliday.java b/mobile/src/main/java/com/alexstyl/specialdates/events/bankholidays/BankHoliday.java index fa0a2b22..3eecb72c 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/bankholidays/BankHoliday.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/bankholidays/BankHoliday.java @@ -30,6 +30,31 @@ public String toString() { @Override public int compareTo(@NonNull BankHoliday another) { - return DateComparator.get().compare(date, another.date); + return DateComparator.INSTANCE.compare(date, another.date); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + BankHoliday that = (BankHoliday) o; + + if (!holidayName.equals(that.holidayName)) { + return false; + } + return date.equals(that.date); + + } + + @Override + public int hashCode() { + int result = holidayName.hashCode(); + result = 31 * result + date.hashCode(); + return result; } } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/bankholidays/BankHolidayProvider.java b/mobile/src/main/java/com/alexstyl/specialdates/events/bankholidays/BankHolidayProvider.java new file mode 100644 index 00000000..31e83b88 --- /dev/null +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/bankholidays/BankHolidayProvider.java @@ -0,0 +1,79 @@ +package com.alexstyl.specialdates.events.bankholidays; + +import com.alexstyl.specialdates.Optional; +import com.alexstyl.specialdates.date.Date; +import com.alexstyl.specialdates.upcoming.TimePeriod; + +import java.util.ArrayList; +import java.util.List; + +public final class BankHolidayProvider { + + private final GreekBankHolidaysCalculator bankHolidaysCalculator; + + public BankHolidayProvider(GreekBankHolidaysCalculator bankHolidaysCalculator) { + this.bankHolidaysCalculator = bankHolidaysCalculator; + } + + public List calculateBankHolidaysBetween(TimePeriod timePeriod) { + if (isWithinTheSameYear(timePeriod)) { + return calculateBankHolidaysFor(timePeriod); + } else { + ArrayList allBankholidays = new ArrayList<>(); + + TimePeriod firstHalf = getfirstHalfOf(timePeriod); + List firstHalfBankHolidays = calculateBankHolidaysFor(firstHalf); + allBankholidays.addAll(firstHalfBankHolidays); + + TimePeriod secondHalf = getSecondHalfOf(timePeriod); + List secondHalfBankHolidays = calculateBankHolidaysFor(secondHalf); + allBankholidays.addAll(secondHalfBankHolidays); + + return allBankholidays; + } + } + + private static TimePeriod getfirstHalfOf(TimePeriod timePeriod) { + Date startingDate = timePeriod.getStartingDate(); + return TimePeriod.between( + startingDate, + Date.endOfYear(startingDate.getYear()) + ); + } + + private static TimePeriod getSecondHalfOf(TimePeriod timePeriod) { + Date endingDate = timePeriod.getEndingDate(); + return TimePeriod.between( + Date.startOfTheYear(endingDate.getYear()), + endingDate + ); + } + + private List calculateBankHolidaysFor(TimePeriod timePeriod) { + List bankHolidays = new ArrayList<>(); + int year = timePeriod.getStartingDate().getYear(); + List allBankHolidays = bankHolidaysCalculator.calculateBankholidaysForYear(year); + for (BankHoliday bankHoliday : allBankHolidays) { + if (timePeriod.containsDate(bankHoliday.getDate())) { + bankHolidays.add(bankHoliday); + } + } + return bankHolidays; + } + + private boolean isWithinTheSameYear(TimePeriod timePeriod) { + return timePeriod.getStartingDate().getYear() == timePeriod.getEndingDate().getYear(); + } + + public Optional calculateBankHolidayOn(Date date) { + List bankHolidaysList; + bankHolidaysList = bankHolidaysCalculator.calculateBankholidaysForYear(date.getYear()); + for (BankHoliday bankHoliday : bankHolidaysList) { + if (bankHoliday.getDate().equals(date)) { + return new Optional<>(bankHoliday); + } + } + return Optional.absent(); + } + +} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/bankholidays/BankHolidayRepository.java b/mobile/src/main/java/com/alexstyl/specialdates/events/bankholidays/BankHolidayRepository.java deleted file mode 100644 index 0b10219c..00000000 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/bankholidays/BankHolidayRepository.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.alexstyl.specialdates.events.bankholidays; - -import com.alexstyl.specialdates.Optional; -import com.alexstyl.specialdates.date.Date; -import com.alexstyl.specialdates.events.namedays.calendar.EasterCalculator; - -import java.util.List; - -public class BankHolidayRepository { - - private final EasterCalculator calculator; - - private Date easter; - private GreekBankHolidays bankHolidays; - - public BankHolidayRepository(EasterCalculator calculator) { - this.calculator = calculator; - } - - public void preloadHolidaysForYear(int year) { - calculateHolidaysForYear(year); - } - - public Optional calculateBankholidayFor(Date date) { - List bankHolidaysList; - int year = date.getYear(); - if (isForNewYear(year)) { - calculateHolidaysForYear(year); - } - bankHolidaysList = bankHolidays.getBankHolidaysForYear(); - - for (BankHoliday bankHoliday : bankHolidaysList) { - if (bankHoliday.getDate().equals(date)) { - return new Optional<>(bankHoliday); - } - } - return Optional.absent(); - } - - private boolean isForNewYear(int year) { - return easter == null || easter.getYear() != year; - } - - private void calculateHolidaysForYear(int year) { - easter = calculator.calculateEasterForYear(year); - bankHolidays = new GreekBankHolidays(easter); - } - -} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/bankholidays/BankholidayCalendar.java b/mobile/src/main/java/com/alexstyl/specialdates/events/bankholidays/BankholidayCalendar.java deleted file mode 100644 index 9392f5ad..00000000 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/bankholidays/BankholidayCalendar.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.alexstyl.specialdates.events.bankholidays; - -import com.alexstyl.specialdates.Optional; -import com.alexstyl.specialdates.date.Date; -import com.alexstyl.specialdates.events.namedays.calendar.EasterCalculator; - -public class BankholidayCalendar { - - private static final Object LOCK = new Object(); - - private static BankholidayCalendar INSTANCE; - private final BankHolidayRepository repository; - - private BankholidayCalendar(BankHolidayRepository repository) { - this.repository = repository; - } - - public static BankholidayCalendar get() { - if (INSTANCE == null) { - INSTANCE = new BankholidayCalendar(new BankHolidayRepository(new EasterCalculator())); - INSTANCE.initialise(); - } - return INSTANCE; - } - - private void initialise() { - Thread thread = new Thread(new Runnable() { - @Override - public void run() { - synchronized (LOCK) { - int year = Date.today().getYear(); - repository.preloadHolidaysForYear(year); - } - } - }); - thread.setPriority(Thread.NORM_PRIORITY); - thread.start(); - } - - public Optional getBankholidayFor(Date date) { - synchronized (LOCK) { - return repository.calculateBankholidayFor(date); - } - } -} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/bankholidays/GreekBankHolidays.java b/mobile/src/main/java/com/alexstyl/specialdates/events/bankholidays/GreekBankHolidaysCalculator.java similarity index 64% rename from mobile/src/main/java/com/alexstyl/specialdates/events/bankholidays/GreekBankHolidays.java rename to mobile/src/main/java/com/alexstyl/specialdates/events/bankholidays/GreekBankHolidaysCalculator.java index 3492ca64..cba37c2b 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/bankholidays/GreekBankHolidays.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/bankholidays/GreekBankHolidaysCalculator.java @@ -1,52 +1,44 @@ package com.alexstyl.specialdates.events.bankholidays; import com.alexstyl.specialdates.date.Date; +import com.alexstyl.specialdates.events.namedays.calendar.OrthodoxEasterCalculator; import java.util.ArrayList; +import java.util.Collections; import java.util.List; -import static com.alexstyl.specialdates.date.DateConstants.AUGUST; -import static com.alexstyl.specialdates.date.DateConstants.MARCH; -import static com.alexstyl.specialdates.date.DateConstants.OCTOBER; +import static com.alexstyl.specialdates.date.DateConstants.*; -public class GreekBankHolidays { +public class GreekBankHolidaysCalculator { + private final OrthodoxEasterCalculator easterCalculator; - private final Date easter; - private List bankHolidays; - - public GreekBankHolidays(Date easter) { - this.easter = easter; + public GreekBankHolidaysCalculator(OrthodoxEasterCalculator easterCalculator) { + this.easterCalculator = easterCalculator; } - public List getBankHolidaysForYear() { - if (bankHolidays == null) { - initialiseBankholidays(); - } - return bankHolidays; - } + public List calculateBankholidaysForYear(int year) { + Date easter = easterCalculator.calculateEasterForYear(year); - private void initialiseBankholidays() { - int currentYear = easter.getYear(); - bankHolidays = new ArrayList<>(); - bankHolidays.add(new BankHoliday("Πρωτοχρονιά", Date.on(1, 1, currentYear))); - bankHolidays.add(new BankHoliday("Χριστούγεννα", Date.on(25, 12, currentYear))); - bankHolidays.add(new BankHoliday("Θεοφάνεια", Date.on(6, 1, currentYear))); + List bankHolidays = new ArrayList<>(); + bankHolidays.add(new BankHoliday("Πρωτοχρονιά", Date.on(1, JANUARY, year))); + bankHolidays.add(new BankHoliday("Χριστούγεννα", Date.on(25, DECEMBER, year))); + bankHolidays.add(new BankHoliday("Θεοφάνεια", Date.on(6, JANUARY, year))); bankHolidays.add(new BankHoliday("Μεγάλη Παρασκευή", easter.minusDay(2))); bankHolidays.add(new BankHoliday("Πάσχα", easter)); bankHolidays.add(new BankHoliday("2α Διακαινησίμου ", easter.addDay(1))); - bankHolidays.add(new BankHoliday("25η Μαρτίου (επανάσταση του 1821)", Date.on(25, MARCH, currentYear))); - bankHolidays.add(new BankHoliday("Κοίμηση της Θεοτόκου (Δεκαπενταύγουστος)", Date.on(15, AUGUST, currentYear))); - bankHolidays.add(new BankHoliday("26η Οκτωβρίου (εορτή Αγ.Δημητρίου - πολιούχου Θεσσαλονίκης)", Date.on(26, OCTOBER, currentYear))); - bankHolidays.add(new BankHoliday("28η Οκτωβρίου (επέτειος του ΟΧΙ)", Date.on(28, OCTOBER, currentYear))); + bankHolidays.add(new BankHoliday("25η Μαρτίου (επανάσταση του 1821)", Date.on(25, MARCH, year))); + bankHolidays.add(new BankHoliday("Κοίμηση της Θεοτόκου (Δεκαπενταύγουστος)", Date.on(15, AUGUST, year))); + bankHolidays.add(new BankHoliday("26η Οκτωβρίου (εορτή Αγ.Δημητρίου - πολιούχου Θεσσαλονίκης)", Date.on(26, OCTOBER, year))); + bankHolidays.add(new BankHoliday("28η Οκτωβρίου (επέτειος του ΟΧΙ)", Date.on(28, OCTOBER, year))); bankHolidays.add(new BankHoliday("Τσικνοπέμπτη", easter.minusDay(59))); bankHolidays.add(new BankHoliday("Καθαρά Δευτέρα", easter.minusDay(48))); bankHolidays.add(new BankHoliday("Κυριακή των Βαίων", easter.minusDay(7))); bankHolidays.add(new BankHoliday("Σάββατο του Λαζάρου", easter.minusDay(8))); - bankHolidays.add(new BankHoliday("Μεγάλη Δευτέρα", easter.minusDay(6))); bankHolidays.add(new BankHoliday("Του Θωμά", easter.addDay(7))); bankHolidays.add(new BankHoliday("Πεντηκοστή", easter.addDay(59))); bankHolidays.add(new BankHoliday("Αγ. Πνεύματος", easter.addDay(50))); bankHolidays.add(new BankHoliday("Αγίων Πάντων", easter.addDay(56))); + return Collections.unmodifiableList(bankHolidays); } } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/database/ContactColumns.java b/mobile/src/main/java/com/alexstyl/specialdates/events/database/ContactColumns.java index 90a61426..fee62576 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/database/ContactColumns.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/database/ContactColumns.java @@ -1,9 +1,6 @@ package com.alexstyl.specialdates.events.database; -interface ContactColumns { +public interface ContactColumns { String CONTACT_ID = "contact_id"; String DISPLAY_NAME = "display_name"; - String SOURCE = "contact_source"; - - int SOURCE_DEVICE = 0; } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/database/EventsDBContract.java b/mobile/src/main/java/com/alexstyl/specialdates/events/database/DatabaseContract.java similarity index 55% rename from mobile/src/main/java/com/alexstyl/specialdates/events/database/EventsDBContract.java rename to mobile/src/main/java/com/alexstyl/specialdates/events/database/DatabaseContract.java index fe553252..993448b4 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/database/EventsDBContract.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/database/DatabaseContract.java @@ -2,15 +2,10 @@ import android.provider.BaseColumns; -public final class EventsDBContract { +public final class DatabaseContract { public static final class AnnualEventsContract implements BaseColumns, ContactColumns, EventColumns { - public static final String TABLE_NAME = "annual_events"; } - public static final class DynamicEventsContract implements BaseColumns, ContactColumns, EventColumns { - public static final String TABLE_NAME = "dynamic_events"; - } - } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/database/EventColumns.java b/mobile/src/main/java/com/alexstyl/specialdates/events/database/EventColumns.java index e6a0c7a8..aeab8d74 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/database/EventColumns.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/database/EventColumns.java @@ -1,9 +1,21 @@ package com.alexstyl.specialdates.events.database; -interface EventColumns { +public interface EventColumns { String EVENT_TYPE = "event_type"; - int TYPE_NAMEDAY = 1; + /** + * The id of the same event stored in the device's default database + *

Will be set to -1 if not available

+ */ + String DEVICE_EVENT_ID = "device_event_id"; + int TYPE_BIRTHDAY = 0; + int TYPE_NAMEDAY = 1; + int TYPE_ANNIVERSARY = 2; + int TYPE_OTHER = 3; + int TYPE_CUSTOM = 4; String DATE = "date"; + String SOURCE = "source"; + int SOURCE_MEMENTO = 0; + int SOURCE_DEVICE = 1; } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/database/EventSQLiteOpenHelper.java b/mobile/src/main/java/com/alexstyl/specialdates/events/database/EventSQLiteOpenHelper.java index 1d0556db..e1563bdb 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/database/EventSQLiteOpenHelper.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/database/EventSQLiteOpenHelper.java @@ -4,13 +4,12 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; -import com.alexstyl.specialdates.events.database.EventsDBContract.AnnualEventsContract; -import com.alexstyl.specialdates.events.database.EventsDBContract.DynamicEventsContract; +import com.alexstyl.specialdates.events.database.DatabaseContract.AnnualEventsContract; public class EventSQLiteOpenHelper extends SQLiteOpenHelper { private static final String DATABASE_NAME = "events.db"; - private static final int INITIAL_CODE = 1; + private static final int INITIAL_CODE = 2; private static final int DATABASE_VERSION = INITIAL_CODE; public EventSQLiteOpenHelper(Context context) { @@ -32,30 +31,24 @@ public void onOpen(SQLiteDatabase db) { "CREATE TABLE " + AnnualEventsContract.TABLE_NAME + " (" + AnnualEventsContract._ID + INT_TYPE + NOT_NULL + COMMA_SEP + AnnualEventsContract.DISPLAY_NAME + TEXT_TYPE + NOT_NULL + COMMA_SEP + + AnnualEventsContract.DEVICE_EVENT_ID + INT_TYPE + NOT_NULL + COMMA_SEP + AnnualEventsContract.CONTACT_ID + INT_TYPE + NOT_NULL + COMMA_SEP + AnnualEventsContract.DATE + TEXT_TYPE + NOT_NULL + COMMA_SEP - + AnnualEventsContract.SOURCE + INT_TYPE + NOT_NULL + COMMA_SEP + AnnualEventsContract.EVENT_TYPE + INT_TYPE + NOT_NULL + COMMA_SEP + + AnnualEventsContract.SOURCE + INT_TYPE + NOT_NULL + COMMA_SEP + " PRIMARY KEY (" + AnnualEventsContract._ID + ")" + ")"; - private static final String SQL_CREATE_DYNAMIC_EVENTS = - "CREATE TABLE " + DynamicEventsContract.TABLE_NAME + " (" - + DynamicEventsContract._ID + INT_TYPE + NOT_NULL + COMMA_SEP - + DynamicEventsContract.CONTACT_ID + INT_TYPE + NOT_NULL + COMMA_SEP - + DynamicEventsContract.DISPLAY_NAME + TEXT_TYPE + NOT_NULL + COMMA_SEP - + DynamicEventsContract.DATE + TEXT_TYPE + NOT_NULL + COMMA_SEP - + DynamicEventsContract.SOURCE + INT_TYPE + NOT_NULL + COMMA_SEP - + DynamicEventsContract.EVENT_TYPE + INT_TYPE + NOT_NULL + COMMA_SEP - + " PRIMARY KEY (" + DynamicEventsContract._ID + ")" - + ")"; + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + // drop everything. we're quite fast at rebuilding the entire db anyway + db.execSQL("DROP TABLE IF EXISTS dynamic_events;"); + db.execSQL("DROP TABLE IF EXISTS annual_events;"); + onCreate(db); + } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(SQL_CREATE_ANNUAL_EVENTS); - db.execSQL(SQL_CREATE_DYNAMIC_EVENTS); - } - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/database/EventTypeId.java b/mobile/src/main/java/com/alexstyl/specialdates/events/database/EventTypeId.java index 98d209f9..3551fc87 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/database/EventTypeId.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/database/EventTypeId.java @@ -6,6 +6,6 @@ import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.CLASS) -@IntDef({EventColumns.TYPE_BIRTHDAY, EventColumns.TYPE_NAMEDAY}) +@IntDef({EventColumns.TYPE_BIRTHDAY, EventColumns.TYPE_NAMEDAY, EventColumns.TYPE_ANNIVERSARY, EventColumns.TYPE_OTHER, EventColumns.TYPE_CUSTOM}) public @interface EventTypeId { } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/database/PeopleEventsContract.java b/mobile/src/main/java/com/alexstyl/specialdates/events/database/PeopleEventsContract.java index dd8c436c..3dcd0031 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/database/PeopleEventsContract.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/database/PeopleEventsContract.java @@ -1,13 +1,9 @@ package com.alexstyl.specialdates.events.database; import android.content.ContentResolver; -import android.database.Cursor; import android.net.Uri; import android.provider.BaseColumns; -import com.alexstyl.specialdates.date.Date; -import com.alexstyl.specialdates.events.peopleevents.EventType; - public class PeopleEventsContract { public static final String AUTHORITY = "com.alexstyl.specialdates.peopleeventsprovider"; @@ -22,38 +18,6 @@ private PeopleEvents() { public static final String PATH = "people_events"; public static final String CONTENT_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE + "/vnd." + AUTHORITY + "." + PATH; public static final Uri CONTENT_URI = Uri.withAppendedPath(PeopleEventsContract.CONTENT_URI, PATH); - public static final String SORT_ORDER_DEFAULT = DATE + " ASC"; - - public static Date getDateFrom(Cursor cursor) { - int index = cursor.getColumnIndexOrThrow(PeopleEventsContract.PeopleEvents.DATE); - String text = cursor.getString(index); - return from(text); - } - - public static long getContactIdFrom(Cursor cursor) { - int contactIdIndex = cursor.getColumnIndexOrThrow(PeopleEvents.CONTACT_ID); - return cursor.getLong(contactIdIndex); - } - - public static EventType getEventType(Cursor cursor) { - int eventTypeIndex = cursor.getColumnIndexOrThrow(PeopleEvents.EVENT_TYPE); - @EventTypeId int rawEventType = cursor.getInt(eventTypeIndex); - return EventType.fromId(rawEventType); - } - - private static final String SEPARATOR = "-"; - - public static Date from(String text) { - int dayToMonth = text.lastIndexOf(SEPARATOR); - int monthToYear = text.lastIndexOf(SEPARATOR, dayToMonth - 1); - - int day = Integer.valueOf(text.substring(dayToMonth + 1, text.length())); - int month = Integer.valueOf(text.substring(monthToYear + 1, dayToMonth)); - - int yearToMonth = text.indexOf(SEPARATOR); - int year = Integer.valueOf(text.substring(0, yearToMonth)); - return Date.on(day, month, year); - } } } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/database/SourceType.java b/mobile/src/main/java/com/alexstyl/specialdates/events/database/SourceType.java new file mode 100644 index 00000000..089aefb3 --- /dev/null +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/database/SourceType.java @@ -0,0 +1,14 @@ +package com.alexstyl.specialdates.events.database; + +import android.support.annotation.IntDef; + +import com.alexstyl.specialdates.events.database.DatabaseContract.AnnualEventsContract; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.CLASS) +@IntDef({AnnualEventsContract.SOURCE_DEVICE, AnnualEventsContract.SOURCE_MEMENTO}) +public @interface SourceType { +} + diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/NamedayBundle.java b/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/NamedayBundle.java index ded9f828..cafdb238 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/NamedayBundle.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/NamedayBundle.java @@ -2,6 +2,7 @@ import com.alexstyl.specialdates.date.Date; import com.alexstyl.specialdates.events.namedays.calendar.resource.Node; +import com.alexstyl.specialdates.upcoming.TimePeriod; import java.util.ArrayList; @@ -30,4 +31,8 @@ public NamesInADate getNamedaysFor(Date date) { public ArrayList getNames() { return namedaysList.getNames(); } + + public NamedayBundle getNamedaysFor(TimePeriod period) { + return null; + } } 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 d0338d42..f01042fc 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 @@ -1,198 +1,42 @@ package com.alexstyl.specialdates.events.namedays; -import android.annotation.SuppressLint; -import android.content.ContentResolver; import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.provider.ContactsContract; -import com.alexstyl.specialdates.DisplayName; -import com.alexstyl.specialdates.Names; -import com.alexstyl.specialdates.Optional; -import com.alexstyl.specialdates.contact.Contact; -import com.alexstyl.specialdates.contact.ContactNotFoundException; -import com.alexstyl.specialdates.contact.ContactProvider; import com.alexstyl.specialdates.date.ContactEvent; -import com.alexstyl.specialdates.date.Date; -import com.alexstyl.specialdates.events.database.EventSQLiteOpenHelper; -import com.alexstyl.specialdates.events.namedays.calendar.NamedayCalendar; -import com.alexstyl.specialdates.events.namedays.calendar.resource.NamedayCalendarProvider; import com.alexstyl.specialdates.events.peopleevents.ContactEventsMarshaller; -import com.alexstyl.specialdates.events.peopleevents.EventType; import com.alexstyl.specialdates.events.peopleevents.PeopleEventsPersister; +import com.alexstyl.specialdates.events.peopleevents.PeopleNamedaysCalculator; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; import java.util.List; -import java.util.Set; public class NamedayDatabaseRefresher { - private static final Optional NO_CONTACT = Optional.absent(); - @SuppressWarnings("unchecked") - private static final List NO_EVENTS = Collections.EMPTY_LIST; - - private final ContentResolver contentResolver; - private final ContactProvider contactProvider; - private final NamedayCalendarProvider namedayCalendarProvider; private final NamedayPreferences namedayPreferences; private final PeopleEventsPersister perister; private final ContactEventsMarshaller eventMarshaller; + private final PeopleNamedaysCalculator calculator; - public static NamedayDatabaseRefresher newInstance(Context context) { - ContentResolver contentResolver = context.getContentResolver(); - NamedayCalendarProvider namedayProvider = NamedayCalendarProvider.newInstance(context.getResources()); - NamedayPreferences namedayPreferences = NamedayPreferences.newInstance(context); - ContactProvider contactProvider = ContactProvider.get(context); - ContactEventsMarshaller marshaller = new ContactEventsMarshaller(); - PeopleEventsPersister persister = new PeopleEventsPersister(new EventSQLiteOpenHelper(context)); - return new NamedayDatabaseRefresher(contentResolver, namedayProvider, namedayPreferences, persister, contactProvider, marshaller); - } - - NamedayDatabaseRefresher(ContentResolver contentResolver, - NamedayCalendarProvider namedayCalendarProvider, - NamedayPreferences namedayPreferences, - PeopleEventsPersister databaseProvider, ContactProvider contactProvider, - ContactEventsMarshaller eventMarshaller) { - this.contentResolver = contentResolver; - this.namedayCalendarProvider = namedayCalendarProvider; + public NamedayDatabaseRefresher(NamedayPreferences namedayPreferences, + PeopleEventsPersister databaseProvider, + ContactEventsMarshaller eventMarshaller, + PeopleNamedaysCalculator calculator + ) { this.namedayPreferences = namedayPreferences; this.perister = databaseProvider; - this.contactProvider = contactProvider; this.eventMarshaller = eventMarshaller; + this.calculator = calculator; } public void refreshNamedaysIfEnabled() { perister.deleteAllNamedays(); - if (namedaysEnabled()) { + if (namedayPreferences.isEnabled()) { initialiseNamedays(); } } private void initialiseNamedays() { - List namedays = loadDeviceStaticNamedays(); + List namedays = calculator.loadDeviceStaticNamedays(); storeNamedaysToDisk(namedays); - - List specialNamedays = loadSpecialNamedays(); - storeSpecialNamedaysToDisk(specialNamedays); - } - - private List loadDeviceStaticNamedays() { - Cursor cursor = DeviceContactsQuery.query(contentResolver); - if (isInvalidCursor(cursor)) { - return NO_EVENTS; - } - - List namedayEvents = new ArrayList<>(); - Set contactIDs = new HashSet<>(); - - while (cursor.moveToNext()) { - long id = DeviceContactsQuery.getID(cursor); - - if (contactIDs.contains(id)) { - continue; - } - contactIDs.add(id); - Optional contact = getDeviceContactWithId(id); - if (!contact.isPresent()) { - continue; - } - - DisplayName displayName = DisplayName.from(getDisplayName(cursor)); - HashSet namedays = new HashSet<>(); - - Names firstNames = getNamesToCheckFrom(displayName); - for (String firstName : firstNames) { - NameCelebrations nameDays = getNamedaysOf(firstName); - if (nameDays.containsNoDate()) { - continue; - } - int namedaysCount = nameDays.size(); - for (int i = 0; i < namedaysCount; i++) { - Date date = nameDays.getDate(i); - if (namedays.contains(date)) { - continue; - } - ContactEvent event = new ContactEvent(EventType.NAMEDAY, date, contact.get()); - namedayEvents.add(event); - namedays.add(date); - } - } - } - - cursor.close(); - return namedayEvents; - } - - private Names getNamesToCheckFrom(DisplayName displayName) { - if (namedayPreferences.shouldLookupAllNames()) { - return displayName.getAllNames(); - } else { - return displayName.getFirstNames(); - } - } - - private List loadSpecialNamedays() { - Cursor cursor = DeviceContactsQuery.query(contentResolver); - if (isInvalidCursor(cursor)) { - return NO_EVENTS; - } - - List namedayEvents = new ArrayList<>(); - Set contactIDs = new HashSet<>(); - - while (cursor.moveToNext()) { - long id = DeviceContactsQuery.getID(cursor); - - if (contactIDs.contains(id)) { - continue; - } - contactIDs.add(id); - - Optional contact = getDeviceContactWithId(id); - if (!contact.isPresent()) { - continue; - } - - DisplayName displayName = DisplayName.from(getDisplayName(cursor)); - - for (String firstName : getNamesToCheckFrom(displayName)) { - NameCelebrations nameDays = getSpecialNamedaysOf(firstName); - if (nameDays.containsNoDate()) { - continue; - } - - int namedaysCount = nameDays.size(); - for (int i = 0; i < namedaysCount; i++) { - Date date = nameDays.getDate(i); - ContactEvent nameday = new ContactEvent(EventType.NAMEDAY, date, contact.get()); - namedayEvents.add(nameday); - } - } - } - - cursor.close(); - return namedayEvents; - } - - private Optional getDeviceContactWithId(long id) { - try { - Contact contact = contactProvider.getOrCreateContact(id); - return new Optional<>(contact); - } catch (ContactNotFoundException e) { - return NO_CONTACT; - } - } - - private boolean isInvalidCursor(Cursor cursor) { - return cursor == null; - } - - private String getDisplayName(Cursor cursor) { - return cursor.getString(DeviceContactsQuery.DISPLAY_NAME); } private void storeNamedaysToDisk(List namedays) { @@ -200,64 +44,4 @@ private void storeNamedaysToDisk(List namedays) { perister.insertAnnualEvents(values); } - private void storeSpecialNamedaysToDisk(List specialNamedays) { - ContentValues[] values = eventMarshaller.marshall(specialNamedays); - perister.insertDynamicEvents(values); - } - - private NameCelebrations getNamedaysOf(String given) { - NamedayCalendar namedayCalendar = getNamedayCalendar(); - return namedayCalendar.getNormalNamedaysFor(given); - } - - private NameCelebrations getSpecialNamedaysOf(String firstName) { - NamedayCalendar namedayCalendar = getNamedayCalendar(); - return namedayCalendar.getSpecialNamedaysFor(firstName); - } - - public NamedayCalendar getNamedayCalendar() { - NamedayLocale locale = namedayPreferences.getSelectedLanguage(); - int todayYear = Date.today().getYear(); - return namedayCalendarProvider.loadNamedayCalendarForLocale(locale, todayYear); - } - - private boolean namedaysEnabled() { - return namedayPreferences.isEnabled(); - } - - private static class DeviceContactsQuery { - - public final static Uri CONTENT_URI = ContactsContract.Data.CONTENT_URI; - - public static final String WHERE = - ContactsContract.Data.MIMETYPE + " = ? AND " + ContactsContract.Data.DISPLAY_NAME + " NOT NULL" - + " AND " + ContactsContract.Data.IN_VISIBLE_GROUP + "=1"; - - public static final String[] WHERE_ARGS = { - ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE // given names - }; - - @SuppressLint("InlinedApi") - public final static String SORT_ORDER = ContactsContract.Contacts.DISPLAY_NAME_PRIMARY; - - @SuppressLint("InlinedApi") - public static final String[] PROJECTION = { - ContactsContract.Data.CONTACT_ID, - ContactsContract.Contacts.DISPLAY_NAME_PRIMARY - }; - - public static final int ID = 0; - - public static final int DISPLAY_NAME = 1; - - public static Cursor query(ContentResolver cr) { - return cr.query(CONTENT_URI, PROJECTION, WHERE, WHERE_ARGS, SORT_ORDER); - } - - public static long getID(Cursor cursor) { - return cursor.getLong(DeviceContactsQuery.ID); - } - - } - } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/NamedayLocale.java b/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/NamedayLocale.java index fd3a3e6e..c6be986d 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/NamedayLocale.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/NamedayLocale.java @@ -6,24 +6,26 @@ import com.novoda.notils.exception.DeveloperError; public enum NamedayLocale { - gr(true, R.raw.gr_namedays), - ro(false, R.raw.ro_namedays), - ru(false, R.raw.ru_namedays), - lv(false, R.raw.lv_namedays), - sk(false, R.raw.sk_namedays), - cs(false, R.raw.cs_namedays); - + GREEK("gr", true, R.raw.gr_namedays), + ROMANIAN("ro", false, R.raw.ro_namedays), + RUSSIAN("ru", false, R.raw.ru_namedays), + LATVIAN("lv", false, R.raw.lv_namedays), + SLOVAK("sk", false, R.raw.sk_namedays), + CZECH("cs", false, R.raw.cs_namedays); + + private final String shortCode; private final boolean soundCompared; private final int rawResId; - NamedayLocale(boolean soundCompared, @RawRes int rawResId) { + NamedayLocale(String shortCode, boolean soundCompared, @RawRes int rawResId) { + this.shortCode = shortCode; this.soundCompared = soundCompared; this.rawResId = rawResId; } public static NamedayLocale from(String displayLanguage) { for (NamedayLocale locale : values()) { - if (locale.name().equalsIgnoreCase(displayLanguage)) { + if (locale.getShortCode().equalsIgnoreCase(displayLanguage)) { return locale; } } @@ -37,4 +39,8 @@ public int getRawResId() { public boolean isComparedBySound() { return soundCompared; } + + public String getShortCode() { + return shortCode; + } } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/NamedayPreferences.java b/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/NamedayPreferences.java index 6f11bf91..970cdf2b 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/NamedayPreferences.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/NamedayPreferences.java @@ -7,7 +7,7 @@ public class NamedayPreferences { - private static final String DEFAULT_LOCALE = NamedayLocale.gr.name(); + private static final String DEFAULT_LOCALE = NamedayLocale.GREEK.getShortCode(); private final boolean enabledByDefault; private final EasyPreferences preferences; diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/NamesInADate.java b/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/NamesInADate.java index 23b7f429..19167d61 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/NamesInADate.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/NamesInADate.java @@ -31,6 +31,10 @@ public List getNames() { return Collections.unmodifiableList(names); } + /** + * Do not use this method. Prefer passing names from the constructor of the class + */ + @Deprecated public void addName(String name) { names.add(name); } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/EasterCalculator.java b/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/OrthodoxEasterCalculator.java similarity index 76% rename from mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/EasterCalculator.java rename to mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/OrthodoxEasterCalculator.java index c3f314d7..3f3f73e8 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/EasterCalculator.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/OrthodoxEasterCalculator.java @@ -1,8 +1,10 @@ package com.alexstyl.specialdates.events.namedays.calendar; import com.alexstyl.specialdates.date.Date; +import com.alexstyl.specialdates.date.MonthInt; -public class EasterCalculator { +public enum OrthodoxEasterCalculator { + INSTANCE; /** * Calculates the date of the easter Sunday for the given year @@ -13,7 +15,7 @@ public Date calculateEasterForYear(int year) { int c = year % 19; int d = (19 * c + 15) % 30; int e = (2 * a + 4 * b - d + 34) % 7; - int month = (int) Math.floor((d + e + 114) / 31); + @MonthInt int month = (int) Math.floor((d + e + 114) / 31); int day = ((d + e + 144) % 31) + 1; day++; return Date.on(day, month, year) diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/resource/GreekNamedays.java b/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/resource/GreekNamedays.java index dfc51fa4..4a5c8f89 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/resource/GreekNamedays.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/resource/GreekNamedays.java @@ -4,7 +4,7 @@ import com.alexstyl.specialdates.events.namedays.NameCelebrations; import com.alexstyl.specialdates.events.namedays.NamedayBundle; import com.alexstyl.specialdates.events.namedays.NamesInADate; -import com.alexstyl.specialdates.events.namedays.calendar.EasterCalculator; +import com.alexstyl.specialdates.events.namedays.calendar.OrthodoxEasterCalculator; import com.alexstyl.specialdates.events.namedays.calendar.EasternNameday; import com.alexstyl.specialdates.events.namedays.calendar.EasternNamedaysExtractor; @@ -15,7 +15,7 @@ public final class GreekNamedays { - private final EasterCalculator easterCalculator = new EasterCalculator(); + private final OrthodoxEasterCalculator easterCalculator = OrthodoxEasterCalculator.INSTANCE; private final SpecialGreekNamedaysCalculator specialGreekNamedaysCalculator; diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/resource/NamedayCalendarProvider.java b/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/resource/NamedayCalendarProvider.java index 5468b098..fe7c86f9 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/resource/NamedayCalendarProvider.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/resource/NamedayCalendarProvider.java @@ -24,7 +24,6 @@ public static NamedayCalendarProvider newInstance(Resources resources) { return new NamedayCalendarProvider(jsonResourceProvider, factory); } - // eventually we will use DI to provide this, so don't worry about the `public` public NamedayCalendarProvider(NamedayJSONResourceProvider jsonProvider, SpecialNamedaysHandlerFactory factory) { this.factory = factory; this.jsonProvider = jsonProvider; diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/resource/RomanianEasterSpecialCalculator.java b/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/resource/RomanianEasterSpecialCalculator.java index 191934bc..26926f56 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/resource/RomanianEasterSpecialCalculator.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/resource/RomanianEasterSpecialCalculator.java @@ -1,16 +1,16 @@ package com.alexstyl.specialdates.events.namedays.calendar.resource; import com.alexstyl.specialdates.date.Date; -import com.alexstyl.specialdates.events.namedays.calendar.EasterCalculator; +import com.alexstyl.specialdates.events.namedays.calendar.OrthodoxEasterCalculator; import static com.alexstyl.specialdates.date.DateConstants.SUNDAY; class RomanianEasterSpecialCalculator { - private final EasterCalculator easterCalculator; + private final OrthodoxEasterCalculator easterCalculator; - RomanianEasterSpecialCalculator() { - easterCalculator = new EasterCalculator(); + RomanianEasterSpecialCalculator(OrthodoxEasterCalculator easterCalculator) { + this.easterCalculator = easterCalculator; } Date calculateSpecialRomanianDayForYear(int year) { diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/resource/RomanianNamedays.java b/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/resource/RomanianNamedays.java index 1acf00d8..41765c76 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/resource/RomanianNamedays.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/resource/RomanianNamedays.java @@ -5,6 +5,7 @@ import com.alexstyl.specialdates.events.namedays.NamedayBundle; import com.alexstyl.specialdates.events.namedays.NamedaysList; import com.alexstyl.specialdates.events.namedays.NamesInADate; +import com.alexstyl.specialdates.events.namedays.calendar.OrthodoxEasterCalculator; import java.util.ArrayList; import java.util.List; @@ -18,7 +19,7 @@ public class RomanianNamedays { private Date romanianDate; public static RomanianNamedays from(List names) { - RomanianEasterSpecialCalculator calculator = new RomanianEasterSpecialCalculator(); + RomanianEasterSpecialCalculator calculator = new RomanianEasterSpecialCalculator(OrthodoxEasterCalculator.INSTANCE); return new RomanianNamedays(calculator, names); } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/resource/SpecialGreekNamedaysCalculator.java b/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/resource/SpecialGreekNamedaysCalculator.java index f1fb833d..f453a754 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/resource/SpecialGreekNamedaysCalculator.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/resource/SpecialGreekNamedaysCalculator.java @@ -1,9 +1,5 @@ package com.alexstyl.specialdates.events.namedays.calendar.resource; -import android.content.Context; - -import com.alexstyl.specialdates.MementoApplication; -import com.alexstyl.specialdates.R; import com.alexstyl.specialdates.date.Date; import com.alexstyl.specialdates.date.DateComparator; import com.alexstyl.specialdates.date.DateConstants; @@ -12,19 +8,52 @@ import com.alexstyl.specialdates.events.namedays.NamedaysList; import com.alexstyl.specialdates.events.namedays.calendar.EasternNameday; +import java.util.Arrays; import java.util.Calendar; import java.util.List; class SpecialGreekNamedaysCalculator { - private final DateComparator comparator = DateComparator.get(); - private final List easternNamedays; + private static final DateComparator COMPARATOR = DateComparator.INSTANCE; + private static final List PROPATORWN = Arrays.asList( + "Ααρών", + "Αβραάμ", + "Αδάμ", + "Αδαμάντιος", + "Διαμαντής", + "Αδαμαντία", + "Διαμαντούλα", + "Διαμάντω", + "Δαβίδ", + "Δαυίδ", + "Δανάη", + "Δανιήλ", + "Δεβόρα", + "Εσθήρ", + "Εύα", + "Ισαάκ", + "Ιώβ", + "Νώε", + "Ραχήλ", + "Ρεβέκκα", + "Ρουμπίνη", + "Σάρα", + "Μελχισεδέ" + ); + private static final String CLOE = "Χλόη"; + + private static final List MARKOS_ALTS = Arrays.asList( + "Μάρκος", + "Μαρκής", + "Μαρκία", + "Μαρκούλης", + "Μαρκούλ" + ); - private final Context context; + private final List easternNamedays; SpecialGreekNamedaysCalculator(List easternNamedays) { this.easternNamedays = easternNamedays; - this.context = MementoApplication.getContext(); } NamedayBundle calculateForEasterDate(Date easter) { @@ -45,14 +74,13 @@ NamedayBundle calculateForEasterDate(Date easter) { } private void appendSpecialScenarios(Date easter, Node node, NamedaysList namedaysList) { - addSpecialPropatorwn(node, namedaysList, context); - addSpecialMarkos(node, namedaysList, context, easter); - addSpecialGiwrgos(node, namedaysList, context, easter); - addSpecialChloe(node, namedaysList, context); + addSpecialPropatorwn(node, namedaysList); + addSpecialMarkos(node, namedaysList, easter); + addSpecialGiwrgos(node, namedaysList, easter); + addSpecialChloe(node, namedaysList); } - private void addSpecialPropatorwn(Node node, NamedaysList namedaysList, Context context) { - + private void addSpecialPropatorwn(Node node, NamedaysList namedaysList) { Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.MONTH, Calendar.DECEMBER); calendar.set(Calendar.DAY_OF_MONTH, 11); @@ -61,47 +89,58 @@ private void addSpecialPropatorwn(Node node, NamedaysList namedaysList, Context } Date date = createDayDateFrom(calendar); - String[] vars = context.getResources().getStringArray(R.array.special_propatorwn_alts); - for (String variation : vars) { + for (String variation : PROPATORWN) { node.addDate(variation, date); namedaysList.addNameday(date, variation); } } - private void addSpecialMarkos(Node node, NamedaysList namedaysList, Context context, Date easter) { + private void addSpecialMarkos(Node node, NamedaysList namedaysList, Date easter) { int year = Date.today().getYear(); Date date = Date.on(23, DateConstants.APRIL, year); - if (comparator.compare(easter, date) > 0) { + if (COMPARATOR.compare(easter, date) > 0) { date = date.addDay(2); } else { date = Date.on(25, DateConstants.APRIL, year); } - String[] vars = context.getResources().getStringArray(R.array.special_markos_alts); - for (String variation : vars) { + for (String variation : MARKOS_ALTS) { node.addDate(variation, date); namedaysList.addNameday(date, variation); } } - private void addSpecialGiwrgos(Node node, NamedaysList namedaysList, Context context, Date easter) { + private static final List GEORGE_ALTS = Arrays.asList( + "Γεώργιος", + "Γεωργής", + "Γιώργος", + "Γκόγκος", + "Γιώργης", + "Γιωργίτσης", + "Γεωργία", + "Γιωργία", + "Γεωργούλα", + "Γιωργίτσα", + "Γίτσα" + ); + + private void addSpecialGiwrgos(Node node, NamedaysList namedaysList, Date easter) { Date date = Date.on(23, DateConstants.APRIL, Date.today().getYear()); Date actualDate; - if (comparator.compare(easter, date) > 0) { + if (COMPARATOR.compare(easter, date) > 0) { actualDate = easter.addDay(1); } else { actualDate = date; } - String[] vars = context.getResources().getStringArray(R.array.special_george_alts); - for (String variation : vars) { + for (String variation : GEORGE_ALTS) { node.addDate(variation, actualDate); namedaysList.addNameday(actualDate, variation); } } - private void addSpecialChloe(Node node, NamedaysList namedaysList, Context context) { + private void addSpecialChloe(Node node, NamedaysList namedaysList) { Calendar cal = Calendar.getInstance(); cal.set(Calendar.MONTH, Calendar.FEBRUARY); cal.set(Calendar.DAY_OF_MONTH, 13); @@ -111,7 +150,7 @@ private void addSpecialChloe(Node node, NamedaysList namedaysList, Context conte } Date date = createDayDateFrom(cal); - String variation = context.getString(R.string.specialname_chloe); + String variation = CLOE; node.addDate(variation, date); namedaysList.addNameday(date, variation); } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/resource/SpecialNamedaysHandlerFactory.java b/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/resource/SpecialNamedaysHandlerFactory.java index c36a802b..3d7ff445 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/resource/SpecialNamedaysHandlerFactory.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/namedays/calendar/resource/SpecialNamedaysHandlerFactory.java @@ -20,11 +20,11 @@ SpecialNamedays createStrategyForLocale(NamedayLocale locale, NamedayJSON nameda } private boolean isRomanian(NamedayLocale locale) { - return locale == NamedayLocale.ro; + return locale == NamedayLocale.ROMANIAN; } private boolean isGreekLocale(NamedayLocale locale) { - return locale == NamedayLocale.gr; + return locale == NamedayLocale.GREEK; } private static final SpecialNamedays NO_SPECIAL_NAMEDAYS = new SpecialNamedays() { diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/BirthdayDatabaseRefresher.java b/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/BirthdayDatabaseRefresher.java deleted file mode 100644 index b09b9671..00000000 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/BirthdayDatabaseRefresher.java +++ /dev/null @@ -1,127 +0,0 @@ -package com.alexstyl.specialdates.events.peopleevents; - -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.provider.ContactsContract; - -import com.alexstyl.specialdates.Optional; -import com.alexstyl.specialdates.contact.Contact; -import com.alexstyl.specialdates.contact.ContactNotFoundException; -import com.alexstyl.specialdates.contact.ContactProvider; -import com.alexstyl.specialdates.date.ContactEvent; -import com.alexstyl.specialdates.events.database.EventSQLiteOpenHelper; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import static com.alexstyl.specialdates.events.peopleevents.EventType.BIRTHDAY; - -class BirthdayDatabaseRefresher { - - private static final List NO_EVENTS = Collections.emptyList(); - private static final Optional NO_CONTACT = Optional.absent(); - - private final ContactProvider contactProvider; - private final ContentResolver contentResolver; - private final PeopleEventsPersister persister; - private final Marshaller marshaller; - - static BirthdayDatabaseRefresher newInstance(Context context) { - ContactProvider contactProvider = ContactProvider.get(context); - PeopleEventsPersister persister = new PeopleEventsPersister(new EventSQLiteOpenHelper(context)); - Marshaller marshaller = new ContactEventsMarshaller(); - return new BirthdayDatabaseRefresher(contactProvider, context.getContentResolver(), persister, marshaller); - } - - BirthdayDatabaseRefresher(ContactProvider contactProvider, - ContentResolver contentResolver, - PeopleEventsPersister persister, - Marshaller marshaller) { - this.contentResolver = contentResolver; - this.persister = persister; - this.marshaller = marshaller; - this.contactProvider = contactProvider; - } - - public void refreshBirthdays() { - clearAllBirthdays(); - List contacts = loadBirthdaysFromDisk(); - storeContactsToProvider(contacts); - } - - private void clearAllBirthdays() { - persister.deleteAllBirthdays(); - } - - private List loadBirthdaysFromDisk() { - Cursor cursor = BirthdayQuery.query(contentResolver); - if (isInvalid(cursor)) { - return NO_EVENTS; - } - List events = new ArrayList<>(); - try { - while (cursor.moveToNext()) { - int contactIdIndex = cursor.getColumnIndex(ContactsContract.Data.CONTACT_ID); - long contactId = cursor.getLong(contactIdIndex); - Optional optionalContact = getDeviceContactWithId(contactId); - if (optionalContact.isPresent()) { - Contact contact = optionalContact.get(); - events.add(new ContactEvent(BIRTHDAY, contact.getDateOfBirth(), contact)); - } - } - } finally { - cursor.close(); - } - return events; - } - - private Optional getDeviceContactWithId(long id) { - try { - Contact contact = contactProvider.getOrCreateContact(id); - return new Optional<>(contact); - } catch (ContactNotFoundException e) { - return NO_CONTACT; - } - } - - private boolean isInvalid(Cursor cursor) { - return cursor == null || cursor.isClosed(); - } - - private void storeContactsToProvider(List contacts) { - ContentValues[] values = marshaller.marshall(contacts); - persister.insertAnnualEvents(values); - } - - /** - * Contract that queries birthdays only - */ - private static final class BirthdayQuery { - /** - * Queries the contacts tables for birthdays with the default settings. - */ - public static Cursor query(ContentResolver cr) { - return cr.query(CONTENT_URI, PROJECTION, WHERE, WHERE_ARGS, SORT_ORDER); - } - - private static final Uri CONTENT_URI = ContactsContract.Data.CONTENT_URI; - private static final String COL_DISPLAY_NAME = ContactsContract.Contacts.DISPLAY_NAME_PRIMARY; - - public static final String WHERE = - "(" + ContactsContract.Data.MIMETYPE + " = ? AND " + - ContactsContract.CommonDataKinds.Event.TYPE - + "=" - + ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY + ")" - + " AND " + ContactsContract.Data.IN_VISIBLE_GROUP + " = 1"; - - public static final String[] WHERE_ARGS = {ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE}; - public final static String SORT_ORDER = COL_DISPLAY_NAME; - public static final String[] PROJECTION = {ContactsContract.Data.CONTACT_ID}; - public static final int ID = 0; - - } -} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/ContactEventsMarshaller.java b/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/ContactEventsMarshaller.java index 7278eb3c..132e1f02 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/ContactEventsMarshaller.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/ContactEventsMarshaller.java @@ -2,18 +2,26 @@ import android.content.ContentValues; +import com.alexstyl.specialdates.Optional; import com.alexstyl.specialdates.contact.Contact; import com.alexstyl.specialdates.date.ContactEvent; import com.alexstyl.specialdates.date.DateDisplayStringCreator; -import com.alexstyl.specialdates.events.database.EventsDBContract.AnnualEventsContract; import com.alexstyl.specialdates.events.database.PeopleEventsContract; -import com.novoda.notils.exception.DeveloperError; +import com.alexstyl.specialdates.events.database.SourceType; import java.util.List; -public class ContactEventsMarshaller implements Marshaller { +public class ContactEventsMarshaller { + + private final DateDisplayStringCreator instance; + @SourceType + private final int source; + + ContactEventsMarshaller(@SourceType int source) { + this.source = source; + instance = DateDisplayStringCreator.INSTANCE; + } - @Override public ContentValues[] marshall(List item) { ContentValues[] returningValues = new ContentValues[item.size()]; for (int i = 0; i < item.size(); i++) { @@ -28,24 +36,24 @@ private ContentValues createValuesFor(ContactEvent event) { ContentValues values = new ContentValues(4); values.put(PeopleEventsContract.PeopleEvents.CONTACT_ID, contact.getContactID()); - values.put(PeopleEventsContract.PeopleEvents.SOURCE, ContactSource.DEVICE); values.put(PeopleEventsContract.PeopleEvents.DISPLAY_NAME, contact.getDisplayName().toString()); - - String date = DateDisplayStringCreator.getInstance().stringOf(event.getDate()); + String date = instance.stringOf(event.getDate()); values.put(PeopleEventsContract.PeopleEvents.DATE, date); + values.put(PeopleEventsContract.PeopleEvents.EVENT_TYPE, event.getType().getId()); + values.put(PeopleEventsContract.PeopleEvents.SOURCE, source); + + putDeviceContactIdIfPresent(event, values); - values.put(PeopleEventsContract.PeopleEvents.EVENT_TYPE, getTypeFor(event)); return values; } - private int getTypeFor(ContactEvent event) { - switch (event.getType()) { - case BIRTHDAY: - return AnnualEventsContract.TYPE_BIRTHDAY; - case NAMEDAY: - return AnnualEventsContract.TYPE_NAMEDAY; + private void putDeviceContactIdIfPresent(ContactEvent event, ContentValues values) { + Optional deviceEventId = event.getDeviceEventId(); + if (deviceEventId.isPresent()) { + values.put(PeopleEventsContract.PeopleEvents.DEVICE_EVENT_ID, deviceEventId.get()); + } else { + values.put(PeopleEventsContract.PeopleEvents.DEVICE_EVENT_ID, -1); } - throw new DeveloperError(event.getType() + " has no EventColumn reference"); } } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/CustomEventType.java b/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/CustomEventType.java new file mode 100644 index 00000000..ddd33037 --- /dev/null +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/CustomEventType.java @@ -0,0 +1,30 @@ +package com.alexstyl.specialdates.events.peopleevents; + +import android.content.res.Resources; + +import com.alexstyl.specialdates.R; +import com.alexstyl.specialdates.events.database.EventColumns; + +public class CustomEventType implements EventType { + + private final String name; + + public CustomEventType(String name) { + this.name = name; + } + + @Override + public String getEventName(Resources resources) { + return name; + } + + @Override + public int getColorRes() { + return R.color.purple_custom_event; + } + + @Override + public int getId() { + return EventColumns.TYPE_CUSTOM; + } +} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/DeviceContactsQuery.java b/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/DeviceContactsQuery.java new file mode 100644 index 00000000..b56cf891 --- /dev/null +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/DeviceContactsQuery.java @@ -0,0 +1,68 @@ +package com.alexstyl.specialdates.events.peopleevents; + +import android.content.ContentResolver; +import android.database.Cursor; +import android.provider.ContactsContract; + +import com.alexstyl.specialdates.DisplayName; +import com.alexstyl.specialdates.ErrorTracker; +import com.alexstyl.specialdates.contact.Contact; +import com.alexstyl.specialdates.contact.ContactsQuery; +import com.alexstyl.specialdates.contact.DeviceContact; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public final class DeviceContactsQuery { + + private static final String WHERE = ContactsContract.Data.IN_VISIBLE_GROUP + "=1"; + + private final ContentResolver resolver; + + public DeviceContactsQuery(ContentResolver resolver) { + this.resolver = resolver; + } + + public List getAllContacts() { + Cursor cursor = resolver.query( + ContactsQuery.CONTENT_URI, + ContactsQuery.PROJECTION, + WHERE, + null, + ContactsQuery.SORT_ORDER + ); + List contacts = new ArrayList<>(); + try { + while (cursor.moveToNext()) { + Contact contact = createContactFrom(cursor); + contacts.add(contact); + } + } catch (Exception e) { + ErrorTracker.track(e); + } finally { + cursor.close(); + } + return Collections.unmodifiableList(contacts); + } + + private Contact createContactFrom(Cursor cursor) { + long contactID = getContactIdFrom(cursor); + DisplayName displayName = getDisplayNameFrom(cursor); + String lookupKey = getLookupKeyFrom(cursor); + return new DeviceContact(contactID, displayName, lookupKey); + } + + private static long getContactIdFrom(Cursor cursor) { + return cursor.getLong(ContactsQuery.CONTACT_ID); + } + + private static DisplayName getDisplayNameFrom(Cursor cursor) { + return DisplayName.from(cursor.getString(ContactsQuery.DISPLAY_NAME)); + } + + private static String getLookupKeyFrom(Cursor cursor) { + return cursor.getString(ContactsQuery.LOOKUP_KEY); + } + +} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/Marshaller.java b/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/Marshaller.java deleted file mode 100644 index 032c5cd2..00000000 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/Marshaller.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.alexstyl.specialdates.events.peopleevents; - -import android.content.ContentValues; - -import java.util.List; - -interface Marshaller { - ContentValues[] marshall(List item); -} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/PeopleEventsContentProvider.java b/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/PeopleEventsContentProvider.java index 7bbc752d..96565b4c 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/PeopleEventsContentProvider.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/PeopleEventsContentProvider.java @@ -1,157 +1,99 @@ package com.alexstyl.specialdates.events.peopleevents; import android.content.ContentProvider; +import android.content.ContentResolver; import android.content.ContentValues; +import android.content.Context; import android.content.UriMatcher; +import android.content.res.Resources; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteQueryBuilder; import android.net.Uri; -import android.support.annotation.Nullable; +import android.os.Handler; +import android.support.annotation.NonNull; -import com.alexstyl.specialdates.date.Date; -import com.alexstyl.specialdates.date.DateParseException; +import com.alexstyl.specialdates.contact.ContactsProvider; +import com.alexstyl.specialdates.events.database.DatabaseContract; +import com.alexstyl.specialdates.events.database.EventColumns; import com.alexstyl.specialdates.events.database.EventSQLiteOpenHelper; -import com.alexstyl.specialdates.events.database.EventsDBContract; -import com.alexstyl.specialdates.events.database.EventsDBContract.AnnualEventsContract; import com.alexstyl.specialdates.events.database.PeopleEventsContract; import com.alexstyl.specialdates.events.database.PeopleEventsContract.PeopleEvents; -import com.alexstyl.specialdates.upcoming.LoadingTimeDuration; +import com.alexstyl.specialdates.events.namedays.NamedayDatabaseRefresher; +import com.alexstyl.specialdates.events.namedays.NamedayPreferences; +import com.alexstyl.specialdates.events.namedays.calendar.resource.NamedayCalendarProvider; +import com.alexstyl.specialdates.permissions.PermissionChecker; +import com.alexstyl.specialdates.upcoming.NamedaySettingsMonitor; +import com.alexstyl.specialdates.util.ContactsObserver; import com.alexstyl.specialdates.util.DateParser; import com.novoda.notils.exception.DeveloperError; public class PeopleEventsContentProvider extends ContentProvider { - private static final int PEOPLE_EVENTS = 10; - private final DateParser parser = new DateParser(); + private static final int CODE_PEOPLE_EVENTS = 10; private EventSQLiteOpenHelper eventSQLHelper; private UriMatcher uriMatcher; - private PeopleEventsUpdater peopleEventsUpdater; @Override public boolean onCreate() { - eventSQLHelper = new EventSQLiteOpenHelper(getContext()); - peopleEventsUpdater = PeopleEventsUpdater.newInstance(getContext()); + Context context = getContext(); + Resources resources = context.getResources(); + ContentResolver contentResolver = context.getContentResolver(); + + ContactsProvider contactsProvider = ContactsProvider.get(context); + DateParser dateParser = DateParser.INSTANCE; + PeopleEventsRepository repository = new PeopleEventsRepository(contentResolver, contactsProvider, dateParser); + eventSQLHelper = new EventSQLiteOpenHelper(context); + PeopleEventsPersister peopleEventsPersister = new PeopleEventsPersister(eventSQLHelper); + NamedayPreferences namedayPreferences = NamedayPreferences.newInstance(context); + ContactEventsMarshaller deviceEventsMarshaller = new ContactEventsMarshaller(EventColumns.SOURCE_DEVICE); + ContactEventsMarshaller mementoMarshaller = new ContactEventsMarshaller(EventColumns.SOURCE_MEMENTO); + NamedayCalendarProvider namedayCalendarProvider = NamedayCalendarProvider.newInstance(resources); + PeopleNamedaysCalculator calculator = new PeopleNamedaysCalculator(namedayPreferences, namedayCalendarProvider, contactsProvider); + peopleEventsUpdater = new PeopleEventsUpdater( + new PeopleEventsDatabaseRefresher(repository, deviceEventsMarshaller, peopleEventsPersister), + new NamedayDatabaseRefresher(namedayPreferences, peopleEventsPersister, mementoMarshaller, calculator), + new EventPreferences(context), + new ContactsObserver(contentResolver, new Handler()), + new NamedaySettingsMonitor(namedayPreferences), + new PermissionChecker(context) + ); peopleEventsUpdater.register(); - initialiseMatcher(); - return true; - } - - private void initialiseMatcher() { uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); - uriMatcher.addURI(PeopleEventsContract.AUTHORITY, PeopleEventsContract.PeopleEvents.PATH, PEOPLE_EVENTS); + uriMatcher.addURI(PeopleEventsContract.AUTHORITY, PeopleEventsContract.PeopleEvents.PATH, CODE_PEOPLE_EVENTS); + return true; } - private SQLArgumentBuilder sqlArgumentBuilder = new SQLArgumentBuilder(); - - @Nullable @Override - public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { - refreshEventsIfNeeded(); - - SQLiteDatabase db = eventSQLHelper.getReadableDatabase(); - SQLiteQueryBuilder builder = new SQLiteQueryBuilder(); - - int uriType = uriMatcher.match(uri); - - if (uriType == PEOPLE_EVENTS) { - if (isInvalidSetOfArguments(selectionArgs)) { - throwUnsupportedOperation("3+ select args"); - } - Cursor[] cursors = new Cursor[2]; - - cursors[0] = queryAnnualEvents(projection, selection, selectionArgs, sortOrder, db, builder); - cursors[1] = queryDynamicEvents(projection, selection, selectionArgs, sortOrder, db, builder); - - SortCursor sortCursor = new SortCursor(cursors, AnnualEventsContract.DATE); - sortCursor.setNotificationUri(getContext().getContentResolver(), uri); - return sortCursor; - } else { - throwForInvalidUri("querying", uri); - return null; - } - } - - private boolean isInvalidSetOfArguments(String[] selectionArgs) { - return selectionArgs != null && selectionArgs.length > 2; - } - - private Cursor queryDynamicEvents(String[] projection, String selection, String[] selectionArgs, String sortOrder, SQLiteDatabase db, SQLiteQueryBuilder builder) { - builder.setTables(EventsDBContract.DynamicEventsContract.TABLE_NAME); - return builder.query(db, projection, selection, selectionArgs, null, null, sortOrder); - } - - private Cursor queryAnnualEvents(String[] projection, String selection, String[] selectionArgs, String sortOrder, SQLiteDatabase db, SQLiteQueryBuilder builder) { - builder.setTables(AnnualEventsContract.TABLE_NAME); - - if (selectionArgs != null) { - LoadingTimeDuration duration = getDurationfrom(selection, selectionArgs); - selection = getSelectionArgumentsForAnnualEvents(duration); - projection = makeAnnualProjectFrom(projection, duration.getFrom().getYear()); - } - return builder.query(db, projection, selection, null, null, null, sortOrder); - } - - private String[] makeAnnualProjectFrom(String[] projection, int year) { - if (projection == null || projection.length == 0) { - return projection; - } - String[] newProjection = new String[projection.length]; - for (int i = 0; i < newProjection.length; i++) { - if (AnnualEventsContract.DATE.equals(projection[i])) { - newProjection[i] = sqlArgumentBuilder.dateIn(year); - } else { - newProjection[i] = projection[i]; - } - } - return newProjection; - } + public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { + peopleEventsUpdater.updateEventsIfNeeded(); - private LoadingTimeDuration getDurationfrom(String selection, String[] selectionArgs) { - if (selectionArgs.length == 1) { - try { - Date date = parser.parse(selectionArgs[0]); - if (selection.contains(">") || selection.contains("<")) { - Date endOfYear = Date.endOfYear(date.getYear()); - return new LoadingTimeDuration(date, endOfYear); - } else { - return new LoadingTimeDuration(date, date); - } - } catch (DateParseException e) { - throw new DeveloperError(e.getMessage()); - } - } - if (selectionArgs.length == 2) { - Date[] dates = new Date[2]; - for (int i = 0; i < selectionArgs.length; i++) { - try { - dates[i] = parser.parse(selectionArgs[i]); - } catch (DateParseException e) { - throw new DeveloperError(e.getMessage()); - } - } - return new LoadingTimeDuration(dates[0], dates[1]); + if (isAboutPeopleEvents(uri)) { + UriQuery query = new UriQuery(uri, projection, selection, selectionArgs, sortOrder); + SQLiteDatabase db = eventSQLHelper.getReadableDatabase(); + Cursor cursor = queryAnnualEvents(query, db); + cursor.setNotificationUri(getContext().getContentResolver(), uri); + return cursor; } - throw new DeveloperError("Invalid length " + selectionArgs); - } - - private String getSelectionArgumentsForAnnualEvents(LoadingTimeDuration duration) { - return sqlArgumentBuilder.dateBetween(duration); + return null; } - private void throwForInvalidUri(String when, Uri uri) { - throw new DeveloperError("Invalid uri passed " + uri + " while " + when); + private boolean isAboutPeopleEvents(Uri uri) { + int uriType = uriMatcher.match(uri); + return uriType == CODE_PEOPLE_EVENTS; } - private void refreshEventsIfNeeded() { - peopleEventsUpdater.updateEventsIfNeeded(); + private Cursor queryAnnualEvents(UriQuery query, SQLiteDatabase db) { + SQLiteQueryBuilder builder = new SQLiteQueryBuilder(); + builder.setTables(DatabaseContract.AnnualEventsContract.TABLE_NAME); + return builder.query(db, query.getProjection(), query.getSelection(), query.getSelectionArgs(), null, null, query.getSortOrder()); } @Override - public String getType(Uri uri) { + public String getType(@NonNull Uri uri) { switch (uriMatcher.match(uri)) { - case PEOPLE_EVENTS: + case CODE_PEOPLE_EVENTS: return PeopleEvents.CONTENT_TYPE; default: return null; @@ -159,7 +101,7 @@ public String getType(Uri uri) { } @Override - public Uri insert(Uri uri, ContentValues values) { + public Uri insert(@NonNull Uri uri, ContentValues values) { throwUnsupportedOperation("insert"); return null; } @@ -169,13 +111,13 @@ private void throwUnsupportedOperation(String insert) { } @Override - public int delete(Uri uri, String selection, String[] selectionArgs) { + public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) { throwUnsupportedOperation("delete"); return -1; } @Override - public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) { throwUnsupportedOperation("update"); return -1; } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/PeopleEventsDatabaseRefresher.java b/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/PeopleEventsDatabaseRefresher.java new file mode 100644 index 00000000..863b6cee --- /dev/null +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/PeopleEventsDatabaseRefresher.java @@ -0,0 +1,32 @@ +package com.alexstyl.specialdates.events.peopleevents; + +import android.content.ContentValues; + +import com.alexstyl.specialdates.date.ContactEvent; + +import java.util.List; + +class PeopleEventsDatabaseRefresher { + + private final PeopleEventsRepository repository; + private final PeopleEventsPersister persister; + private final ContactEventsMarshaller marshaller; + + PeopleEventsDatabaseRefresher(PeopleEventsRepository repository, ContactEventsMarshaller marshaller, PeopleEventsPersister persister) { + this.persister = persister; + this.marshaller = marshaller; + this.repository = repository; + } + + void refreshEvents() { + persister.deleteAllDeviceEvents(); + List contacts = repository.fetchPeopleWithEvents(); + storeContactsToProvider(contacts); + } + + private void storeContactsToProvider(List contacts) { + ContentValues[] values = marshaller.marshall(contacts); + persister.insertAnnualEvents(values); + } + +} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/PeopleEventsPersister.java b/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/PeopleEventsPersister.java index 49beb21c..0a2a3ad5 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/PeopleEventsPersister.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/PeopleEventsPersister.java @@ -5,15 +5,15 @@ import android.database.sqlite.SQLiteDatabase; import com.alexstyl.specialdates.ErrorTracker; +import com.alexstyl.specialdates.events.database.DatabaseContract.AnnualEventsContract; import com.alexstyl.specialdates.events.database.EventSQLiteOpenHelper; -import com.alexstyl.specialdates.events.database.EventsDBContract.AnnualEventsContract; -import com.alexstyl.specialdates.events.database.EventsDBContract.DynamicEventsContract; +import com.alexstyl.specialdates.events.database.EventTypeId; -public class PeopleEventsPersister { +public final class PeopleEventsPersister { private final EventSQLiteOpenHelper helper; - public PeopleEventsPersister(EventSQLiteOpenHelper helper) { + PeopleEventsPersister(EventSQLiteOpenHelper helper) { this.helper = helper; } @@ -21,18 +21,14 @@ public void deleteAllNamedays() { deleteAllEventsOfType(AnnualEventsContract.TYPE_NAMEDAY); } - public void deleteAllBirthdays() { - deleteAllEventsOfType(AnnualEventsContract.TYPE_BIRTHDAY); - } - - private void deleteAllEventsOfType(int type) { + private void deleteAllEventsOfType(@EventTypeId int eventType) { SQLiteDatabase database = helper.getWritableDatabase(); - database.delete(AnnualEventsContract.TABLE_NAME, AnnualEventsContract.EVENT_TYPE + "=" + type, null); - database.delete(DynamicEventsContract.TABLE_NAME, DynamicEventsContract.EVENT_TYPE + "=" + type, null); + database.delete(AnnualEventsContract.TABLE_NAME, AnnualEventsContract.EVENT_TYPE + "==" + eventType, null); } - public void insertDynamicEvents(ContentValues[] values) { - insertEventsInTable(values, DynamicEventsContract.TABLE_NAME); + void deleteAllDeviceEvents() { + SQLiteDatabase database = helper.getWritableDatabase(); + database.delete(AnnualEventsContract.TABLE_NAME, AnnualEventsContract.SOURCE + " == " + AnnualEventsContract.SOURCE_DEVICE, null); } public void insertAnnualEvents(ContentValues[] values) { diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/PeopleEventsRepository.java b/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/PeopleEventsRepository.java new file mode 100644 index 00000000..d703ba54 --- /dev/null +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/PeopleEventsRepository.java @@ -0,0 +1,115 @@ +package com.alexstyl.specialdates.events.peopleevents; + +import android.content.ContentResolver; +import android.database.Cursor; +import android.net.Uri; +import android.provider.ContactsContract; + +import com.alexstyl.specialdates.ErrorTracker; +import com.alexstyl.specialdates.Optional; +import com.alexstyl.specialdates.contact.Contact; +import com.alexstyl.specialdates.contact.ContactNotFoundException; +import com.alexstyl.specialdates.contact.ContactsProvider; +import com.alexstyl.specialdates.date.ContactEvent; +import com.alexstyl.specialdates.date.Date; +import com.alexstyl.specialdates.date.DateParseException; +import com.alexstyl.specialdates.util.DateParser; +import com.novoda.notils.logger.simple.Log; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static com.alexstyl.specialdates.events.peopleevents.StandardEventType.*; + +class PeopleEventsRepository { + + private static final Uri CONTENT_URI = ContactsContract.Data.CONTENT_URI; + private static final String[] PROJECTION = { + ContactsContract.Data.CONTACT_ID, + ContactsContract.CommonDataKinds.Event.TYPE, + ContactsContract.CommonDataKinds.Event._ID, + ContactsContract.CommonDataKinds.Event.START_DATE + }; + private static final String SELECTION = + "( " + ContactsContract.Data.MIMETYPE + " = ? " + + " AND " + ContactsContract.Data.IN_VISIBLE_GROUP + " = 1)"; + + private static final String[] SELECT_ARGS = {ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE}; + private static final String SORT_ORDER = null; + + private static final List NO_EVENTS = Collections.emptyList(); + + private final ContentResolver contentResolver; + private final ContactsProvider contactsProvider; + private final DateParser dateParser; + + PeopleEventsRepository(ContentResolver contentResolver, ContactsProvider contactsProvider, DateParser dateParser) { + this.contentResolver = contentResolver; + this.contactsProvider = contactsProvider; + this.dateParser = dateParser; + } + + List fetchPeopleWithEvents() { + Cursor cursor = contentResolver.query(CONTENT_URI, PROJECTION, SELECTION, SELECT_ARGS, SORT_ORDER); + if (isInvalid(cursor)) { + return NO_EVENTS; + } + List events = new ArrayList<>(); + try { + while (cursor.moveToNext()) { + long contactId = getContactIdFrom(cursor); + EventType eventType = getEventTypeFrom(cursor); + try { + Date eventDate = getEventDateFrom(cursor); + long eventId = getEventIdFrom(cursor); + Contact contact = contactsProvider.getOrCreateContact(contactId); + events.add(new ContactEvent(new Optional<>(eventId), eventType, eventDate, contact)); + } catch (DateParseException e) { + ErrorTracker.track(e); + } catch (ContactNotFoundException e) { + Log.e(e); + } + } + } finally { + cursor.close(); + } + return events; + } + + private static long getEventIdFrom(Cursor cursor) { + int eventIdIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Event._ID); + return cursor.getLong(eventIdIndex); + } + + private long getContactIdFrom(Cursor cursor) { + int contactIdIndex = cursor.getColumnIndex(ContactsContract.Data.CONTACT_ID); + return cursor.getLong(contactIdIndex); + } + + private Date getEventDateFrom(Cursor cursor) throws DateParseException { + int dateIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Event.START_DATE); + String dateRaw = cursor.getString(dateIndex); + + return dateParser.parse(dateRaw); + } + + private EventType getEventTypeFrom(Cursor cursor) { + int eventTypeIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Event.TYPE); + int eventTypeRaw = cursor.getInt(eventTypeIndex); + switch (eventTypeRaw) { + case ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY: + return BIRTHDAY; + case ContactsContract.CommonDataKinds.Event.TYPE_ANNIVERSARY: + return ANNIVERSARY; + case ContactsContract.CommonDataKinds.Event.TYPE_CUSTOM: + return CUSTOM; + } + return OTHER; + } + + private boolean isInvalid(Cursor cursor) { + return cursor == null || cursor.isClosed(); + } + +} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/PeopleEventsUpdater.java b/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/PeopleEventsUpdater.java index 80e785d4..987b2057 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/PeopleEventsUpdater.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/PeopleEventsUpdater.java @@ -1,52 +1,30 @@ package com.alexstyl.specialdates.events.peopleevents; -import android.content.Context; -import android.os.Handler; - import com.alexstyl.specialdates.ErrorTracker; -import com.alexstyl.specialdates.MementoApplication; import com.alexstyl.specialdates.events.namedays.NamedayDatabaseRefresher; -import com.alexstyl.specialdates.events.namedays.NamedayPreferences; import com.alexstyl.specialdates.permissions.PermissionChecker; import com.alexstyl.specialdates.upcoming.NamedaySettingsMonitor; import com.alexstyl.specialdates.util.ContactsObserver; class PeopleEventsUpdater { - private final BirthdayDatabaseRefresher birthdayDatabaseRefresher; + private static final Object REFRESH_LOCK = new Object(); + + private final PeopleEventsDatabaseRefresher peopleEventsDatabaseRefresher; private final EventPreferences eventPreferences; private final NamedaySettingsMonitor namedayMonitor; private final ContactsObserver contactsObserver; private final PermissionChecker permissionChecker; + private final NamedayDatabaseRefresher namedayDatabaseRefresher; - private NamedayDatabaseRefresher namedayDatabaseRefresher; - - static PeopleEventsUpdater newInstance(Context context) { - BirthdayDatabaseRefresher birthdayDatabaseRefresher = BirthdayDatabaseRefresher.newInstance(context); - NamedayDatabaseRefresher namedayDatabaseRefresher = NamedayDatabaseRefresher.newInstance(context); - EventPreferences eventPreferences = new EventPreferences(context); - ContactsObserver contactsObserver = new ContactsObserver(context.getContentResolver(), new Handler()); - NamedaySettingsMonitor namedaySettingsMonitor = new NamedaySettingsMonitor(NamedayPreferences.newInstance(context)); - namedaySettingsMonitor.initialise(); - PermissionChecker permissionChecker = new PermissionChecker(context); - return new PeopleEventsUpdater( - birthdayDatabaseRefresher, - namedayDatabaseRefresher, - eventPreferences, - contactsObserver, - namedaySettingsMonitor, - permissionChecker - ); - } - - PeopleEventsUpdater(BirthdayDatabaseRefresher birthdayDatabaseRefresher, + PeopleEventsUpdater(PeopleEventsDatabaseRefresher peopleEventsDatabaseRefresher, NamedayDatabaseRefresher namedayDatabaseRefresher, EventPreferences eventPreferences, ContactsObserver contactsObserver, NamedaySettingsMonitor namedayMonitor, PermissionChecker permissionChecker ) { - this.birthdayDatabaseRefresher = birthdayDatabaseRefresher; + this.peopleEventsDatabaseRefresher = peopleEventsDatabaseRefresher; this.namedayDatabaseRefresher = namedayDatabaseRefresher; this.eventPreferences = eventPreferences; this.contactsObserver = contactsObserver; @@ -54,21 +32,21 @@ static PeopleEventsUpdater newInstance(Context context) { this.permissionChecker = permissionChecker; } - public void updateEventsIfNeeded() { + void updateEventsIfNeeded() { if (!permissionChecker.canReadAndWriteContacts()) { ErrorTracker.track(new RuntimeException("Tried to update events without permission")); return; } - boolean isFirstRun = isFirstTimeRunning(); - if (isFirstRun) { - birthdayDatabaseRefresher.refreshBirthdays(); - namedayDatabaseRefresher.refreshNamedaysIfEnabled(); - eventPreferences.markEventsAsInitialised(); - } else { - updateEventsIfSettingsChanged(); + synchronized (REFRESH_LOCK) { + if (isFirstTimeRunning()) { + peopleEventsDatabaseRefresher.refreshEvents(); + namedayDatabaseRefresher.refreshNamedaysIfEnabled(); + eventPreferences.markEventsAsInitialised(); + } else { + updateEventsIfSettingsChanged(); + } } - } private boolean isFirstTimeRunning() { @@ -79,11 +57,8 @@ private void updateEventsIfSettingsChanged() { boolean wereContactsUpdated = contactsObserver.wereContactsUpdated(); boolean wereNamedaysSettingsUpdated = namedayMonitor.dataWasUpdated(); - if (wereNamedaysSettingsUpdated) { - namedayDatabaseRefresher = NamedayDatabaseRefresher.newInstance(MementoApplication.getContext()); - } if (wereContactsUpdated) { - birthdayDatabaseRefresher.refreshBirthdays(); + peopleEventsDatabaseRefresher.refreshEvents(); } if (wereContactsUpdated || wereNamedaysSettingsUpdated) { namedayDatabaseRefresher.refreshNamedaysIfEnabled(); @@ -96,7 +71,7 @@ private void resetMonitorFlags() { contactsObserver.resetFlag(); } - public void register() { + void register() { contactsObserver.register(); } } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/PeopleNamedaysCalculator.java b/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/PeopleNamedaysCalculator.java new file mode 100644 index 00000000..85537696 --- /dev/null +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/PeopleNamedaysCalculator.java @@ -0,0 +1,98 @@ +package com.alexstyl.specialdates.events.peopleevents; + +import com.alexstyl.specialdates.DisplayName; +import com.alexstyl.specialdates.Optional; +import com.alexstyl.specialdates.contact.Contact; +import com.alexstyl.specialdates.contact.ContactsProvider; +import com.alexstyl.specialdates.date.ContactEvent; +import com.alexstyl.specialdates.date.Date; +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.resource.NamedayCalendarProvider; +import com.alexstyl.specialdates.upcoming.TimePeriod; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +public final class PeopleNamedaysCalculator { + + private static final Optional NO_DEVICE_EVENT_ID = Optional.absent(); + private final NamedayPreferences namedayPreferences; + private final NamedayCalendarProvider namedayCalendarProvider; + private final ContactsProvider contactsProvider; + + public PeopleNamedaysCalculator(NamedayPreferences namedayPreferences, + NamedayCalendarProvider namedayCalendarProvider, + ContactsProvider contactsProvider) { + this.namedayPreferences = namedayPreferences; + this.namedayCalendarProvider = namedayCalendarProvider; + this.contactsProvider = contactsProvider; + } + + public List loadDeviceStaticNamedays() { + List namedayEvents = new ArrayList<>(); + List contacts = contactsProvider.fetchAllDeviceContacts(); + for (Contact contact : contacts) { + DisplayName displayName = contact.getDisplayName(); + HashSet namedays = new HashSet<>(); + for (String firstName : displayName.getFirstNames()) { + NameCelebrations nameDays = getNamedaysOf(firstName); + if (nameDays.containsNoDate()) { + continue; + } + int namedaysCount = nameDays.size(); + for (int i = 0; i < namedaysCount; i++) { + Date date = nameDays.getDate(i); + if (namedays.contains(date)) { + continue; + } + ContactEvent event = new ContactEvent(NO_DEVICE_EVENT_ID, StandardEventType.NAMEDAY, date, contact); + namedayEvents.add(event); + namedays.add(date); + } + } + } + + return namedayEvents; + } + + public List loadSpecialNamedaysBetween(TimePeriod timeDuration) { + List namedayEvents = new ArrayList<>(); + for (Contact contact : contactsProvider.fetchAllDeviceContacts()) { + for (String firstName : contact.getDisplayName().getFirstNames()) { + NameCelebrations nameDays = getSpecialNamedaysOf(firstName); + if (nameDays.containsNoDate()) { + continue; + } + + int namedaysCount = nameDays.size(); + for (int i = 0; i < namedaysCount; i++) { + Date date = nameDays.getDate(i); + if (timeDuration.containsDate(date)) { + ContactEvent nameday = new ContactEvent(NO_DEVICE_EVENT_ID, StandardEventType.NAMEDAY, date, contact); + namedayEvents.add(nameday); + } + } + } + } + return namedayEvents; + } + + private NameCelebrations getNamedaysOf(String given) { + NamedayCalendar namedayCalendar = getNamedayCalendar(); + return namedayCalendar.getNormalNamedaysFor(given); + } + + private NameCelebrations getSpecialNamedaysOf(String firstName) { + NamedayCalendar namedayCalendar = getNamedayCalendar(); + return namedayCalendar.getSpecialNamedaysFor(firstName); + } + + public NamedayCalendar getNamedayCalendar() { + NamedayLocale locale = namedayPreferences.getSelectedLanguage(); + return namedayCalendarProvider.loadNamedayCalendarForLocale(locale, Date.CURRENT_YEAR); + } +} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/SQLArgumentBuilder.java b/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/SQLArgumentBuilder.java index 1aa6b1b5..3d0154fe 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/SQLArgumentBuilder.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/SQLArgumentBuilder.java @@ -1,29 +1,23 @@ package com.alexstyl.specialdates.events.peopleevents; import com.alexstyl.specialdates.date.Date; -import com.alexstyl.specialdates.events.database.EventsDBContract.AnnualEventsContract; import com.alexstyl.specialdates.events.database.PeopleEventsContract; -import com.alexstyl.specialdates.upcoming.LoadingTimeDuration; -import java.util.Locale; +public final class SQLArgumentBuilder { -class SQLArgumentBuilder { - - private static final String date = PeopleEventsContract.PeopleEvents.DATE; - - String dateIn(int year) { - return String.format(Locale.US, "'%d' || substr(date, -6) as '%s'", year, AnnualEventsContract.DATE); - } - - String dateBetween(LoadingTimeDuration duration) { - return dateFrom(duration.getFrom()) + " AND " + dateTo(duration.getTo()); - } - - private String dateFrom(Date date) { - return String.format(Locale.US, "'%d' || substr(%s,-6) >= '%s'", date.getYear(), SQLArgumentBuilder.date, date.toString()); + public static String dateWithoutYear(Date date) { + StringBuilder stringBuilder = new StringBuilder(); + int month = date.getMonth(); + addWithLeadingZeroIfNeeded(stringBuilder, month); + stringBuilder.append("-"); + addWithLeadingZeroIfNeeded(stringBuilder, date.getDayOfMonth()); + return stringBuilder.toString(); } - private String dateTo(Date date) { - return String.format(Locale.US, "'%d' || substr(%s,-6) <= '%s'", date.getYear(), SQLArgumentBuilder.date, date.toString()); + private static void addWithLeadingZeroIfNeeded(StringBuilder stringBuilder, int value) { + if (value < 10) { + stringBuilder.append("0"); + } + stringBuilder.append(value); } } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/SortCursor.java b/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/SortCursor.java deleted file mode 100644 index d9266977..00000000 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/SortCursor.java +++ /dev/null @@ -1,298 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.alexstyl.specialdates.events.peopleevents; - -import android.annotation.TargetApi; -import android.database.AbstractCursor; -import android.database.Cursor; -import android.database.DataSetObserver; -import android.os.Build; -import android.util.Log; - -/** - * A variant of MergeCursor that sorts the cursors being merged. If decent - * performance is ever obtained, it can be put back under android.database. - */ -class SortCursor extends AbstractCursor { - private static final String TAG = "SortCursor"; - private Cursor mCursor; // updated in onMove - private Cursor[] mCursors; - private int[] mSortColumns; - private final int ROWCACHESIZE = 64; - private int mRowNumCache[] = new int[ROWCACHESIZE]; - private int mCursorCache[] = new int[ROWCACHESIZE]; - private int mCurRowNumCache[][]; - private int mLastCacheHit = -1; - private DataSetObserver mObserver = new DataSetObserver() { - @Override - public void onChanged() { - // Reset our position so the optimizations in move-related code - // don't screw us over - mPos = -1; - } - - @Override - public void onInvalidated() { - mPos = -1; - } - }; - - public SortCursor(Cursor[] cursors, String sortcolumn) { - mCursors = cursors; - int length = mCursors.length; - mSortColumns = new int[length]; - for (int i = 0; i < length; i++) { - if (mCursors[i] == null) { - continue; - } - - // Register ourself as a data set observer - mCursors[i].registerDataSetObserver(mObserver); - - mCursors[i].moveToFirst(); - // We don't catch the exception - mSortColumns[i] = mCursors[i].getColumnIndexOrThrow(sortcolumn); - } - mCursor = null; - String smallest = ""; - for (int j = 0; j < length; j++) { - if (mCursors[j] == null || mCursors[j].isAfterLast()) { - continue; - } - String current = mCursors[j].getString(mSortColumns[j]); - if (mCursor == null || current.compareToIgnoreCase(smallest) < 0) { - smallest = current; - mCursor = mCursors[j]; - } - } - for (int i = mRowNumCache.length - 1; i >= 0; i--) { - mRowNumCache[i] = -2; - } - mCurRowNumCache = new int[ROWCACHESIZE][length]; - } - - @Override - public int getCount() { - int count = 0; - int length = mCursors.length; - for (int i = 0; i < length; i++) { - if (mCursors[i] != null) { - count += mCursors[i].getCount(); - } - } - return count; - } - - @Override - public boolean onMove(int oldPosition, int newPosition) { - if (oldPosition == newPosition) { - return true; - } - /* Find the right cursor - * Because the client of this cursor (the listadapter/view) tends - * to jump around in the cursor somewhat, a simple cache strategy - * is used to avoid having to search all cursors from the start. - * TODO: investigate strategies for optimizing random access and - * reverse-order access. - */ - int cache_entry = newPosition % ROWCACHESIZE; - if (mRowNumCache[cache_entry] == newPosition) { - int which = mCursorCache[cache_entry]; - mCursor = mCursors[which]; - if (mCursor == null) { - Log.w(TAG, "onMove: cache results in a null cursor."); - return false; - } - mCursor.moveToPosition(mCurRowNumCache[cache_entry][which]); - mLastCacheHit = cache_entry; - return true; - } - mCursor = null; - int length = mCursors.length; - if (mLastCacheHit >= 0) { - for (int i = 0; i < length; i++) { - if (mCursors[i] == null) { - continue; - } - mCursors[i].moveToPosition(mCurRowNumCache[mLastCacheHit][i]); - } - } - if (newPosition < oldPosition || oldPosition == -1) { - for (int i = 0; i < length; i++) { - if (mCursors[i] == null) { - continue; - } - mCursors[i].moveToFirst(); - } - oldPosition = 0; - } - if (oldPosition < 0) { - oldPosition = 0; - } - // search forward to the new position - int smallestIdx = -1; - for (int i = oldPosition; i <= newPosition; i++) { - String smallest = ""; - smallestIdx = -1; - for (int j = 0; j < length; j++) { - if (mCursors[j] == null || mCursors[j].isAfterLast()) { - continue; - } - String current = mCursors[j].getString(mSortColumns[j]); - if (smallestIdx < 0 || current.compareToIgnoreCase(smallest) < 0) { - smallest = current; - smallestIdx = j; - } - } - if (i == newPosition) { - break; - } - if (mCursors[smallestIdx] != null) { - mCursors[smallestIdx].moveToNext(); - } - } - mCursor = mCursors[smallestIdx]; - mRowNumCache[cache_entry] = newPosition; - mCursorCache[cache_entry] = smallestIdx; - for (int i = 0; i < length; i++) { - if (mCursors[i] != null) { - mCurRowNumCache[cache_entry][i] = mCursors[i].getPosition(); - } - } - mLastCacheHit = -1; - return true; - } - - @Override - public String getString(int column) { - return mCursor.getString(column); - } - - @Override - public short getShort(int column) { - return mCursor.getShort(column); - } - - @Override - public int getInt(int column) { - return mCursor.getInt(column); - } - - @Override - public long getLong(int column) { - return mCursor.getLong(column); - } - - @Override - public float getFloat(int column) { - return mCursor.getFloat(column); - } - - @Override - public double getDouble(int column) { - return mCursor.getDouble(column); - } - - @TargetApi(Build.VERSION_CODES.HONEYCOMB) - @Override - public int getType(int column) { - return mCursor.getType(column); - } - - @Override - public boolean isNull(int column) { - return mCursor.isNull(column); - } - - @Override - public byte[] getBlob(int column) { - return mCursor.getBlob(column); - } - - @Override - public String[] getColumnNames() { - if (mCursor != null) { - return mCursor.getColumnNames(); - } else { - // All of the cursors may be empty, but they can still return - // this information. - int length = mCursors.length; - for (int i = 0; i < length; i++) { - if (mCursors[i] != null) { - return mCursors[i].getColumnNames(); - } - } - throw new IllegalStateException("No cursor that can return names"); - } - } - - @Override - public void deactivate() { - int length = mCursors.length; - for (int i = 0; i < length; i++) { - if (mCursors[i] == null) { - continue; - } - mCursors[i].deactivate(); - } - } - - @Override - public void close() { - int length = mCursors.length; - for (int i = 0; i < length; i++) { - if (mCursors[i] == null) { - continue; - } - mCursors[i].close(); - } - } - - @Override - public void registerDataSetObserver(DataSetObserver observer) { - int length = mCursors.length; - for (int i = 0; i < length; i++) { - if (mCursors[i] != null) { - mCursors[i].registerDataSetObserver(observer); - } - } - } - - @Override - public void unregisterDataSetObserver(DataSetObserver observer) { - int length = mCursors.length; - for (int i = 0; i < length; i++) { - if (mCursors[i] != null) { - mCursors[i].unregisterDataSetObserver(observer); - } - } - } - - @Override - public boolean requery() { - int length = mCursors.length; - for (int i = 0; i < length; i++) { - if (mCursors[i] == null) { - continue; - } - - if (mCursors[i].requery() == false) { - return false; - } - } - return true; - } -} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/EventType.java b/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/StandardEventType.java similarity index 53% rename from mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/EventType.java rename to mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/StandardEventType.java index ea9e1b41..56f21e33 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/EventType.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/StandardEventType.java @@ -1,52 +1,62 @@ package com.alexstyl.specialdates.events.peopleevents; +import android.content.res.Resources; import android.support.annotation.ColorRes; import android.support.annotation.StringRes; import com.alexstyl.specialdates.R; +import com.alexstyl.specialdates.events.database.DatabaseContract.AnnualEventsContract; import com.alexstyl.specialdates.events.database.EventTypeId; -import com.alexstyl.specialdates.events.database.EventsDBContract.AnnualEventsContract; import java.util.HashMap; import java.util.Map; -public enum EventType { +public enum StandardEventType implements EventType { BIRTHDAY(AnnualEventsContract.TYPE_BIRTHDAY, R.string.birthday, R.color.birthday_red), - NAMEDAY(AnnualEventsContract.TYPE_NAMEDAY, R.string.nameday, R.color.nameday_blue); + NAMEDAY(AnnualEventsContract.TYPE_NAMEDAY, R.string.nameday, R.color.nameday_blue), + ANNIVERSARY(AnnualEventsContract.TYPE_ANNIVERSARY, R.string.Anniversary, R.color.anniversary_yellow), + OTHER(AnnualEventsContract.TYPE_OTHER, R.string.Other, R.color.past_date_grey), + CUSTOM(AnnualEventsContract.TYPE_CUSTOM, R.string.Custom, R.color.past_date_grey); + @EventTypeId private final int eventTypeId; private final int eventNameRes; private final int eventColorRes; - EventType(@EventTypeId int eventTypeId, @StringRes int nameResId, @ColorRes int colorResId) { + StandardEventType(@EventTypeId int eventTypeId, @StringRes int nameResId, @ColorRes int colorResId) { this.eventTypeId = eventTypeId; this.eventNameRes = nameResId; this.eventColorRes = colorResId; } - private static final Map map; + private static final Map map; static { map = new HashMap<>(); - for (EventType eventType : values()) { + for (StandardEventType eventType : values()) { map.put(eventType.eventTypeId, eventType); } } - public static EventType fromId(@EventTypeId int eventTypeId) { + public static StandardEventType fromId(@EventTypeId int eventTypeId) { if (map.containsKey(eventTypeId)) { return map.get(eventTypeId); } throw new IllegalArgumentException("No event type with eventTypeId " + eventTypeId); } - @StringRes - public int nameRes() { - return eventNameRes; + @Override + public String getEventName(Resources resources) { + return resources.getString(eventNameRes); } @ColorRes public int getColorRes() { return eventColorRes; } + + @EventTypeId + public int getId() { + return eventTypeId; + } } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/UriQuery.java b/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/UriQuery.java new file mode 100644 index 00000000..de3d6e89 --- /dev/null +++ b/mobile/src/main/java/com/alexstyl/specialdates/events/peopleevents/UriQuery.java @@ -0,0 +1,39 @@ +package com.alexstyl.specialdates.events.peopleevents; + +import android.net.Uri; + +final class UriQuery { + private final Uri uri; + private final String[] projection; + private final String selection; + private final String[] selectionArgs; + private final String sortOrder; + + UriQuery(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { + this.uri = uri; + this.projection = projection; + this.selection = selection; + this.selectionArgs = selectionArgs; + this.sortOrder = sortOrder; + } + + Uri getUri() { + return uri; + } + + String[] getProjection() { + return projection; + } + + String getSelection() { + return selection; + } + + String[] getSelectionArgs() { + return selectionArgs; + } + + String getSortOrder() { + return sortOrder; + } +} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/search/ContactWithEventsSearch.java b/mobile/src/main/java/com/alexstyl/specialdates/search/ContactWithEventsSearch.java index 6b31ee3f..7ae43350 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/search/ContactWithEventsSearch.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/search/ContactWithEventsSearch.java @@ -1,92 +1,50 @@ package com.alexstyl.specialdates.search; -import android.content.Context; -import android.database.Cursor; - -import com.alexstyl.specialdates.DisplayName; import com.alexstyl.specialdates.contact.Contact; -import com.alexstyl.specialdates.contact.ContactNotFoundException; -import com.alexstyl.specialdates.contact.ContactProvider; -import com.alexstyl.specialdates.events.database.PeopleEventsContract.PeopleEvents; -import com.novoda.notils.logger.simple.Log; +import com.alexstyl.specialdates.contact.ContactsProvider; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; -final public class ContactWithEventsSearch { +final class ContactWithEventsSearch { - private final Context context; - private final ContactProvider contactProvider; + private final ContactsProvider contactsProvider; private final NameMatcher nameMatcher; - public static ContactWithEventsSearch newInstance(Context context) { - ContactProvider contactProvider = ContactProvider.get(context); - NameMatcher nameMatcher = NameMatcher.newInstance(); - return new ContactWithEventsSearch(context, contactProvider, nameMatcher); - } - - ContactWithEventsSearch(Context context, ContactProvider contactProvider, NameMatcher nameMatcher) { - this.contactProvider = contactProvider; + ContactWithEventsSearch(ContactsProvider contactsProvider, NameMatcher nameMatcher) { + this.contactsProvider = contactsProvider; this.nameMatcher = nameMatcher; - this.context = context.getApplicationContext(); } - public List searchForContacts(String searchQuery, int counter) { + List searchForContacts(String searchQuery, int counter) { // since we cannot select ignoring accents, we have to manually do a searching searchQuery = searchQuery.trim(); List contacts = new ArrayList<>(); Set stashedContacts = new HashSet<>(); - Cursor cursor = queryAllContactsWithEvents(); - + List allDeviceContacts = contactsProvider.fetchAllDeviceContacts(); int size = 0; - while (cursor.moveToNext() && size < counter) { - long contactID = getContactIDFrom(cursor); + for (Contact contact : allDeviceContacts) { + long contactID = contact.getContactID(); if (stashedContacts.contains(contactID)) { continue; } - DisplayName displayName = getDisplayNameFrom(cursor); - if (nameMatcher.match(displayName, searchQuery)) { - long contactId = PeopleEvents.getContactIdFrom(cursor); - Contact contact; - try { - contact = contactProvider.getOrCreateContact(contactId); - stashedContacts.add(contactID); - contacts.add(contact); - size++; - } catch (ContactNotFoundException e) { - Log.w(e); - } + if (nameMatcher.match(contact.getDisplayName(), searchQuery)) { + stashedContacts.add(contactID); + contacts.add(contact); + size++; + } + if (size >= counter) { + break; } } - cursor.close(); - return contacts; } - private DisplayName getDisplayNameFrom(Cursor cursor) { - int displayNameIndex = cursor.getColumnIndex(PeopleEvents.DISPLAY_NAME); - String displayName = cursor.getString(displayNameIndex); - return DisplayName.from(displayName); - } - - private long getContactIDFrom(Cursor cursor) { - int indexContactID = cursor.getColumnIndexOrThrow(PeopleEvents.CONTACT_ID); - return cursor.getLong(indexContactID); - } - - private Cursor queryAllContactsWithEvents() { - return context.getContentResolver().query( - PeopleEvents.CONTENT_URI, - null, null, null, - PeopleEvents.CONTACT_ID - ); - } - } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/search/ContactsSearch.java b/mobile/src/main/java/com/alexstyl/specialdates/search/ContactsSearch.java index 930c6cb5..abcae0bf 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/search/ContactsSearch.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/search/ContactsSearch.java @@ -7,7 +7,7 @@ import com.alexstyl.specialdates.DisplayName; import com.alexstyl.specialdates.contact.Contact; import com.alexstyl.specialdates.contact.ContactNotFoundException; -import com.alexstyl.specialdates.contact.ContactProvider; +import com.alexstyl.specialdates.contact.ContactsProvider; import com.novoda.notils.logger.simple.Log; import java.util.ArrayList; @@ -18,18 +18,18 @@ final public class ContactsSearch { private final Context context; - private final ContactProvider contactProvider; + private final ContactsProvider contactsProvider; private final NameMatcher nameMatcher; public static ContactsSearch newInstance(Context context) { - ContactProvider contactProvider = ContactProvider.get(context); + ContactsProvider contactsProvider = ContactsProvider.get(context); NameMatcher nameMatcher = NameMatcher.newInstance(); - return new ContactsSearch(context, contactProvider, nameMatcher); + return new ContactsSearch(context, contactsProvider, nameMatcher); } - ContactsSearch(Context context, ContactProvider contactProvider, NameMatcher nameMatcher) { - this.contactProvider = contactProvider; + ContactsSearch(Context context, ContactsProvider contactsProvider, NameMatcher nameMatcher) { + this.contactsProvider = contactsProvider; this.nameMatcher = nameMatcher; this.context = context.getApplicationContext(); } @@ -52,7 +52,7 @@ public List searchForContacts(String searchQuery, int counter) { DisplayName displayName = getDisplayNameFrom(cursor); if (nameMatcher.match(displayName, searchQuery)) { try { - Contact contact = contactProvider.getOrCreateContact(contactID); + Contact contact = contactsProvider.getOrCreateContact(contactID); stashedContacts.add(contactID); contacts.add(contact); size++; diff --git a/mobile/src/main/java/com/alexstyl/specialdates/search/NameMatcher.java b/mobile/src/main/java/com/alexstyl/specialdates/search/NameMatcher.java index 03ffb9af..ce6f7fe9 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/search/NameMatcher.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/search/NameMatcher.java @@ -6,7 +6,7 @@ import java.text.Collator; import java.util.Locale; -public class NameMatcher { +class NameMatcher { private final Collator collator; diff --git a/mobile/src/main/java/com/alexstyl/specialdates/search/SearchLoader.java b/mobile/src/main/java/com/alexstyl/specialdates/search/SearchLoader.java index 8663601f..e9b75640 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/search/SearchLoader.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/search/SearchLoader.java @@ -4,12 +4,13 @@ import android.os.Handler; import com.alexstyl.specialdates.contact.Contact; +import com.alexstyl.specialdates.contact.ContactsProvider; import com.alexstyl.specialdates.ui.loader.SimpleAsyncTaskLoader; import com.alexstyl.specialdates.util.ContactsObserver; import java.util.List; -public class SearchLoader extends SimpleAsyncTaskLoader { +class SearchLoader extends SimpleAsyncTaskLoader { private final ContactWithEventsSearch contactWithEventsSearch; private final String searchQuery; @@ -17,9 +18,11 @@ public class SearchLoader extends SimpleAsyncTaskLoader { private final ContactsObserver observer; - public SearchLoader(Context context, String query, int mSearchCounter) { + SearchLoader(Context context, String query, int mSearchCounter) { super(context); - this.contactWithEventsSearch = ContactWithEventsSearch.newInstance(context); + ContactsProvider contactsProvider = ContactsProvider.get(context); + NameMatcher nameMatcher = NameMatcher.newInstance(); + this.contactWithEventsSearch = new ContactWithEventsSearch(contactsProvider, nameMatcher); this.searchQuery = query; this.searchCounter = mSearchCounter; 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 46fd4878..5b05a60e 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/search/SearchResultContactViewHolder.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/search/SearchResultContactViewHolder.java @@ -65,14 +65,15 @@ public void onClick(View v) { } private void bindBirthday(Contact contact) { - if (contact.hasDateOfBirth()) { - Date birthday = contact.getDateOfBirth(); - String message = getBirthdayString(getContext(), birthday); - this.birthday.setVisibility(View.VISIBLE); - this.birthday.setText(message); - } else { + // TODO handle event +// if (contact.hasDateOfBirth()) { +// Date birthday = contact.getDateOfBirth(); +// String message = getBirthdayString(getContext(), birthday); +// this.birthday.setVisibility(View.VISIBLE); +// this.birthday.setText(message); +// } else { this.birthday.setVisibility(View.GONE); - } +// } } private void bindNamedays(Contact contact) { diff --git a/mobile/src/main/java/com/alexstyl/specialdates/service/CustomEventProvider.java b/mobile/src/main/java/com/alexstyl/specialdates/service/CustomEventProvider.java new file mode 100644 index 00000000..6ae20d3c --- /dev/null +++ b/mobile/src/main/java/com/alexstyl/specialdates/service/CustomEventProvider.java @@ -0,0 +1,48 @@ +package com.alexstyl.specialdates.service; + +import android.content.ContentResolver; +import android.database.Cursor; +import android.net.Uri; +import android.provider.ContactsContract; + +import com.alexstyl.specialdates.events.peopleevents.CustomEventType; +import com.alexstyl.specialdates.events.peopleevents.EventType; +import com.alexstyl.specialdates.events.peopleevents.StandardEventType; + +class CustomEventProvider { + + private static final Uri CONTENT_URI = ContactsContract.Data.CONTENT_URI; + private static final String[] PROJECTION = { + ContactsContract.CommonDataKinds.Event.LABEL + }; + private static final String SELECTION = + "( " + ContactsContract.Data._ID + " = ?" + + " AND " + ContactsContract.Data.MIMETYPE + " = ? " + + " AND " + ContactsContract.Data.IN_VISIBLE_GROUP + " = 1" + + ")"; + + private static final String SORT_ORDER = ContactsContract.CommonDataKinds.Event._ID + " LIMIT 1"; + + private final ContentResolver resolver; + + CustomEventProvider(ContentResolver resolver) { + this.resolver = resolver; + } + + EventType getEventWithId(long deviceId) { + Cursor cursor = resolver.query(CONTENT_URI, PROJECTION, SELECTION, new String[]{ + String.valueOf(deviceId), + ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE + }, SORT_ORDER); + try { + if (cursor.moveToFirst()) { + int columnIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Event.LABEL); + String eventName = cursor.getString(columnIndex); + return new CustomEventType(eventName); + } + } finally { + cursor.close(); + } + return StandardEventType.OTHER; + } +} 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 9c75782b..5e331894 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/service/DailyReminderIntentService.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/service/DailyReminderIntentService.java @@ -7,18 +7,20 @@ import android.content.Intent; import com.alexstyl.specialdates.BuildConfig; -import com.alexstyl.specialdates.date.Date; +import com.alexstyl.specialdates.Optional; import com.alexstyl.specialdates.dailyreminder.DailyReminderDebugPreferences; -import com.alexstyl.specialdates.events.peopleevents.ContactEvents; +import com.alexstyl.specialdates.date.Date; import com.alexstyl.specialdates.events.bankholidays.BankHoliday; +import com.alexstyl.specialdates.events.bankholidays.BankHolidayProvider; import com.alexstyl.specialdates.events.bankholidays.BankHolidaysPreferences; -import com.alexstyl.specialdates.events.bankholidays.GreekBankHolidays; +import com.alexstyl.specialdates.events.bankholidays.GreekBankHolidaysCalculator; 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.OrthodoxEasterCalculator; import com.alexstyl.specialdates.events.namedays.calendar.resource.NamedayCalendarProvider; +import com.alexstyl.specialdates.events.peopleevents.ContactEvents; import com.alexstyl.specialdates.permissions.PermissionChecker; import com.alexstyl.specialdates.receiver.EventReceiver; import com.alexstyl.specialdates.settings.MainPreferenceActivity; @@ -26,7 +28,6 @@ import com.novoda.notils.logger.simple.Log; import java.util.Calendar; -import java.util.List; /** * A service that looks up all events on the specified date and notifies the user about it @@ -120,22 +121,15 @@ private boolean bankholidaysAreEnabled() { } private void notifyForBankholidaysFor(Date date) { - BankHoliday bankHoliday = findBankholidayFor(date); - if (bankHoliday != null) { - notifier.forBankholiday(date, bankHoliday); + Optional bankHoliday = findBankholidayFor(date); + if (bankHoliday.isPresent()) { + notifier.forBankholiday(date, bankHoliday.get()); } } - private BankHoliday findBankholidayFor(Date date) { - EasterCalculator calculator = new EasterCalculator(); - Date easter = calculator.calculateEasterForYear(date.getYear()); - List bankHolidays = new GreekBankHolidays(easter).getBankHolidaysForYear(); - for (BankHoliday bankHoliday : bankHolidays) { - if (bankHoliday.getDate().equals(date)) { - return bankHoliday; - } - } - return null; + private Optional findBankholidayFor(Date date) { + BankHolidayProvider provider = new BankHolidayProvider(new GreekBankHolidaysCalculator(OrthodoxEasterCalculator.INSTANCE)); + return provider.calculateBankHolidayOn(date); } private boolean containsNames(NamesInADate names) { diff --git a/mobile/src/main/java/com/alexstyl/specialdates/service/PeopleEventsProvider.java b/mobile/src/main/java/com/alexstyl/specialdates/service/PeopleEventsProvider.java index 293a4c88..0470d0fa 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/service/PeopleEventsProvider.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/service/PeopleEventsProvider.java @@ -3,46 +3,182 @@ import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; +import android.database.MergeCursor; import android.net.Uri; -import android.support.annotation.NonNull; import com.alexstyl.specialdates.ErrorTracker; +import com.alexstyl.specialdates.Optional; import com.alexstyl.specialdates.contact.Contact; import com.alexstyl.specialdates.contact.ContactNotFoundException; -import com.alexstyl.specialdates.contact.ContactProvider; +import com.alexstyl.specialdates.contact.ContactsProvider; import com.alexstyl.specialdates.date.ContactEvent; import com.alexstyl.specialdates.date.Date; +import com.alexstyl.specialdates.date.DateParseException; import com.alexstyl.specialdates.datedetails.PeopleEventsQuery; -import com.alexstyl.specialdates.events.peopleevents.ContactEvents; -import com.alexstyl.specialdates.events.peopleevents.EventType; +import com.alexstyl.specialdates.events.database.EventColumns; +import com.alexstyl.specialdates.events.database.EventTypeId; import com.alexstyl.specialdates.events.database.PeopleEventsContract; +import com.alexstyl.specialdates.events.database.PeopleEventsContract.PeopleEvents; import com.alexstyl.specialdates.events.namedays.NamedayPreferences; -import com.alexstyl.specialdates.upcoming.LoadingTimeDuration; +import com.alexstyl.specialdates.events.namedays.calendar.resource.NamedayCalendarProvider; +import com.alexstyl.specialdates.events.peopleevents.ContactEvents; +import com.alexstyl.specialdates.events.peopleevents.EventType; +import com.alexstyl.specialdates.events.peopleevents.PeopleNamedaysCalculator; +import com.alexstyl.specialdates.events.peopleevents.SQLArgumentBuilder; +import com.alexstyl.specialdates.events.peopleevents.StandardEventType; +import com.alexstyl.specialdates.upcoming.TimePeriod; +import com.alexstyl.specialdates.util.DateParser; import com.novoda.notils.exception.DeveloperError; import com.novoda.notils.logger.simple.Log; import java.util.ArrayList; +import java.util.Collections; import java.util.List; public class PeopleEventsProvider { - private final ContactProvider contactProvider; + private static final String DATE_FROM = "substr(" + PeopleEvents.DATE + ",-5) >= ?"; + private static final String DATE_TO = "substr(" + PeopleEvents.DATE + ",-5) <= ?"; + private static final String DATE_BETWEEN_IGNORING_YEAR = DATE_FROM + " AND " + DATE_TO; + private static final String[] PEOPLE_PROJECTION = new String[]{PeopleEvents.DATE}; + private static final String[] PROJECTION = { + PeopleEvents.CONTACT_ID, + PeopleEvents.DEVICE_EVENT_ID, + PeopleEvents.DATE, + PeopleEvents.EVENT_TYPE, + }; + + private final ContactsProvider contactsProvider; private final ContentResolver resolver; private final NamedayPreferences namedayPreferences; - - private static final String[] PEOPLE_PROJECTION = new String[]{PeopleEventsContract.PeopleEvents.DATE}; + private final PeopleNamedaysCalculator peopleNamedaysCalculator; + private final CustomEventProvider customEventProvider; public static PeopleEventsProvider newInstance(Context context) { - ContactProvider provider = ContactProvider.get(context); + ContactsProvider contactsProvider = ContactsProvider.get(context); ContentResolver resolver = context.getContentResolver(); NamedayPreferences namedayPreferences = NamedayPreferences.newInstance(context); - return new PeopleEventsProvider(provider, resolver, namedayPreferences); + NamedayCalendarProvider namedayCalendarProvider = NamedayCalendarProvider.newInstance(context.getResources()); + PeopleNamedaysCalculator peopleNamedaysCalculator = new PeopleNamedaysCalculator( + namedayPreferences, + namedayCalendarProvider, + contactsProvider + ); + CustomEventProvider customEventProvider = new CustomEventProvider(resolver); + return new PeopleEventsProvider(contactsProvider, resolver, namedayPreferences, peopleNamedaysCalculator, customEventProvider); } - public PeopleEventsProvider(ContactProvider contactProvider, ContentResolver resolver, NamedayPreferences namedayPreferences) { - this.contactProvider = contactProvider; + private PeopleEventsProvider(ContactsProvider contactsProvider, + ContentResolver resolver, + NamedayPreferences namedayPreferences, + PeopleNamedaysCalculator peopleNamedaysCalculator, CustomEventProvider customEventProvider) { + this.contactsProvider = contactsProvider; this.resolver = resolver; this.namedayPreferences = namedayPreferences; + this.peopleNamedaysCalculator = peopleNamedaysCalculator; + this.customEventProvider = customEventProvider; + } + + public List getCelebrationDateOn(Date date) { + TimePeriod timeDuration = TimePeriod.between(date, date); + List contactEvents = fetchStaticEventsBetween(timeDuration); + + if (namedayPreferences.isEnabled()) { + List namedaysContactEvents = peopleNamedaysCalculator.loadSpecialNamedaysBetween(timeDuration); + contactEvents.addAll(namedaysContactEvents); + } + return contactEvents; + + } + + public List getCelebrationDateFor(TimePeriod timeDuration) { + List contactEvents = fetchStaticEventsBetween(timeDuration); + + if (namedayPreferences.isEnabled()) { + List namedaysContactEvents = peopleNamedaysCalculator.loadSpecialNamedaysBetween(timeDuration); + contactEvents.addAll(namedaysContactEvents); + } + return Collections.unmodifiableList(contactEvents); + } + + private List fetchStaticEventsBetween(TimePeriod timeDuration) { + List contactEvents = new ArrayList<>(); + Cursor cursor = queryEventsFor(timeDuration); + throwIfInvalid(cursor); + while (cursor.moveToNext()) { + try { + ContactEvent contactEvent = getContactEventFrom(cursor); + contactEvents.add(contactEvent); + } catch (ContactNotFoundException e) { + Log.w(e); + } + } + cursor.close(); + return contactEvents; + } + + private Cursor queryEventsFor(TimePeriod timeDuration) { + if (isWithinTheSameYear(timeDuration)) { + return queryPeopleEvents(timeDuration, PeopleEvents.DATE + " ASC"); + } else { + return queryForBothYearsIn(timeDuration); + } + } + + private Cursor queryPeopleEvents(TimePeriod timePeriod, String sortOrder) { + String[] selectArgs = new String[]{ + SQLArgumentBuilder.dateWithoutYear(timePeriod.getStartingDate()), + SQLArgumentBuilder.dateWithoutYear(timePeriod.getEndingDate()), + }; + + Cursor cursor = resolver.query( + PeopleEvents.CONTENT_URI, + PROJECTION, + DATE_BETWEEN_IGNORING_YEAR, + selectArgs, + sortOrder + ); + if (isInvalid(cursor)) { + ErrorTracker.track(new IllegalStateException("People Events returned invalid cursor")); + } + return cursor; + } + + private Cursor queryForBothYearsIn(TimePeriod timeDuration) { + TimePeriod firstHalf = firstHalfOf(timeDuration); + Cursor[] cursors = new Cursor[2]; + cursors[0] = queryPeopleEvents(firstHalf, PeopleEvents.DATE + " ASC"); + TimePeriod secondHalf = secondHalfOf(timeDuration); + cursors[1] = queryPeopleEvents(secondHalf, PeopleEvents.DATE + " ASC"); + return new MergeCursor(cursors); + } + + private static TimePeriod firstHalfOf(TimePeriod timeDuration) { + return TimePeriod.between( + timeDuration.getStartingDate(), + Date.endOfYear(timeDuration.getStartingDate().getYear()) + ); + } + + private static TimePeriod secondHalfOf(TimePeriod timeDuration) { + return TimePeriod.between( + Date.startOfTheYear(timeDuration.getEndingDate().getYear()), + timeDuration.getEndingDate() + ); + } + + private boolean isWithinTheSameYear(TimePeriod timeDuration) { + return timeDuration.getStartingDate().getYear() == timeDuration.getEndingDate().getYear(); + } + + private ContactEvent getContactEventFrom(Cursor cursor) throws ContactNotFoundException { + long contactId = getContactIdFrom(cursor); + Contact contact = contactsProvider.getOrCreateContact(contactId); + Date date = getDateFrom(cursor); + EventType eventType = getEventType(cursor); + + Optional eventId = getDeviceEventIdFrom(cursor); + return new ContactEvent(eventId, eventType, date, contact); } public ContactEvents getCelebrationsClosestTo(Date date) { @@ -50,26 +186,27 @@ public ContactEvents getCelebrationsClosestTo(Date date) { return getCelebrationDateFor(closestDate); } - public ContactEvents getCelebrationDateFor(Date date) { + ContactEvents getCelebrationDateFor(Date date) { List contactEvents = new ArrayList<>(); Cursor cursor = resolver.query( - PeopleEventsContract.PeopleEvents.CONTENT_URI, + PeopleEvents.CONTENT_URI, null, getSelection(), getSelectArgs(date), - PeopleEventsContract.PeopleEvents.CONTACT_ID + PeopleEvents.CONTACT_ID ); if (isInvalid(cursor)) { throw new DeveloperError("Cursor was invalid"); } while (cursor.moveToNext()) { - long contactId = PeopleEventsContract.PeopleEvents.getContactIdFrom(cursor); + long contactId = getContactIdFrom(cursor); try { - Contact contact = contactProvider.getOrCreateContact(contactId); - EventType eventType = PeopleEventsContract.PeopleEvents.getEventType(cursor); + Contact contact = contactsProvider.getOrCreateContact(contactId); + EventType eventType = getEventType(cursor); + Optional deviceEventId = getDeviceEventIdFrom(cursor); - ContactEvent event = new ContactEvent(eventType, date, contact); + ContactEvent event = new ContactEvent(deviceEventId, eventType, date, contact); contactEvents.add(event); } catch (Exception e) { ErrorTracker.track(e); @@ -87,7 +224,7 @@ private Date findClosestDateTo(Date date) { Date dateFrom; if (cursor.moveToFirst()) { - dateFrom = PeopleEventsContract.PeopleEvents.getDateFrom(cursor); + dateFrom = getDateFrom(cursor); } else { dateFrom = date; } @@ -95,43 +232,33 @@ private Date findClosestDateTo(Date date) { return dateFrom; } - private static final Uri PEOPLE_EVENTS = PeopleEventsContract.PeopleEvents.CONTENT_URI; + private static final Uri PEOPLE_EVENTS = PeopleEvents.CONTENT_URI; private Cursor queryDateClosestTo(Date date) { return resolver.query( PEOPLE_EVENTS, - PEOPLE_PROJECTION, whereDateIsEqualOrAfter(), thePassing(date), onlyTheFirstMatch() + PEOPLE_PROJECTION, + PeopleEvents.DATE + " >= ?", + thePassing(date), + PeopleEvents.DATE + " ASC LIMIT 1" ); } - @NonNull - private String onlyTheFirstMatch() { - return PeopleEventsContract.PeopleEvents.DATE + " ASC" + " LIMIT 1"; - } - private String[] thePassing(Date date) { return new String[]{ date.toShortDate() }; } - private String whereDateIsEqualOrAfter() { - return PeopleEventsContract.PeopleEvents.DATE + " >= ?"; - } - - private boolean isInvalid(Cursor cursor) { - return cursor == null || cursor.isClosed(); - } - private String[] getSelectArgs(Date date) { return new String[]{date.toShortDate()}; } private String getSelection() { if (namedaysAreEnabled()) { - return PeopleEventsQuery.Date.SELECT; + return PeopleEventsQuery.SELECT; } else { - return PeopleEventsQuery.Date.SELECT_ONLY_BIRTHDAYS; + return PeopleEventsQuery.SELECT_ONLY_BIRTHDAYS; } } @@ -139,67 +266,64 @@ private boolean namedaysAreEnabled() { return namedayPreferences.isEnabled(); } - public List getCelebrationDateFor(LoadingTimeDuration timeDuration) { - List contactEvents = new ArrayList<>(); - Cursor cursor = queryPeopleEvents(timeDuration.getFrom(), timeDuration.getTo()); - throwIfInvalid(cursor); - - while (cursor.moveToNext()) { - try { - ContactEvent contactEvent = getContactEventFrom(cursor); - contactEvents.add(contactEvent); - } catch (ContactNotFoundException e) { - Log.w(e); - } - } - - cursor.close(); - return contactEvents; - } - - private Cursor queryPeopleEvents(Date startingDate, Date endingDate) { - String select = PeopleEventsContract.PeopleEvents.DATE + " >= ? AND " + PeopleEventsContract.PeopleEvents.DATE + " <=?"; - String[] selectArgs = new String[]{ - startingDate.toShortDate(), - endingDate.toShortDate() - }; - - Cursor cursor = resolver.query( - PeopleEventsContract.PeopleEvents.CONTENT_URI, - PROJECTION, - select, - selectArgs, - PeopleEventsContract.PeopleEvents.DATE + " ASC" - ); + private static void throwIfInvalid(Cursor cursor) { if (isInvalid(cursor)) { - ErrorTracker.track(new IllegalStateException("People Events returned invalid cursor")); + throw new RuntimeException("Invalid cursor"); } - return cursor; } - private static final String[] PROJECTION = { - PeopleEventsContract.PeopleEvents.CONTACT_ID, - PeopleEventsContract.PeopleEvents.SOURCE, + private static boolean isInvalid(Cursor cursor) { + return cursor == null || cursor.isClosed(); + } - PeopleEventsContract.PeopleEvents.DATE, + private static Date getDateFrom(Cursor cursor) { + int index = cursor.getColumnIndexOrThrow(PeopleEventsContract.PeopleEvents.DATE); + String text = cursor.getString(index); + return from(text); + } - PeopleEventsContract.PeopleEvents.EVENT_TYPE, - }; + private static long getContactIdFrom(Cursor cursor) { + int contactIdIndex = cursor.getColumnIndexOrThrow(PeopleEvents.CONTACT_ID); + return cursor.getLong(contactIdIndex); + } - private ContactEvent getContactEventFrom(Cursor cursor) throws ContactNotFoundException { + private EventType getEventType(Cursor cursor) { + int eventTypeIndex = cursor.getColumnIndexOrThrow(PeopleEvents.EVENT_TYPE); + @EventTypeId int rawEventType = cursor.getInt(eventTypeIndex); + if (rawEventType == EventColumns.TYPE_CUSTOM) { + Optional deviceEventIdFrom = getDeviceEventIdFrom(cursor); + if (deviceEventIdFrom.isPresent()) { + return queryCustomEvent(deviceEventIdFrom.get()); + } + return StandardEventType.OTHER; + } + return StandardEventType.fromId(rawEventType); + } - long contactId = PeopleEventsContract.PeopleEvents.getContactIdFrom(cursor); - Contact contact = contactProvider.getOrCreateContact(contactId); - Date date = PeopleEventsContract.PeopleEvents.getDateFrom(cursor); - EventType eventType = PeopleEventsContract.PeopleEvents.getEventType(cursor); + private EventType queryCustomEvent(long deviceId) { + return customEventProvider.getEventWithId(deviceId); + } - return new ContactEvent(eventType, date, contact); + public static Date from(String text) { + try { + return DateParser.INSTANCE.parse(text); + } catch (DateParseException e) { + e.printStackTrace(); + throw new DeveloperError("Invalid date stored to database. [" + text + "]"); + } } - private void throwIfInvalid(Cursor cursor) { - if (isInvalid(cursor)) { - throw new RuntimeException("Invalid cursor"); + private static Optional getDeviceEventIdFrom(Cursor cursor) { + int eventId = cursor.getColumnIndexOrThrow(PeopleEvents.DEVICE_EVENT_ID); + long deviceEventId = cursor.getLong(eventId); + if (isALegitEventId(deviceEventId)) { + return Optional.absent(); } + return new Optional<>(deviceEventId); + } + + private static boolean isALegitEventId(long deviceEventId) { + return deviceEventId == -1; } } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/AnnualDate.java b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/AnnualDate.java new file mode 100644 index 00000000..788bd215 --- /dev/null +++ b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/AnnualDate.java @@ -0,0 +1,33 @@ +package com.alexstyl.specialdates.upcoming; + +import com.alexstyl.specialdates.date.Date; + +final class AnnualDate { + private final Date date; + + AnnualDate(Date date) { + this.date = date; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + AnnualDate that = (AnnualDate) o; + + return date.getMonth() == that.date.getMonth() && + date.getDayOfMonth() == that.date.getDayOfMonth(); + } + + @Override + public int hashCode() { + int result = date.getDayOfMonth(); + result = 31 * result + date.getMonth(); + return result; + } +} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/LoadingTimeDuration.java b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/LoadingTimeDuration.java deleted file mode 100644 index 977e5a1c..00000000 --- a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/LoadingTimeDuration.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.alexstyl.specialdates.upcoming; - -import com.alexstyl.specialdates.date.Date; - -import java.io.Serializable; - -public class LoadingTimeDuration implements Serializable { - - private Date from; - private Date to; - - public LoadingTimeDuration(Date from, Date to) { - this.from = from; - this.to = to; - } - - public Date getFrom() { - return from; - } - - public Date getTo() { - return to; - } - -} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/MonthLabels.java b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/MonthLabels.java index de2b09fa..149c2477 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/MonthLabels.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/MonthLabels.java @@ -1,5 +1,7 @@ package com.alexstyl.specialdates.upcoming; +import com.alexstyl.specialdates.date.MonthInt; + import java.text.DateFormatSymbols; import java.util.Arrays; import java.util.Locale; @@ -18,18 +20,15 @@ public static MonthLabels forLocale(Locale locale) { this.monthLabels = monthLabels; } - public String getMonthOfYear(int monthPosition) { - checkArgument(monthPosition); + public String getMonthOfYear(@MonthInt int monthPosition) { return monthLabels[monthPosition - 1]; } - private void checkArgument(int month) { - if (month < 1 || month > 12) { - throw new IllegalArgumentException("There are only 12 months in a year. [" + month + "] is not one of them"); - } - } - public String[] getMonthsOfYear() { return Arrays.copyOf(monthLabels, monthLabels.length); } + + public String getFullLabel(@MonthInt int month) { + return "0"; + } } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/MonthOfYear.java b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/MonthOfYear.java index d63832d9..f2b1a583 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/MonthOfYear.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/MonthOfYear.java @@ -1,9 +1,10 @@ package com.alexstyl.specialdates.upcoming; import com.alexstyl.specialdates.date.CelebrationDate; +import com.alexstyl.specialdates.date.MonthInt; public class MonthOfYear { - + @MonthInt private final int month; private final int year; @@ -11,11 +12,12 @@ public static MonthOfYear of(CelebrationDate date) { return new MonthOfYear(date.getMonth(), date.getYear()); } - public MonthOfYear(int month, int year) { + private MonthOfYear(int month, int year) { this.month = month; this.year = year; } + @MonthInt public int getMonth() { return month; } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/TimePeriod.java b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/TimePeriod.java new file mode 100644 index 00000000..850dcf58 --- /dev/null +++ b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/TimePeriod.java @@ -0,0 +1,45 @@ +package com.alexstyl.specialdates.upcoming; + +import com.alexstyl.specialdates.date.Date; +import com.alexstyl.specialdates.date.DateComparator; + +public class TimePeriod { + + private final Date from; + private final Date to; + + public static TimePeriod between(Date startDate, Date endDate) { + if (DateComparator.INSTANCE.compare(startDate, endDate) > 0) { + throw new IllegalArgumentException("starting Date was after end Date"); + } + return new TimePeriod(startDate, endDate); + } + + private TimePeriod(Date from, Date to) { + this.from = from; + this.to = to; + } + + public Date getStartingDate() { + return from; + } + + public Date getEndingDate() { + return to; + } + + public boolean containsDate(Date date) { + return (DateComparator.INSTANCE.compare(from, date) <= 0 + && + DateComparator.INSTANCE.compare(date, to) <= 0 + ); + } + + @Override + public String toString() { + return "TimePeriod{" + + "from=" + from + + ", to=" + to + + '}'; + } +} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/UpcomingDatesBuilder.java b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/UpcomingDatesBuilder.java index 642cfa66..acb12d08 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/UpcomingDatesBuilder.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/UpcomingDatesBuilder.java @@ -15,56 +15,33 @@ import java.util.HashMap; import java.util.List; -class UpcomingDatesBuilder { +final class UpcomingDatesBuilder { - private static final List NO_CONTACT_EVENTS = Collections.unmodifiableList(Collections.emptyList()); + private static final List NO_CELEBRATIONS = Collections.emptyList(); + private static final List NO_CONTACT_EVENTS = Collections.emptyList(); + private static final DateComparator COMPARATOR = DateComparator.INSTANCE; - private final DateComparator comparator = DateComparator.get(); - private final HashMapList contactEvents = new HashMapList<>(); - private final HashMap namedays = new HashMap<>(); - private final HashMap bankHolidays = new HashMap<>(); + private final HashMapList contactEvents = new HashMapList<>(); + private final HashMap namedays = new HashMap<>(); + private final HashMap bankHolidays = new HashMap<>(); + private final TimePeriod duration; - private Optional earliestDate = Optional.absent(); - private Optional latestDate = Optional.absent(); + UpcomingDatesBuilder(TimePeriod duration) { + this.duration = duration; + } UpcomingDatesBuilder withContactEvents(List contactEvents) { for (ContactEvent contactEvent : contactEvents) { Date date = contactEvent.getDate(); - keepIfEarliestDate(date); - keepIfLatestDate(date); - this.contactEvents.addValue(date, contactEvent); + this.contactEvents.addValue(new AnnualDate(date), contactEvent); } return this; } - private void keepIfLatestDate(Date date) { - if (latestDate.isPresent()) { - Date previousLatestDate = latestDate.get(); - if (comparator.compare(date, previousLatestDate) > 0) { - latestDate = new Optional<>(date); - } - } else { - latestDate = new Optional<>(date); - } - } - - private void keepIfEarliestDate(Date date) { - if (earliestDate.isPresent()) { - Date previousEarliestDate = earliestDate.get(); - if (comparator.compare(date, previousEarliestDate) < 0) { - earliestDate = new Optional<>(date); - } - } else { - earliestDate = new Optional<>(date); - } - } - UpcomingDatesBuilder withNamedays(List namedays) { for (NamesInADate nameday : namedays) { Date date = nameday.getDate(); - keepIfEarliestDate(date); - keepIfLatestDate(date); - this.namedays.put(date, nameday); + this.namedays.put(new AnnualDate(date), nameday); } return this; } @@ -72,36 +49,30 @@ UpcomingDatesBuilder withNamedays(List namedays) { UpcomingDatesBuilder withBankHolidays(List bankHolidays) { for (BankHoliday bankHoliday : bankHolidays) { Date date = bankHoliday.getDate(); - keepIfEarliestDate(date); - keepIfLatestDate(date); - this.bankHolidays.put(date, bankHoliday); + this.bankHolidays.put(new AnnualDate(date), bankHoliday); } return this; } - private static final List NO_CELEBRATIONS = Collections.emptyList(); - public List build() { if (noEventsArePresent()) { return NO_CELEBRATIONS; } List celebrationDates = new ArrayList<>(); - Date indexDate = earliestDate.get(); - Date lastDate = latestDate.get(); - while (comparator.compare(indexDate, lastDate) <= 0) { - List contactEvent = contactEvents.get(indexDate); - if (contactEvent == null) { - contactEvent = NO_CONTACT_EVENTS; - } - NamesInADate namedays = this.namedays.get(indexDate); - - BankHoliday bankHoliday = bankHolidays.get(indexDate); - if (atLeastOneEventExists(contactEvent, namedays, bankHoliday)) { + Date indexDate = duration.getStartingDate(); + Date lastDate = duration.getEndingDate(); + + while (COMPARATOR.compare(indexDate, lastDate) <= 0) { + AnnualDate annualDate = new AnnualDate(indexDate); + List contactEvent = getEventsOn(annualDate); + NamesInADate namesOnDate = namedays.get(annualDate); + BankHoliday bankHoliday = bankHolidays.get(annualDate); + if (atLeastOneEventExists(contactEvent, namesOnDate, bankHoliday)) { CelebrationDate date = new CelebrationDate( indexDate, ContactEvents.createFrom(indexDate, contactEvent), - new Optional<>(namedays), + new Optional<>(namesOnDate), new Optional<>(bankHoliday) ); celebrationDates.add(date); @@ -111,8 +82,17 @@ public List build() { return celebrationDates; } + private List getEventsOn(AnnualDate indexDate) { + List contactEvent = contactEvents.get(indexDate); + if (contactEvent == null) { + return NO_CONTACT_EVENTS; + } else { + return contactEvent; + } + } + private boolean noEventsArePresent() { - return !earliestDate.isPresent() || !latestDate.isPresent(); + return contactEvents.isEmpty() && namedays.isEmpty() && bankHolidays.isEmpty(); } private boolean atLeastOneEventExists(List contactEvent, NamesInADate namedays, BankHoliday bankHoliday) { diff --git a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/UpcomingEventsFetcher.java b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/UpcomingEventsFetcher.java index 9ac49539..488d80c5 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/UpcomingEventsFetcher.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/UpcomingEventsFetcher.java @@ -6,6 +6,11 @@ import android.support.v4.content.Loader; import com.alexstyl.specialdates.date.CelebrationDate; +import com.alexstyl.specialdates.date.Date; +import com.alexstyl.specialdates.events.bankholidays.BankHolidayProvider; +import com.alexstyl.specialdates.events.bankholidays.GreekBankHolidaysCalculator; +import com.alexstyl.specialdates.events.namedays.calendar.OrthodoxEasterCalculator; +import com.alexstyl.specialdates.events.namedays.calendar.resource.NamedayCalendarProvider; import com.alexstyl.specialdates.service.PeopleEventsProvider; import com.novoda.notils.exception.DeveloperError; @@ -13,32 +18,39 @@ class UpcomingEventsFetcher { - private static final String KEY_LOADING_TIME = "alexstyl:loading_time"; private static final int LOADER_ID_DATES = 2; private final LoaderManager loaderManager; - private Callback callback; + private final Date startingDate; private final Context context; + private Callback callback; - UpcomingEventsFetcher(LoaderManager loaderManager, Context context) { + UpcomingEventsFetcher(LoaderManager loaderManager, Context context, Date startingDate) { this.loaderManager = loaderManager; this.context = context; + this.startingDate = startingDate; } - public void loadDatesBetween(LoadingTimeDuration duration, Callback callback) { + void loadDatesStartingFromDate(Callback callback) { this.callback = callback; - Bundle args = new Bundle(); - args.putSerializable(KEY_LOADING_TIME, duration); - this.loaderManager.restartLoader(LOADER_ID_DATES, args, loaderCallbacks); + this.loaderManager.restartLoader(LOADER_ID_DATES, null, loaderCallbacks); } private final LoaderManager.LoaderCallbacks> loaderCallbacks = new LoaderManager.LoaderCallbacks>() { @Override public Loader> onCreateLoader(int loaderID, Bundle arg1) { - LoadingTimeDuration duration = (LoadingTimeDuration) arg1.getSerializable(KEY_LOADING_TIME); if (loaderID == LOADER_ID_DATES) { - return new UpcomingEventsLoader(context, PeopleEventsProvider.newInstance(context), duration); + PeopleEventsProvider peopleEventsProvider = PeopleEventsProvider.newInstance(context); + NamedayCalendarProvider namedayCalendarProvider = NamedayCalendarProvider.newInstance(context.getResources()); + BankHolidayProvider bankHolidayProvider = new BankHolidayProvider(new GreekBankHolidaysCalculator(OrthodoxEasterCalculator.INSTANCE)); + return new UpcomingEventsLoader( + context, + startingDate, + peopleEventsProvider, + bankHolidayProvider, + namedayCalendarProvider + ); } throw new DeveloperError("Unhandled loaderID: " + loaderID); } 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 32d2dd60..1100314a 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/UpcomingEventsFragment.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/UpcomingEventsFragment.java @@ -29,7 +29,6 @@ import com.alexstyl.specialdates.ui.base.MementoFragment; 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; import java.util.List; @@ -75,7 +74,6 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa @Override public void onViewCreated(View view, Bundle savedInstanceState) { - new FabPaddingSetter().setBottomPaddingTo(upcomingEventsListView); upcomingEventsListView.setHasFixedSize(true); } 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 40d6b5ca..456070a3 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/UpcomingEventsLoader.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/UpcomingEventsLoader.java @@ -6,16 +6,14 @@ import com.alexstyl.specialdates.date.CelebrationDate; import com.alexstyl.specialdates.date.ContactEvent; -import com.alexstyl.specialdates.date.DateComparator; import com.alexstyl.specialdates.date.Date; +import com.alexstyl.specialdates.date.DateComparator; import com.alexstyl.specialdates.events.bankholidays.BankHoliday; +import com.alexstyl.specialdates.events.bankholidays.BankHolidayProvider; 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.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.resource.NamedayCalendarProvider; import com.alexstyl.specialdates.service.PeopleEventsProvider; @@ -26,24 +24,32 @@ import java.util.Collections; import java.util.List; -public class UpcomingEventsLoader extends SimpleAsyncTaskLoader> { +class UpcomingEventsLoader extends SimpleAsyncTaskLoader> { + + private static final DateComparator COMPARATOR = DateComparator.INSTANCE; private final PeopleEventsProvider peopleEventsProvider; private final NamedayPreferences namedayPreferences; + private final Date startingPeriod; private final ContactsObserver contactsObserver; - private final LoadingTimeDuration timeDuration; - private final EasterCalculator easterCalculator = new EasterCalculator(); - - private final int currentYear; - private final DateComparator comparator = DateComparator.get(); - - public UpcomingEventsLoader(Context context, PeopleEventsProvider peopleEventsProvider, LoadingTimeDuration timeDuration) { + private final BankHolidaysPreferences bankHolidaysPreferences; + private final BankHolidayProvider bankHolidayProvider; + private final NamedayCalendarProvider namedayCalendarProvider; + + UpcomingEventsLoader(Context context, + Date startingPeriod, + PeopleEventsProvider peopleEventsProvider, + BankHolidayProvider bankHolidayProvider, + NamedayCalendarProvider namedayCalendarProvider + ) { super(context); this.peopleEventsProvider = peopleEventsProvider; - this.timeDuration = timeDuration; - this.currentYear = timeDuration.getFrom().getYear(); this.namedayPreferences = NamedayPreferences.newInstance(context); + this.startingPeriod = startingPeriod; + this.bankHolidayProvider = bankHolidayProvider; + this.namedayCalendarProvider = namedayCalendarProvider; this.contactsObserver = new ContactsObserver(getContentResolver(), new Handler()); + this.bankHolidaysPreferences = BankHolidaysPreferences.newInstance(getContext()); setupContactsObserver(); } @@ -55,45 +61,45 @@ protected void onUnregisterObserver() { @Override public List loadInBackground() { + TimePeriod timePeriod = TimePeriod.between( + startingPeriod, + startingPeriod.addDay(364) + ); + List celebrationDates = calculateEventsBetween(timePeriod); + Collections.sort(celebrationDates); + return celebrationDates; + } - List contactEvents = peopleEventsProvider.getCelebrationDateFor(timeDuration); - - BankHolidaysPreferences bankHolidaysPreferences = BankHolidaysPreferences.newInstance(getContext()); - UpcomingDatesBuilder upcomingDatesBuilder = new UpcomingDatesBuilder() + private List calculateEventsBetween(TimePeriod period) { + List contactEvents = peopleEventsProvider.getCelebrationDateFor(period); + UpcomingDatesBuilder upcomingDatesBuilder = new UpcomingDatesBuilder(period) .withContactEvents(contactEvents); - if (bankHolidaysPreferences.isEnabled()) { - BankholidayCalendar.get(); - Date easter = easterCalculator.calculateEasterForYear(currentYear); - List bankHolidays = new GreekBankHolidays(easter).getBankHolidaysForYear(); + if (shouldLoadBankHolidays()) { + List bankHolidays = bankHolidayProvider.calculateBankHolidaysBetween(period); upcomingDatesBuilder.withBankHolidays(bankHolidays); } - if (includeNamedays()) { - List namedays = getNamedaysFor(timeDuration); + if (shouldLoadNamedays()) { + List namedays = calculateNamedaysBetween(period); upcomingDatesBuilder.withNamedays(namedays); } - - List allDates = upcomingDatesBuilder.build(); - - Collections.sort(allDates); - return allDates; + return upcomingDatesBuilder.build(); } - private boolean includeNamedays() { - return namedayPreferences.isEnabled() && !namedayPreferences.isEnabledForContactsOnly(); + private boolean shouldLoadBankHolidays() { + return bankHolidaysPreferences.isEnabled(); } - private List getNamedaysFor(LoadingTimeDuration timeDuration) { + private List calculateNamedaysBetween(TimePeriod timeDuration) { NamedayLocale selectedLanguage = namedayPreferences.getSelectedLanguage(); - NamedayCalendarProvider namedayCalendarProvider = NamedayCalendarProvider.newInstance(getContext().getResources()); - NamedayCalendar namedayCalendar = namedayCalendarProvider.loadNamedayCalendarForLocale(selectedLanguage, currentYear); + NamedayCalendar namedayCalendar = namedayCalendarProvider.loadNamedayCalendarForLocale(selectedLanguage, timeDuration.getStartingDate().getYear()); - Date indexDate = timeDuration.getFrom(); - Date toDate = timeDuration.getTo(); + Date indexDate = timeDuration.getStartingDate(); + Date toDate = timeDuration.getEndingDate(); List namedays = new ArrayList<>(); - while (comparator.compare(indexDate, toDate) < 0) { + while (COMPARATOR.compare(indexDate, toDate) < 0) { NamesInADate allNamedayOn = namedayCalendar.getAllNamedayOn(indexDate); if (allNamedayOn.nameCount() > 0) { namedays.add(allNamedayOn); @@ -103,6 +109,10 @@ private List getNamedaysFor(LoadingTimeDuration timeDuration) { return namedays; } + private boolean shouldLoadNamedays() { + return namedayPreferences.isEnabled() && !namedayPreferences.isEnabledForContactsOnly(); + } + private void setupContactsObserver() { contactsObserver.registerWith( new ContactsObserver.Callback() { diff --git a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/UpcomingEventsProvider.java b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/UpcomingEventsProvider.java index 835f8a5e..0c87e9b5 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/upcoming/UpcomingEventsProvider.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/upcoming/UpcomingEventsProvider.java @@ -8,33 +8,24 @@ import java.util.ArrayList; import java.util.List; -public class UpcomingEventsProvider { +class UpcomingEventsProvider { private final UpcomingEventsFetcher fetcher; - private final LoadingTimeDuration displayingDuration; private LoadingListener listener; static UpcomingEventsProvider newInstance(FragmentActivity activity, LoadingListener onEventsLoadedListener) { - UpcomingEventsFetcher upcomingEventsFetcher = new UpcomingEventsFetcher(activity.getSupportLoaderManager(), activity); - return new UpcomingEventsProvider(upcomingEventsFetcher, startingTimeDuration(), onEventsLoadedListener); + UpcomingEventsFetcher upcomingEventsFetcher = new UpcomingEventsFetcher(activity.getSupportLoaderManager(), activity, Date.today()); + return new UpcomingEventsProvider(upcomingEventsFetcher, onEventsLoadedListener); } - public UpcomingEventsProvider(UpcomingEventsFetcher fetcher, LoadingTimeDuration initialDuration, LoadingListener listener) { + private UpcomingEventsProvider(UpcomingEventsFetcher fetcher, LoadingListener listener) { this.fetcher = fetcher; - this.displayingDuration = initialDuration; this.listener = listener; } - private static LoadingTimeDuration startingTimeDuration() { - int year = Date.CURRENT_YEAR; - Date startOfLastMonth = Date.startOfTheYear(year); - Date endingOfNextMonth = Date.endOfYear(year); - return new LoadingTimeDuration(startOfLastMonth, endingOfNextMonth); - } - - public void reloadData() { - fetcher.loadDatesBetween(displayingDuration, callback); + void reloadData() { + fetcher.loadDatesStartingFromDate(callback); } private final UpcomingEventsFetcher.Callback callback = new UpcomingEventsFetcher.Callback() { 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 38d17493..de140fde 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,7 +10,7 @@ import com.alexstyl.specialdates.upcoming.MonthLabels; import com.alexstyl.specialdates.upcoming.MonthOfYear; -public final class MonthHeaderViewHolder extends RecyclerView.ViewHolder { +final class MonthHeaderViewHolder extends RecyclerView.ViewHolder { private final MonthLabels monthLabels; private final TextView header; @@ -20,17 +20,13 @@ public static MonthHeaderViewHolder createFor(ViewGroup parent, MonthLabels mont return new MonthHeaderViewHolder(view, monthLabels); } - public MonthHeaderViewHolder(View convertView, MonthLabels monthLabels) { + private MonthHeaderViewHolder(View convertView, MonthLabels monthLabels) { super(convertView); this.monthLabels = monthLabels; this.header = (TextView) convertView.findViewById(android.R.id.text1); } - public void displayMonth(MonthOfYear monthOfYear, int currentYear) { - if (monthOfYear.getYear() == currentYear) { - header.setText(monthLabels.getMonthOfYear(monthOfYear.getMonth())); - } else { - header.setText(String.valueOf(monthOfYear.getYear())); - } + void displayMonth(MonthOfYear monthOfYear) { + header.setText(monthLabels.getMonthOfYear(monthOfYear.getMonth())); } } 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 4670802b..7b663f60 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 @@ -87,7 +87,7 @@ public int getItemViewType(int position) { private void bindMonthViewHolder(MonthHeaderViewHolder viewHolder, int position) { MonthOfYear monthOfYear = headers.get(position); - viewHolder.displayMonth(monthOfYear, today.getYear()); + viewHolder.displayMonth(monthOfYear); } private void bindContactView(UpcomingEventsViewHolder viewHolder, int position) { @@ -99,11 +99,9 @@ private boolean isUpcomingViewType(int type) { return type == VIEWTYPE_DAY_EVENTS; } - public CelebrationDate getCelebrationAtPosition(int position) { - Integer a = positionToCelebration.get(position); - - return celebrationDates.get(a); - + private CelebrationDate getCelebrationAtPosition(int position) { + Integer index = positionToCelebration.get(position); + return celebrationDates.get(index); } @Override @@ -191,7 +189,7 @@ private boolean closestDayIsUnknown() { } private boolean isOnOrAfterToday(CelebrationDate event) { - return DateComparator.get().compare(event.getDate(), today) >= 0; + return DateComparator.INSTANCE.compare(event.getDate(), today) >= 0; } private boolean containsNoCelebration() { diff --git a/mobile/src/main/java/com/alexstyl/specialdates/util/DateParser.java b/mobile/src/main/java/com/alexstyl/specialdates/util/DateParser.java index 0e291c62..22168f2f 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/util/DateParser.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/util/DateParser.java @@ -12,7 +12,8 @@ import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; -public class DateParser { +public enum DateParser { + INSTANCE; private static Locale[] LOCALES; diff --git a/mobile/src/main/java/com/alexstyl/specialdates/util/HashMapList.java b/mobile/src/main/java/com/alexstyl/specialdates/util/HashMapList.java index ac4a0619..a33adeb2 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/util/HashMapList.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/util/HashMapList.java @@ -20,4 +20,8 @@ public boolean addValue(K key, V value) { public List get(K key) { return map.get(key); } + + public boolean isEmpty() { + return map.isEmpty(); + } } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/views/FabPaddingSetter.java b/mobile/src/main/java/com/alexstyl/specialdates/views/FabPaddingSetter.java deleted file mode 100644 index c4398d97..00000000 --- a/mobile/src/main/java/com/alexstyl/specialdates/views/FabPaddingSetter.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.alexstyl.specialdates.views; - - -import android.content.res.Resources; -import android.view.View; - -import com.alexstyl.specialdates.R; - -public class FabPaddingSetter { - - public void setBottomPaddingTo(View view) { - // set padding so that we don't cover the FAB doesn't cover the last row - Resources resources = view.getResources(); - int fabHeight = resources.getDimensionPixelSize(R.dimen.fab_size_normal) - + resources.getDimensionPixelSize(R.dimen.fab_margin); - - view.setPadding(view.getPaddingLeft(), view.getPaddingTop(), - view.getPaddingRight(), view.getPaddingBottom() + fabHeight); - - } -} diff --git a/mobile/src/main/java/com/alexstyl/specialdates/widgetprovider/QueryUpcomingTask.java b/mobile/src/main/java/com/alexstyl/specialdates/widgetprovider/QueryUpcomingPeoplEventsTask.java similarity index 82% rename from mobile/src/main/java/com/alexstyl/specialdates/widgetprovider/QueryUpcomingTask.java rename to mobile/src/main/java/com/alexstyl/specialdates/widgetprovider/QueryUpcomingPeoplEventsTask.java index 4449fe59..2e3e7f9c 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/widgetprovider/QueryUpcomingTask.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/widgetprovider/QueryUpcomingPeoplEventsTask.java @@ -6,11 +6,11 @@ import com.alexstyl.specialdates.date.Date; import com.alexstyl.specialdates.service.PeopleEventsProvider; -public abstract class QueryUpcomingTask extends AsyncTask { +abstract class QueryUpcomingPeoplEventsTask extends AsyncTask { private final PeopleEventsProvider eventsProvider; - protected QueryUpcomingTask(PeopleEventsProvider eventsProvider) { + QueryUpcomingPeoplEventsTask(PeopleEventsProvider eventsProvider) { this.eventsProvider = eventsProvider; } diff --git a/mobile/src/main/java/com/alexstyl/specialdates/widgetprovider/TodayWidgetProvider.java b/mobile/src/main/java/com/alexstyl/specialdates/widgetprovider/TodayWidgetProvider.java index 391f32ee..caff459b 100644 --- a/mobile/src/main/java/com/alexstyl/specialdates/widgetprovider/TodayWidgetProvider.java +++ b/mobile/src/main/java/com/alexstyl/specialdates/widgetprovider/TodayWidgetProvider.java @@ -38,7 +38,7 @@ public void onReceive(Context context, Intent intent) { @Override public void onUpdate(final Context context, final AppWidgetManager appWidgetManager, final int[] appWidgetIds) { - new QueryUpcomingTask(PeopleEventsProvider.newInstance(context)) { + new QueryUpcomingPeoplEventsTask(PeopleEventsProvider.newInstance(context)) { @Override void onLoaded(ContactEvents contactEvents) { if (contactEvents.size() > 0) { diff --git a/mobile/src/main/res/layout/card_bankholiday.xml b/mobile/src/main/res/layout/card_bankholiday.xml index b07c3705..9aa93bda 100644 --- a/mobile/src/main/res/layout/card_bankholiday.xml +++ b/mobile/src/main/res/layout/card_bankholiday.xml @@ -1,4 +1,4 @@ - diff --git a/mobile/src/main/res/layout/fragment_upcoming_events.xml b/mobile/src/main/res/layout/fragment_upcoming_events.xml index 84952990..fd29d85c 100644 --- a/mobile/src/main/res/layout/fragment_upcoming_events.xml +++ b/mobile/src/main/res/layout/fragment_upcoming_events.xml @@ -1,8 +1,8 @@ diff --git a/mobile/src/main/res/values/colors.xml b/mobile/src/main/res/values/colors.xml index 6ab54dc0..f1f200e4 100644 --- a/mobile/src/main/res/values/colors.xml +++ b/mobile/src/main/res/values/colors.xml @@ -28,9 +28,11 @@ #9e1726 #ffff4444 + @color/avatar_variant_1 #ff33b5e5 #595959 + #6a1b9a #99ffffff #46000000 diff --git a/mobile/src/main/res/values/dimens.xml b/mobile/src/main/res/values/dimens.xml index 32701679..f72cabf9 100644 --- a/mobile/src/main/res/values/dimens.xml +++ b/mobile/src/main/res/values/dimens.xml @@ -64,7 +64,6 @@ 24dp 38dp 56dp - 16dp 38dp 18dp @@ -75,5 +74,8 @@ 40dp + + 16dp 48dp + 64dp diff --git a/mobile/src/main/res/values/gr_special_names.xml b/mobile/src/main/res/values/gr_special_names.xml deleted file mode 100644 index f1cc2214..00000000 --- a/mobile/src/main/res/values/gr_special_names.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - Χλόη - - - Γεώργιος - Γεωργής - Γιώργος - Γκόγκος - Γιώργης - Γιωργίτσης - Γεωργία - Γιωργία - Γεωργούλα - Γιωργίτσα - Γίτσα - - - - Μάρκος - Μαρκής - Μαρκία - Μαρκούλης - Μαρκούλ - - - - Ααρών - Αβραάμ - Αδάμ - Αδαμάντιος - Διαμαντής - Αδαμαντία - Διαμαντούλα - Διαμάντω - Δαβίδ - Δαυίδ - Δανάη - Δανιήλ - Δεβόρα - Εσθήρ - Εύα - Ισαάκ - Ιώβ - Νώε - Ραχήλ - Ρεβέκκα - Ρουμπίνη - Σάρα - Μελχισεδέ - - diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml new file mode 100644 index 00000000..75b07441 --- /dev/null +++ b/mobile/src/main/res/values/strings.xml @@ -0,0 +1,6 @@ + + + Anniversary + Other + Custom + diff --git a/mobile/src/test/java/com/alexstyl/specialdates/TestContact.java b/mobile/src/test/java/com/alexstyl/specialdates/TestContact.java index 34fc0495..0929bbb4 100644 --- a/mobile/src/test/java/com/alexstyl/specialdates/TestContact.java +++ b/mobile/src/test/java/com/alexstyl/specialdates/TestContact.java @@ -6,18 +6,13 @@ import com.alexstyl.specialdates.contact.Contact; import com.alexstyl.specialdates.contact.actions.LabeledAction; -import com.alexstyl.specialdates.date.Date; import java.util.List; public class TestContact extends Contact { public TestContact(long id, DisplayName displayName) { - super(id, displayName, Optional.absent()); - } - - public TestContact(long id, DisplayName displayName, Date birthday) { - super(id, displayName, new Optional<>(birthday)); + super(id, displayName); } @Override diff --git a/mobile/src/test/java/com/alexstyl/specialdates/contact/ContactCacheTest.java b/mobile/src/test/java/com/alexstyl/specialdates/contact/ContactCacheTest.java index 5b7d9eaa..b1686c0b 100644 --- a/mobile/src/test/java/com/alexstyl/specialdates/contact/ContactCacheTest.java +++ b/mobile/src/test/java/com/alexstyl/specialdates/contact/ContactCacheTest.java @@ -1,8 +1,6 @@ package com.alexstyl.specialdates.contact; import com.alexstyl.specialdates.DisplayName; -import com.alexstyl.specialdates.Optional; -import com.alexstyl.specialdates.date.Date; import org.junit.Before; import org.junit.Test; @@ -45,7 +43,7 @@ public void givenOneContactIsAdded_thenSameContactIsReturned() { } private static Contact anyContact() { - return new DeviceContact(5, ANY_DISPLAY_NAME, "any_lookup_key", Optional.absent()); + return new DeviceContact(5, ANY_DISPLAY_NAME, "any_lookup_key"); } } diff --git a/mobile/src/test/java/com/alexstyl/specialdates/contact/ContactTest.java b/mobile/src/test/java/com/alexstyl/specialdates/contact/ContactTest.java index d3efd8fe..51002bf0 100644 --- a/mobile/src/test/java/com/alexstyl/specialdates/contact/ContactTest.java +++ b/mobile/src/test/java/com/alexstyl/specialdates/contact/ContactTest.java @@ -5,9 +5,7 @@ import android.view.View; import com.alexstyl.specialdates.DisplayName; -import com.alexstyl.specialdates.Optional; import com.alexstyl.specialdates.contact.actions.LabeledAction; -import com.alexstyl.specialdates.date.Date; import com.novoda.notils.exception.DeveloperError; import java.util.List; @@ -34,8 +32,8 @@ public void toStringReturnsTheDisplayNameRepresentation() { private final class TestableContact extends Contact { - public TestableContact(long id, DisplayName displayName) { - super(id, displayName, Optional.absent()); + TestableContact(long id, DisplayName displayName) { + super(id, displayName); } @Override diff --git a/mobile/src/test/java/com/alexstyl/specialdates/date/ContactEventTest.java b/mobile/src/test/java/com/alexstyl/specialdates/date/ContactEventTest.java index 22a34880..9c445623 100644 --- a/mobile/src/test/java/com/alexstyl/specialdates/date/ContactEventTest.java +++ b/mobile/src/test/java/com/alexstyl/specialdates/date/ContactEventTest.java @@ -3,10 +3,11 @@ import android.content.res.Resources; import com.alexstyl.specialdates.DisplayName; +import com.alexstyl.specialdates.Optional; import com.alexstyl.specialdates.R; import com.alexstyl.specialdates.TestContact; import com.alexstyl.specialdates.contact.Contact; -import com.alexstyl.specialdates.events.peopleevents.EventType; +import com.alexstyl.specialdates.events.peopleevents.StandardEventType; import org.junit.Before; import org.junit.Test; @@ -21,8 +22,11 @@ @RunWith(MockitoJUnitRunner.class) public class ContactEventTest { - private static final Contact CONTACT_WITHOUT_BIRTHDAY = new TestContact(1, DisplayName.from("Peter")); - private static final Date SOME_DATE = Date.on(1, 1, 1990); + private static final Optional NO_DEVICE_ID = Optional.absent(); + private static final Contact ANY_CONTACT = new TestContact(1, DisplayName.from("Peter")); + private static final Date SOME_DATE = Date.on(1, JANUARY, 1990); + private static final Date SOME_DATE_WITHOUT_YEAR = Date.on(1, JANUARY); + private static final int CURRENT_YEAR = Date.CURRENT_YEAR; @Mock private Resources mockResources; @@ -37,51 +41,40 @@ public void setUp() throws Exception { @Test public void labelForNameday() { - ContactEvent contactEvent = new ContactEvent(EventType.NAMEDAY, SOME_DATE, CONTACT_WITHOUT_BIRTHDAY); + ContactEvent contactEvent = new ContactEvent(NO_DEVICE_ID, StandardEventType.NAMEDAY, SOME_DATE, ANY_CONTACT); String label = contactEvent.getLabel(mockResources); assertThat(label).isEqualTo("Nameday"); } @Test public void labelForBirthdayWithoutYear() { - ContactEvent contactEvent = new ContactEvent(EventType.BIRTHDAY, SOME_DATE, contactWithBirthdayOn(1, 1)); + ContactEvent contactEvent = new ContactEvent(NO_DEVICE_ID, StandardEventType.BIRTHDAY, SOME_DATE_WITHOUT_YEAR, ANY_CONTACT); String label = contactEvent.getLabel(mockResources); assertThat(label).isEqualTo("Birthday"); } @Test public void labelForBirthdayWithYearAfterDate() { - Date eventDate = Date.on(1, 1, 1990); - Contact contact = contactWithBirthdayOn(1, 1, 2016); - ContactEvent contactEvent = new ContactEvent(EventType.BIRTHDAY, eventDate, contact); + Date eventDate = Date.on(1, JANUARY, CURRENT_YEAR + 50); + ContactEvent contactEvent = new ContactEvent(NO_DEVICE_ID, StandardEventType.BIRTHDAY, eventDate, ANY_CONTACT); String label = contactEvent.getLabel(mockResources); assertThat(label).isEqualTo("Birthday"); } @Test public void labelForBirthdayWithYearOnDate() { - Date eventDate = Date.on(1, JANUARY, 1990); - Contact contact = contactWithBirthdayOn(1, JANUARY, 1990); - ContactEvent contactEvent = new ContactEvent(EventType.BIRTHDAY, eventDate, contact); + Date eventDate = Date.on(1, JANUARY, CURRENT_YEAR); + ContactEvent contactEvent = new ContactEvent(NO_DEVICE_ID, StandardEventType.BIRTHDAY, eventDate, ANY_CONTACT); String label = contactEvent.getLabel(mockResources); assertThat(label).isEqualTo("Birthday"); } @Test public void labelForBirthdayWithYearBeforeDate() { - Date eventDate = Date.on(1, 1, 2000); - Contact contact = contactWithBirthdayOn(1, 1, 1990); - ContactEvent contactEvent = new ContactEvent(EventType.BIRTHDAY, eventDate, contact); + Date eventDate = Date.on(1, JANUARY, CURRENT_YEAR - 10); + ContactEvent contactEvent = new ContactEvent(NO_DEVICE_ID, StandardEventType.BIRTHDAY, eventDate, ANY_CONTACT); String label = contactEvent.getLabel(mockResources); assertThat(label).isEqualTo("Turns 10"); } - private Contact contactWithBirthdayOn(int dayOfMonth, int month, int year) { - return new TestContact(1, DisplayName.from("Peter"), Date.on(dayOfMonth, month, year)); - } - - private Contact contactWithBirthdayOn(int day, int month) { - return new TestContact(1, DisplayName.from("Peter"), Date.on(day, month)); - } - } diff --git a/mobile/src/test/java/com/alexstyl/specialdates/events/ContactActionTest.java b/mobile/src/test/java/com/alexstyl/specialdates/events/ContactActionTest.java index e0065af2..9a7ce4f4 100644 --- a/mobile/src/test/java/com/alexstyl/specialdates/events/ContactActionTest.java +++ b/mobile/src/test/java/com/alexstyl/specialdates/events/ContactActionTest.java @@ -1,12 +1,13 @@ package com.alexstyl.specialdates.events; import com.alexstyl.specialdates.DisplayName; +import com.alexstyl.specialdates.Optional; import com.alexstyl.specialdates.TestContact; import com.alexstyl.specialdates.contact.Contact; import com.alexstyl.specialdates.date.ContactEvent; import com.alexstyl.specialdates.date.Date; import com.alexstyl.specialdates.events.peopleevents.ContactEvents; -import com.alexstyl.specialdates.events.peopleevents.EventType; +import com.alexstyl.specialdates.events.peopleevents.StandardEventType; import java.util.ArrayList; import java.util.List; @@ -17,12 +18,13 @@ public class ContactActionTest { + private final Optional NO_DEVICE_EVENT_ID = Optional.absent(); private final List ANY_CONTACTS = new ArrayList<>(); private final TestContact CONTACT_ONE = new TestContact(1, DisplayName.from("Alex Styl")); - private final ContactEvent EVENT_ONE = new ContactEvent(EventType.BIRTHDAY, Date.on(1, 1, 1990), CONTACT_ONE); + private final ContactEvent EVENT_ONE = new ContactEvent(NO_DEVICE_EVENT_ID, StandardEventType.BIRTHDAY, Date.on(1, 1, 1990), CONTACT_ONE); private final TestContact CONTACT_TWO = new TestContact(2, DisplayName.from("George Peterson")); - private final ContactEvent EVENT_TWO = new ContactEvent(EventType.BIRTHDAY, Date.on(1, 1, 1970), CONTACT_TWO); + private final ContactEvent EVENT_TWO = new ContactEvent(NO_DEVICE_EVENT_ID, StandardEventType.BIRTHDAY, Date.on(1, 1, 1970), CONTACT_TWO); @Test public void testTheSameDateIsReturned() throws Exception { diff --git a/mobile/src/test/java/com/alexstyl/specialdates/events/DateDisplayStringCreatorTest.java b/mobile/src/test/java/com/alexstyl/specialdates/events/DateDisplayStringCreatorTest.java index 4c779ccf..5f5af4f8 100644 --- a/mobile/src/test/java/com/alexstyl/specialdates/events/DateDisplayStringCreatorTest.java +++ b/mobile/src/test/java/com/alexstyl/specialdates/events/DateDisplayStringCreatorTest.java @@ -16,7 +16,7 @@ public class DateDisplayStringCreatorTest { @BeforeClass public static void init() { - creator = DateDisplayStringCreator.getInstance(); + creator = DateDisplayStringCreator.INSTANCE; } @Test diff --git a/mobile/src/test/java/com/alexstyl/specialdates/events/DateTest.java b/mobile/src/test/java/com/alexstyl/specialdates/events/DateTest.java index 1b97f5d8..58ff2d6e 100644 --- a/mobile/src/test/java/com/alexstyl/specialdates/events/DateTest.java +++ b/mobile/src/test/java/com/alexstyl/specialdates/events/DateTest.java @@ -11,10 +11,13 @@ public class DateTest { + private static final DateComparator comparator = DateComparator.INSTANCE; + private static final int DAY = 5; private static final @MonthInt int MONTH = 10; + private static final Date ANY_DATE = Date.on(DAY, MONTH, 1990); @Test @@ -64,8 +67,6 @@ public void toShortDateWithYear() { assertThat(date.toShortDate()).isEqualTo("1990-01-01"); } - private DateComparator comparator = DateComparator.get(); - @Test public void compareFutureDayDate() { int result = comparator.compare(Date.on(1, JANUARY, 1990), Date.on(2, JANUARY, 1990)); diff --git a/mobile/src/test/java/com/alexstyl/specialdates/events/EventTypeTest.java b/mobile/src/test/java/com/alexstyl/specialdates/events/EventTypeTest.java deleted file mode 100644 index a9261020..00000000 --- a/mobile/src/test/java/com/alexstyl/specialdates/events/EventTypeTest.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.alexstyl.specialdates.events; - -import com.alexstyl.specialdates.events.database.EventsDBContract.AnnualEventsContract; -import com.alexstyl.specialdates.events.peopleevents.EventType; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.runners.MockitoJUnitRunner; - -import static org.fest.assertions.api.Assertions.assertThat; - -@RunWith(MockitoJUnitRunner.class) -public class EventTypeTest { - - @Test - public void mapsTypeBirthdayIdToBirthdayEvent() { - EventType eventType = EventType.fromId(AnnualEventsContract.TYPE_BIRTHDAY); - assertThat(eventType).isEqualTo(EventType.BIRTHDAY); - } - - @Test - public void mapsTypeNamedayIdToNamedayEvent() { - EventType eventType = EventType.fromId(AnnualEventsContract.TYPE_NAMEDAY); - assertThat(eventType).isEqualTo(EventType.NAMEDAY); - } - - @Test(expected = IllegalArgumentException.class) - public void throwExceptionOnInvalidId() { - EventType.fromId(3); - } -} diff --git a/mobile/src/test/java/com/alexstyl/specialdates/events/StandardEventTypeTest.java b/mobile/src/test/java/com/alexstyl/specialdates/events/StandardEventTypeTest.java new file mode 100644 index 00000000..c6a997ac --- /dev/null +++ b/mobile/src/test/java/com/alexstyl/specialdates/events/StandardEventTypeTest.java @@ -0,0 +1,34 @@ +package com.alexstyl.specialdates.events; + +import com.alexstyl.specialdates.events.database.DatabaseContract.AnnualEventsContract; +import com.alexstyl.specialdates.events.database.EventColumns; +import com.alexstyl.specialdates.events.peopleevents.StandardEventType; +import com.novoda.notils.exception.DeveloperError; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.fest.assertions.api.Assertions.assertThat; + +@RunWith(MockitoJUnitRunner.class) +public class StandardEventTypeTest { + + @Test + public void mapsTypeBirthdayIdToBirthdayEvent() { + StandardEventType eventType = StandardEventType.fromId(AnnualEventsContract.TYPE_BIRTHDAY); + assertThat(eventType).isEqualTo(StandardEventType.BIRTHDAY); + } + + @Test + public void mapsTypeNamedayIdToNamedayEvent() { + StandardEventType eventType = StandardEventType.fromId(AnnualEventsContract.TYPE_NAMEDAY); + assertThat(eventType).isEqualTo(StandardEventType.NAMEDAY); + } + + @Test(expected = IllegalArgumentException.class) + public void throwExceptionOnInvalidId() { + StandardEventType.fromId(5); + } + +} diff --git a/mobile/src/test/java/com/alexstyl/specialdates/events/bankholidays/BankHolidayProviderTest.java b/mobile/src/test/java/com/alexstyl/specialdates/events/bankholidays/BankHolidayProviderTest.java new file mode 100644 index 00000000..ee059f23 --- /dev/null +++ b/mobile/src/test/java/com/alexstyl/specialdates/events/bankholidays/BankHolidayProviderTest.java @@ -0,0 +1,42 @@ +package com.alexstyl.specialdates.events.bankholidays; + +//@RunWith(MockitoJUnitRunner.class) +public class BankHolidayProviderTest { +// private static final Date GREEK_INDEPENDENCE_DAY = Date.on(25, MARCH, 1990); +// +// private OrthodoxEasterCalculator calculator; +// private BankHolidayProvider repository; +// +// @Before +// public void setUp() throws Exception { +// repository = new BankHolidayProvider(new GreekBankHolidaysCalculator(OrthodoxEasterCalculator.INSTANCE)); +//// when(calculator.calculateEasterForYear(1990)).thenReturn(Date.on(1, JANUARY, 1990)); +//// when(calculator.calculateEasterForYear(1991)).thenReturn(Date.on(1, JANUARY, 1991)); +// } +// +// @Test +// public void whenCheckingForSameYearTwice_thenEasterIsCalculatedOnlyOnce() { +// repository.getBankHolidayFor(aDateInYear(1990)); +// repository.getBankHolidayFor(aDateInYear(1990)); +// verify(calculator, times(1)).calculateEasterForYear(1990); +// } +// +// @Test +// public void whenCheckingForDifferentYearthenEasterIsRecalculated() { +// repository.getBankHolidayFor(aDateInYear(1990)); +// repository.getBankHolidayFor(aDateInYear(1991)); +// verify(calculator, times(1)).calculateEasterForYear(1990); +// verify(calculator, times(1)).calculateEasterForYear(1991); +// } +// +// @Test +// public void testThatAGreekKnownBankholidayIsCalculatedProperly() { +// Date date = GREEK_INDEPENDENCE_DAY; +// Optional bankHoliday = repository.getBankHolidayFor(date); +// assertThat(bankHoliday.get().getDate()).isEqualTo(Date.on(25, MARCH, 1990)); +// } +// +// private Date aDateInYear(int year) { +// return Date.on(1, JANUARY, year); +// } +} diff --git a/mobile/src/test/java/com/alexstyl/specialdates/events/bankholidays/BankHolidayRepositoryTest.java b/mobile/src/test/java/com/alexstyl/specialdates/events/bankholidays/BankHolidayRepositoryTest.java deleted file mode 100644 index e8d27119..00000000 --- a/mobile/src/test/java/com/alexstyl/specialdates/events/bankholidays/BankHolidayRepositoryTest.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.alexstyl.specialdates.events.bankholidays; - -import com.alexstyl.specialdates.Optional; -import com.alexstyl.specialdates.date.Date; -import com.alexstyl.specialdates.events.namedays.calendar.EasterCalculator; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; - -import static com.alexstyl.specialdates.date.DateConstants.MARCH; -import static org.fest.assertions.api.Assertions.assertThat; -import static org.mockito.Mockito.*; - -@RunWith(MockitoJUnitRunner.class) -public class BankHolidayRepositoryTest { - - private static final Date GREEK_INDEPENDENCE_DAY = Date.on(25, MARCH, 1990); - - @Mock - private EasterCalculator calculator; - private BankHolidayRepository repository; - - @Before - public void setUp() throws Exception { - repository = new BankHolidayRepository(calculator); - when(calculator.calculateEasterForYear(1990)).thenReturn(Date.on(1, 1, 1990)); - when(calculator.calculateEasterForYear(1991)).thenReturn(Date.on(1, 1, 1991)); - } - - @Test - public void whenCheckingForSameYearTwice_thenEasterIsCalculatedOnlyOnce() { - repository.calculateBankholidayFor(aDateInYear(1990)); - repository.calculateBankholidayFor(aDateInYear(1990)); - verify(calculator, times(1)).calculateEasterForYear(1990); - } - - @Test - public void whenCheckingForDifferentYearthenEasterIsRecalculated() { - repository.calculateBankholidayFor(aDateInYear(1990)); - repository.calculateBankholidayFor(aDateInYear(1991)); - verify(calculator, times(1)).calculateEasterForYear(1990); - verify(calculator, times(1)).calculateEasterForYear(1991); - } - - @Test - public void testThatAGreekKnownBankholidayIsCalculatedProperly() { - Date date = GREEK_INDEPENDENCE_DAY; - Optional bankHoliday = repository.calculateBankholidayFor(date); - assertThat(bankHoliday.get().getDate()).isEqualTo(Date.on(25, MARCH, 1990)); - } - - private Date aDateInYear(int year) { - return Date.on(1, 1, year); - } -} diff --git a/mobile/src/test/java/com/alexstyl/specialdates/events/namedays/EasterCalculatorTest.java b/mobile/src/test/java/com/alexstyl/specialdates/events/namedays/OrthodoxEasterCalculatorTest.java similarity index 84% rename from mobile/src/test/java/com/alexstyl/specialdates/events/namedays/EasterCalculatorTest.java rename to mobile/src/test/java/com/alexstyl/specialdates/events/namedays/OrthodoxEasterCalculatorTest.java index 75068002..b6360664 100644 --- a/mobile/src/test/java/com/alexstyl/specialdates/events/namedays/EasterCalculatorTest.java +++ b/mobile/src/test/java/com/alexstyl/specialdates/events/namedays/OrthodoxEasterCalculatorTest.java @@ -1,7 +1,7 @@ package com.alexstyl.specialdates.events.namedays; import com.alexstyl.specialdates.date.Date; -import com.alexstyl.specialdates.events.namedays.calendar.EasterCalculator; +import com.alexstyl.specialdates.events.namedays.calendar.OrthodoxEasterCalculator; import java.util.HashMap; @@ -12,10 +12,10 @@ import static com.alexstyl.specialdates.date.DateConstants.MAY; import static org.fest.assertions.api.Assertions.assertThat; -public class EasterCalculatorTest { +public class OrthodoxEasterCalculatorTest { - private HashMap EXPECTED_DATES = new HashMap<>(); - private final EasterCalculator calculator = new EasterCalculator(); + private final HashMap EXPECTED_DATES = new HashMap<>(); + private final OrthodoxEasterCalculator calculator = OrthodoxEasterCalculator.INSTANCE; @Before public void initExpectedDates() { diff --git a/mobile/src/test/java/com/alexstyl/specialdates/events/namedays/calendar/resource/GreeklishParserTest.java b/mobile/src/test/java/com/alexstyl/specialdates/events/namedays/calendar/resource/GreeklishParserTest.java index 5757b4dc..a11cf686 100644 --- a/mobile/src/test/java/com/alexstyl/specialdates/events/namedays/calendar/resource/GreeklishParserTest.java +++ b/mobile/src/test/java/com/alexstyl/specialdates/events/namedays/calendar/resource/GreeklishParserTest.java @@ -18,7 +18,7 @@ public class GreeklishParserTest { public void setUp() throws JSONException { JavaJSONResourceLoader resourceLoader = new JavaJSONResourceLoader(); NamedayJSONResourceProvider resourceProvider = new NamedayJSONResourceProvider(resourceLoader); - namedayJSON = resourceProvider.getNamedayJSONFor(NamedayLocale.gr); + namedayJSON = resourceProvider.getNamedayJSONFor(NamedayLocale.GREEK); } diff --git a/mobile/src/test/java/com/alexstyl/specialdates/events/namedays/calendar/resource/JavaJSONResourceLoader.java b/mobile/src/test/java/com/alexstyl/specialdates/events/namedays/calendar/resource/JavaJSONResourceLoader.java index 2dc40450..0967f3a6 100644 --- a/mobile/src/test/java/com/alexstyl/specialdates/events/namedays/calendar/resource/JavaJSONResourceLoader.java +++ b/mobile/src/test/java/com/alexstyl/specialdates/events/namedays/calendar/resource/JavaJSONResourceLoader.java @@ -27,28 +27,8 @@ public JSONObject loadJSON(NamedayLocale locale) throws JSONException { } private String getPathTo(NamedayLocale locale) { - String prefix = getPrefixOf(locale); + String prefix = locale.getShortCode(); return String.format("src/main/res/raw/%s_namedays.json", prefix); } - private String getPrefixOf(NamedayLocale locale) { - switch (locale) { - case gr: - return "gr"; - case cs: - return "cs"; - case lv: - return "lv"; - case ro: - return "ro"; - case ru: - return "ru"; - case sk: - return "sk"; - default: - throw new IllegalArgumentException("Cannot match " + locale + " to file"); - - } - } - } diff --git a/mobile/src/test/java/com/alexstyl/specialdates/events/namedays/calendar/resource/NamedayJSONParserTest.java b/mobile/src/test/java/com/alexstyl/specialdates/events/namedays/calendar/resource/NamedayJSONParserTest.java index a7d72018..0bef37b7 100644 --- a/mobile/src/test/java/com/alexstyl/specialdates/events/namedays/calendar/resource/NamedayJSONParserTest.java +++ b/mobile/src/test/java/com/alexstyl/specialdates/events/namedays/calendar/resource/NamedayJSONParserTest.java @@ -22,7 +22,7 @@ public class NamedayJSONParserTest { public void setUp() throws JSONException { JavaJSONResourceLoader resourceLoader = new JavaJSONResourceLoader(); NamedayJSONResourceProvider resourceProvider = new NamedayJSONResourceProvider(resourceLoader); - namedayJSON = resourceProvider.getNamedayJSONFor(NamedayLocale.gr); + namedayJSON = resourceProvider.getNamedayJSONFor(NamedayLocale.GREEK); } diff --git a/mobile/src/test/java/com/alexstyl/specialdates/events/namedays/calendar/resource/NamedayJSONResourceProviderTest.java b/mobile/src/test/java/com/alexstyl/specialdates/events/namedays/calendar/resource/NamedayJSONResourceProviderTest.java index c4cf8680..439291aa 100644 --- a/mobile/src/test/java/com/alexstyl/specialdates/events/namedays/calendar/resource/NamedayJSONResourceProviderTest.java +++ b/mobile/src/test/java/com/alexstyl/specialdates/events/namedays/calendar/resource/NamedayJSONResourceProviderTest.java @@ -26,37 +26,37 @@ public void allLocalesHaveData() throws Exception { @Test public void grHasSpecial() throws Exception { - NamedayJSON namedays = provider.getNamedayJSONFor(NamedayLocale.gr); + NamedayJSON namedays = provider.getNamedayJSONFor(NamedayLocale.GREEK); hasSpecial(namedays); } @Test public void roHasSpecial() throws Exception { - NamedayJSON namedays = provider.getNamedayJSONFor(NamedayLocale.ro); + NamedayJSON namedays = provider.getNamedayJSONFor(NamedayLocale.ROMANIAN); hasSpecial(namedays); } @Test public void ruHasNoSpecial() throws Exception { - NamedayJSON namedays = provider.getNamedayJSONFor(NamedayLocale.ru); + NamedayJSON namedays = provider.getNamedayJSONFor(NamedayLocale.RUSSIAN); hasNoSpecial(namedays); } @Test public void lvHasNoSpecial() throws Exception { - NamedayJSON namedays = provider.getNamedayJSONFor(NamedayLocale.lv); + NamedayJSON namedays = provider.getNamedayJSONFor(NamedayLocale.LATVIAN); hasNoSpecial(namedays); } @Test public void csHasNoSpecial() throws Exception { - NamedayJSON namedays = provider.getNamedayJSONFor(NamedayLocale.cs); + NamedayJSON namedays = provider.getNamedayJSONFor(NamedayLocale.CZECH); hasNoSpecial(namedays); } @Test public void skHasNoSpecial() throws Exception { - NamedayJSON namedays = provider.getNamedayJSONFor(NamedayLocale.sk); + NamedayJSON namedays = provider.getNamedayJSONFor(NamedayLocale.SLOVAK); hasNoSpecial(namedays); } diff --git a/mobile/src/test/java/com/alexstyl/specialdates/events/namedays/calendar/resource/RomanianEasterSpecialCalculatorTest.java b/mobile/src/test/java/com/alexstyl/specialdates/events/namedays/calendar/resource/RomanianEasterSpecialCalculatorTest.java index bf4078ac..2dfeb0bf 100644 --- a/mobile/src/test/java/com/alexstyl/specialdates/events/namedays/calendar/resource/RomanianEasterSpecialCalculatorTest.java +++ b/mobile/src/test/java/com/alexstyl/specialdates/events/namedays/calendar/resource/RomanianEasterSpecialCalculatorTest.java @@ -1,6 +1,7 @@ package com.alexstyl.specialdates.events.namedays.calendar.resource; import com.alexstyl.specialdates.date.Date; +import com.alexstyl.specialdates.events.namedays.calendar.OrthodoxEasterCalculator; import java.util.ArrayList; import java.util.List; @@ -13,7 +14,7 @@ public class RomanianEasterSpecialCalculatorTest { @Test public void calculatesSundayBeforeEasterCorrectly() { - RomanianEasterSpecialCalculator calculator = new RomanianEasterSpecialCalculator(); + RomanianEasterSpecialCalculator calculator = new RomanianEasterSpecialCalculator(OrthodoxEasterCalculator.INSTANCE); List expectedDates = buildExpectedDates(); for (Date expectedDate : expectedDates) { Date actualDate = calculator.calculateSpecialRomanianDayForYear(expectedDate.getYear()); diff --git a/mobile/src/test/java/com/alexstyl/specialdates/events/namedays/calendar/resource/TestNamedayCalendarBuilder.java b/mobile/src/test/java/com/alexstyl/specialdates/events/namedays/calendar/resource/TestNamedayCalendarBuilder.java new file mode 100644 index 00000000..c23a6f76 --- /dev/null +++ b/mobile/src/test/java/com/alexstyl/specialdates/events/namedays/calendar/resource/TestNamedayCalendarBuilder.java @@ -0,0 +1,27 @@ +package com.alexstyl.specialdates.events.namedays.calendar.resource; + +import com.alexstyl.specialdates.date.Date; +import com.alexstyl.specialdates.events.namedays.NamedayLocale; +import com.alexstyl.specialdates.events.namedays.calendar.NamedayCalendar; + +public class TestNamedayCalendarBuilder { + private NamedayLocale locale = NamedayLocale.GREEK; + private int year = Date.CURRENT_YEAR; + private SpecialNamedaysHandlerFactory factory = new SpecialNamedaysHandlerFactory(); + + public TestNamedayCalendarBuilder forLocale(NamedayLocale locale) { + this.locale = locale; + return this; + } + + public TestNamedayCalendarBuilder forYear(int year) { + this.year = year; + return this; + } + + public NamedayCalendar build() { + NamedayJSONResourceProvider jsonProvider = new NamedayJSONResourceProvider(new JavaJSONResourceLoader()); + NamedayCalendarProvider namedayCalendarProvider = new NamedayCalendarProvider(jsonProvider, factory); + return namedayCalendarProvider.loadNamedayCalendarForLocale(locale, year); + } +} diff --git a/mobile/src/test/java/com/alexstyl/specialdates/events/peopleevents/PeopleNamedaysCalculatorTest.java b/mobile/src/test/java/com/alexstyl/specialdates/events/peopleevents/PeopleNamedaysCalculatorTest.java new file mode 100644 index 00000000..d89b314a --- /dev/null +++ b/mobile/src/test/java/com/alexstyl/specialdates/events/peopleevents/PeopleNamedaysCalculatorTest.java @@ -0,0 +1,80 @@ +package com.alexstyl.specialdates.events.peopleevents; + +import com.alexstyl.specialdates.DisplayName; +import com.alexstyl.specialdates.TestContact; +import com.alexstyl.specialdates.contact.Contact; +import com.alexstyl.specialdates.contact.ContactsProvider; +import com.alexstyl.specialdates.date.ContactEvent; +import com.alexstyl.specialdates.date.Date; +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.OrthodoxEasterCalculator; +import com.alexstyl.specialdates.events.namedays.calendar.resource.TestNamedayCalendarBuilder; +import com.alexstyl.specialdates.events.namedays.calendar.resource.NamedayCalendarProvider; +import com.alexstyl.specialdates.upcoming.TimePeriod; +import com.novoda.notils.logger.simple.Log; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.fest.assertions.api.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class PeopleNamedaysCalculatorTest { + + private PeopleNamedaysCalculator calculator; + + private static final NamedayLocale LOCALE = NamedayLocale.GREEK; + + private static final int YEAR = 2016; + @Mock + private NamedayCalendarProvider namedayCalendarProvider; + @Mock + private NamedayPreferences mockPreferences; + @Mock + private ContactsProvider mockContactsProvider; + private final TestContact EASTER_CELEBRATING_CONTACT = new TestContact(1, DisplayName.from("Λάμπρος")); + + @Before + public void setUp() { + Log.setShowLogs(false); + NamedayCalendar namedayCalendar = new TestNamedayCalendarBuilder() + .forLocale(LOCALE) + .forYear(YEAR) + .build(); + + when(namedayCalendarProvider.loadNamedayCalendarForLocale(any(NamedayLocale.class), any(Integer.class))).thenReturn(namedayCalendar); + when(mockPreferences.getSelectedLanguage()).thenReturn(LOCALE); + calculator = new PeopleNamedaysCalculator(mockPreferences, namedayCalendarProvider, mockContactsProvider); + + } + + @Test + public void gettingSpecialNamedaysOnSpecificDateOnlyReturnsTheEventsForThatDate() { + List testContacts = createSomeContacts(); + testContacts.add(EASTER_CELEBRATING_CONTACT); + when(mockContactsProvider.fetchAllDeviceContacts()).thenReturn(testContacts); + + Date easterDate = OrthodoxEasterCalculator.INSTANCE.calculateEasterForYear(YEAR); + List contactEvents = calculator.loadSpecialNamedaysBetween(TimePeriod.between(easterDate, easterDate)); + assertThat(contactEvents).hasSize(1); + assertThat(contactEvents.get(0).getContact()).isEqualTo(EASTER_CELEBRATING_CONTACT); + } + + private List createSomeContacts() { + ArrayList contacts = new ArrayList<>(); + contacts.add(new TestContact(12, DisplayName.from("Αβδηρος"))); + contacts.add(new TestContact(13, DisplayName.from("Αγις"))); + return contacts; + } + +} diff --git a/mobile/src/test/java/com/alexstyl/specialdates/upcoming/AnnualDateTest.java b/mobile/src/test/java/com/alexstyl/specialdates/upcoming/AnnualDateTest.java new file mode 100644 index 00000000..c106769b --- /dev/null +++ b/mobile/src/test/java/com/alexstyl/specialdates/upcoming/AnnualDateTest.java @@ -0,0 +1,36 @@ +package com.alexstyl.specialdates.upcoming; + +import com.alexstyl.specialdates.date.Date; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; + +import static com.alexstyl.specialdates.date.DateConstants.FEBRUARY; +import static com.alexstyl.specialdates.date.DateConstants.MARCH; +import static org.fest.assertions.api.Assertions.assertThat; + +@RunWith(MockitoJUnitRunner.class) +public class AnnualDateTest { + + @Test + public void differentYearsAreEqual() { + assertThat(new AnnualDate(Date.on(1, FEBRUARY, 2016))).isEqualTo( + new AnnualDate(Date.on(1, FEBRUARY, 1990)) + ); + } + + @Test + public void sameYearsAreEqual() { + assertThat(new AnnualDate(Date.on(1, FEBRUARY, 2016))).isEqualTo( + new AnnualDate(Date.on(1, FEBRUARY, 2016)) + ); + } + + @Test + public void differentDatesAreNotEqual() { + assertThat(new AnnualDate(Date.on(5, FEBRUARY, 2016))).isNotEqualTo( + new AnnualDate(Date.on(1, MARCH, 2016)) + ); + } +} diff --git a/mobile/src/test/java/com/alexstyl/specialdates/upcoming/MonthLabelsTest.java b/mobile/src/test/java/com/alexstyl/specialdates/upcoming/MonthLabelsTest.java index b9d43dde..4782d07e 100644 --- a/mobile/src/test/java/com/alexstyl/specialdates/upcoming/MonthLabelsTest.java +++ b/mobile/src/test/java/com/alexstyl/specialdates/upcoming/MonthLabelsTest.java @@ -3,6 +3,8 @@ import org.junit.Before; import org.junit.Test; +import static com.alexstyl.specialdates.date.DateConstants.DECEMBER; +import static com.alexstyl.specialdates.date.DateConstants.JANUARY; import static org.fest.assertions.api.Assertions.assertThat; public class MonthLabelsTest { @@ -19,16 +21,16 @@ public void setup() { @Test public void givenTheFirstMonthOfTheYear_thenTheCorrectLabelIsReturned() { - int index = 1; + int index = JANUARY; String zeroIndexedLabel = monthLabels.getMonthOfYear(index); - assertThat(zeroIndexedLabel).isEqualTo(months[index - 1]); + assertThat(zeroIndexedLabel).isEqualTo("January"); } @Test public void givenTheLastMonthOfTheYear_thenTheCorrectLabelIsReturned() { - int index = 12; + int index = DECEMBER; String zeroIndexedLabel = monthLabels.getMonthOfYear(index); - assertThat(zeroIndexedLabel).isEqualTo(months[index - 1]); + assertThat(zeroIndexedLabel).isEqualTo("December"); } @Test @@ -36,4 +38,4 @@ public void copyOfMonthCreatesCorrectCoppy() { String[] copy = monthLabels.getMonthsOfYear(); assertThat(copy).isEqualTo(months); } -} \ No newline at end of file +} diff --git a/mobile/src/test/java/com/alexstyl/specialdates/upcoming/NamedaySettingsMonitorTest.java b/mobile/src/test/java/com/alexstyl/specialdates/upcoming/NamedaySettingsMonitorTest.java index 8ded75b5..d29bb02e 100644 --- a/mobile/src/test/java/com/alexstyl/specialdates/upcoming/NamedaySettingsMonitorTest.java +++ b/mobile/src/test/java/com/alexstyl/specialdates/upcoming/NamedaySettingsMonitorTest.java @@ -24,7 +24,7 @@ public class NamedaySettingsMonitorTest { private static final boolean DEFAULT_CONTACTS_ONLY = false; private static final boolean DEFAULT_FULL_NAME = false; - private static final NamedayLocale DEFAULT_LOCALE = NamedayLocale.gr; + private static final NamedayLocale DEFAULT_LOCALE = NamedayLocale.GREEK; @Before public void setUp() throws Exception { @@ -41,7 +41,7 @@ private void initialiseMonitor() { @Test public void whenSelectedNamedayLanguageChanges_thenMonitorReturnsTrue() { - when(mockPreferences.getSelectedLanguage()).thenReturn(NamedayLocale.ro); + when(mockPreferences.getSelectedLanguage()).thenReturn(NamedayLocale.ROMANIAN); assertThat(monitor.dataWasUpdated()).isTrue(); } diff --git a/mobile/src/test/java/com/alexstyl/specialdates/upcoming/TimePeriodTest.java b/mobile/src/test/java/com/alexstyl/specialdates/upcoming/TimePeriodTest.java new file mode 100644 index 00000000..b109359b --- /dev/null +++ b/mobile/src/test/java/com/alexstyl/specialdates/upcoming/TimePeriodTest.java @@ -0,0 +1,42 @@ +package com.alexstyl.specialdates.upcoming; + +import com.alexstyl.specialdates.date.Date; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; + +import static com.alexstyl.specialdates.date.DateConstants.FEBRUARY; +import static org.fest.assertions.api.Assertions.assertThat; +import static org.fest.assertions.api.Assertions.fail; + +@RunWith(MockitoJUnitRunner.class) +public class TimePeriodTest { + + @Test + public void dateBetweenPeriod() { + TimePeriod range = TimePeriod.between( + Date.startOfTheYear(2016), + Date.startOfTheYear(2017) + ); + assertThat(range.containsDate(Date.on(1, FEBRUARY, 2016))).isTrue(); + } + + @Test + public void dateOutsideOfPeriod() { + TimePeriod range = TimePeriod.between( + Date.startOfTheYear(2016), + Date.startOfTheYear(2017) + ); + assertThat(range.containsDate(Date.on(1, FEBRUARY, 1990))).isFalse(); + } + + @Test(expected = IllegalArgumentException.class) + public void invalidPeriodThrowsException() { + TimePeriod.between( + Date.startOfTheYear(3560), + Date.startOfTheYear(5) + ); + fail("Should have thrown exception"); + } +} diff --git a/mobile/src/test/java/com/alexstyl/specialdates/upcoming/UpcomingDatesBuilderTest.java b/mobile/src/test/java/com/alexstyl/specialdates/upcoming/UpcomingDatesBuilderTest.java index 23240643..6b48be86 100644 --- a/mobile/src/test/java/com/alexstyl/specialdates/upcoming/UpcomingDatesBuilderTest.java +++ b/mobile/src/test/java/com/alexstyl/specialdates/upcoming/UpcomingDatesBuilderTest.java @@ -1,15 +1,14 @@ package com.alexstyl.specialdates.upcoming; -import android.support.annotation.NonNull; - import com.alexstyl.specialdates.DisplayName; +import com.alexstyl.specialdates.Optional; import com.alexstyl.specialdates.TestContact; import com.alexstyl.specialdates.date.CelebrationDate; import com.alexstyl.specialdates.date.ContactEvent; import com.alexstyl.specialdates.date.Date; import com.alexstyl.specialdates.events.bankholidays.BankHoliday; import com.alexstyl.specialdates.events.namedays.NamesInADate; -import com.alexstyl.specialdates.events.peopleevents.EventType; +import com.alexstyl.specialdates.events.peopleevents.StandardEventType; import java.util.Arrays; import java.util.Collections; @@ -19,8 +18,7 @@ import org.junit.runner.RunWith; import org.mockito.runners.MockitoJUnitRunner; -import static com.alexstyl.specialdates.date.DateConstants.FEBRUARY; -import static com.alexstyl.specialdates.date.DateConstants.MARCH; +import static com.alexstyl.specialdates.date.DateConstants.*; import static org.fest.assertions.api.Assertions.assertThat; @RunWith(MockitoJUnitRunner.class) @@ -29,23 +27,46 @@ public class UpcomingDatesBuilderTest { private static final Date FEBRUARY_1st = Date.on(1, FEBRUARY, 1990); private static final Date FEBRUARY_3rd = Date.on(3, FEBRUARY, 1990); private static final Date MARCH_5th = Date.on(5, MARCH, 1990); + private static final Optional NO_DEVICE_EVENT_ID = Optional.absent(); + + @Test + public void celebrationDateIsCreatedCorrectlyForDifferentYear() { + ContactEvent event = aContactEventOn(Date.on(1, JANUARY, 1990)); + List contactEvents = Collections.singletonList(event); + + TimePeriod duration = TimePeriod.between(Date.on(1, JANUARY, 2016), Date.on(1, DECEMBER, 2016)); + + List dates = new UpcomingDatesBuilder(duration) + .withContactEvents(contactEvents) + .build(); + + assertThat(dates.size()).isEqualTo(1); + } @Test public void givenASingleContactEvent_thenOneCelebrationDateIsCreated() { - List contactEvents = Collections.singletonList(aContactEvent()); + ContactEvent event = aContactEvent(); + List contactEvents = Collections.singletonList(event); - List dates = new UpcomingDatesBuilder() + TimePeriod duration = timeOf(event); + + List dates = new UpcomingDatesBuilder(duration) .withContactEvents(contactEvents) .build(); assertThat(dates.size()).isEqualTo(1); } + private TimePeriod timeOf(ContactEvent event) { + return TimePeriod.between(event.getDate(), event.getDate()); + } + @Test public void givenASingleContactEvent_thenTheCelebrationDateContainsTheContactEvent() { - List contactEvents = Collections.singletonList(aContactEvent()); + ContactEvent event = aContactEvent(); + List contactEvents = Collections.singletonList(event); - List dates = new UpcomingDatesBuilder() + List dates = new UpcomingDatesBuilder(timeOf(event)) .withContactEvents(contactEvents) .build(); @@ -60,7 +81,7 @@ public void givenTwoContactEventsOnSameDate_thenOneCelebrationDateIsCreated() { aContactEventOn(FEBRUARY_1st) ); - List dates = new UpcomingDatesBuilder() + List dates = new UpcomingDatesBuilder(TimePeriod.between(FEBRUARY_1st, FEBRUARY_1st)) .withContactEvents(contactEventsList) .build(); @@ -74,7 +95,7 @@ public void givenTwoContactEventsOnDifferentDate_thenTwoCelebrationDateAreCreate aContactEventOn(FEBRUARY_3rd) ); - List dates = new UpcomingDatesBuilder() + List dates = new UpcomingDatesBuilder(TimePeriod.between(FEBRUARY_1st, FEBRUARY_3rd)) .withContactEvents(contactEventsList) .build(); @@ -83,8 +104,9 @@ public void givenTwoContactEventsOnDifferentDate_thenTwoCelebrationDateAreCreate @Test public void givenABankHoliday_thenACelebrationDateIsCreated() { - List bankHolidays = Collections.singletonList(aBankHoliday()); - List dates = new UpcomingDatesBuilder() + BankHoliday bankHoliday = aBankHoliday(); + List bankHolidays = Collections.singletonList(bankHoliday); + List dates = new UpcomingDatesBuilder(TimePeriod.between(bankHoliday.getDate(), bankHoliday.getDate())) .withBankHolidays(bankHolidays) .build(); @@ -93,7 +115,7 @@ public void givenABankHoliday_thenACelebrationDateIsCreated() { @Test public void givenEventsOnDifferentEvents_thenACelebrationDatesForEachOneAreCreated() { - List dates = new UpcomingDatesBuilder() + List dates = new UpcomingDatesBuilder(TimePeriod.between(FEBRUARY_1st, MARCH_5th)) .withContactEvents(Collections.singletonList(aContactEventOn(FEBRUARY_1st))) .withBankHolidays(Collections.singletonList(aBankHolidayOn(FEBRUARY_3rd))) .withNamedays(Collections.singletonList(new NamesInADate(MARCH_5th, Collections.singletonList("Name")))) @@ -102,22 +124,21 @@ public void givenEventsOnDifferentEvents_thenACelebrationDatesForEachOneAreCreat assertThat(dates.size()).isEqualTo(3); } - private BankHoliday aBankHolidayOn(Date date) { + private static BankHoliday aBankHolidayOn(Date date) { return new BankHoliday("A bank holiday", date); } - @NonNull - private BankHoliday aBankHoliday() { - return new BankHoliday("A bank holiday", Date.on(1, 1, 1990)); + private static BankHoliday aBankHoliday() { + return new BankHoliday("A bank holiday", Date.on(1, JANUARY, 1990)); } private static ContactEvent aContactEvent() { - return aContactEventOn(Date.on(1, 1, 1990)); + return aContactEventOn(Date.on(1, JANUARY, 1990)); } private static ContactEvent aContactEventOn(Date date) { TestContact contact = new TestContact(1, DisplayName.NO_NAME); - return new ContactEvent(EventType.BIRTHDAY, date, contact); + return new ContactEvent(NO_DEVICE_EVENT_ID, StandardEventType.BIRTHDAY, date, contact); } } diff --git a/mobile/src/test/java/com/alexstyl/specialdates/util/DateParserTest.java b/mobile/src/test/java/com/alexstyl/specialdates/util/DateParserTest.java index b36bc498..7d132f32 100644 --- a/mobile/src/test/java/com/alexstyl/specialdates/util/DateParserTest.java +++ b/mobile/src/test/java/com/alexstyl/specialdates/util/DateParserTest.java @@ -10,7 +10,7 @@ public class DateParserTest { - private DateParser dateParser = new DateParser(); + private DateParser dateParser = DateParser.INSTANCE; @Test public void dateWithSlashes() throws DateParseException { diff --git a/mobile/src/test/java/com/alexstyl/specialdates/widgetprovider/PercentToValueConverterTest.java b/mobile/src/test/java/com/alexstyl/specialdates/widgetprovider/PercentToValueConverterTest.java index 6ffaa43d..f887abde 100644 --- a/mobile/src/test/java/com/alexstyl/specialdates/widgetprovider/PercentToValueConverterTest.java +++ b/mobile/src/test/java/com/alexstyl/specialdates/widgetprovider/PercentToValueConverterTest.java @@ -15,7 +15,7 @@ public class PercentToValueConverterTest { private final PercentToValueConverter converter = new PercentToValueConverter(); @Test - public void testPercentToValue() throws Exception { + public void testPercentToValue() { assertThat(converter.percentToProgress(0)).isEqualTo(0); assertThat(converter.percentToProgress(0.25f)).isEqualTo(1); assertThat(converter.percentToProgress(0.50f)).isEqualTo(2); @@ -24,7 +24,7 @@ public void testPercentToValue() throws Exception { } @Test - public void testValueToPercentage() throws Exception { + public void testValueToPercentage() { assertThat(converter.progressToPercent(0)).isEqualTo(0); assertThat(converter.progressToPercent(1)).isEqualTo(0.25f); assertThat(converter.progressToPercent(2)).isEqualTo(0.50f); diff --git a/mock-google-services.json b/mock-google-services.json new file mode 100644 index 00000000..27c7f620 --- /dev/null +++ b/mock-google-services.json @@ -0,0 +1,124 @@ +{ + "project_info": { + "project_id": "mockproject-1234", + "project_number": "123456789000", + "name": "FirebaseQuickstarts", + "firebase_url": "https://mockproject-1234.firebaseio.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:123456789000:android:f1bf012572b04063", + "client_id": "android:com.google.samples.quickstart.admobexample", + "client_type": 1, + "android_client_info": { + "package_name": "com.alexstyl.specialdates", + "certificate_hash": [] + } + }, + "oauth_client": [ + { + "client_id": "123456789000-hjugbg6ud799v4c49dim8ce2usclthar.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.alexstyl.specialdates", + "certificate_hash": "4C20644DE36B8F89D25650C7D1FF9FBAE650FDF7" + } + }, + { + "client_id": "123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzbSzCn1N6LWIe6wthYyrgUUSAlUsdqMb-wvTo" + } + ], + "services": { + "analytics_service": { + "status": 1 + }, + "cloud_messaging_service": { + "status": 2, + "apns_config": [] + }, + "appinvite_service": { + "status": 2, + "other_platform_oauth_client": [ + { + "client_id": "123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com", + "client_type": 3 + } + ] + }, + "google_signin_service": { + "status": 2 + }, + "ads_service": { + "status": 2, + "test_banner_ad_unit_id": "ca-app-pub-3940256099942544/6300978111", + "test_interstitial_ad_unit_id": "ca-app-pub-3940256099942544/1033173712" + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:123456789000:android:f1bf012572b04063", + "client_id": "android:com.google.samples.quickstart.admobexample", + "client_type": 1, + "android_client_info": { + "package_name": "com.alexstyl.specialdates.pro", + "certificate_hash": [] + } + }, + "oauth_client": [ + { + "client_id": "123456789000-hjugbg6ud799v4c49dim8ce2usclthar.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.alexstyl.specialdates.pro", + "certificate_hash": "4C20644DE36B8F89D25650C7D1FF9FBAE650FDF7" + } + }, + { + "client_id": "123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzbSzCn1N6LWIe6wthYyrgUUSAlUsdqMb-wvTo" + } + ], + "services": { + "analytics_service": { + "status": 1 + }, + "cloud_messaging_service": { + "status": 2, + "apns_config": [] + }, + "appinvite_service": { + "status": 2, + "other_platform_oauth_client": [ + { + "client_id": "123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com", + "client_type": 3 + } + ] + }, + "google_signin_service": { + "status": 2 + }, + "ads_service": { + "status": 2, + "test_banner_ad_unit_id": "ca-app-pub-3940256099942544/6300978111", + "test_interstitial_ad_unit_id": "ca-app-pub-3940256099942544/1033173712" + } + } + } + ], + "client_info": [], + "ARTIFACT_VERSION": "1" +}