├── .gitignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── app ├── .gitignore ├── build.gradle ├── lint.xml ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── zendesk │ │ └── rememberthedate │ │ └── rememberthedate │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── ic_launcher-web.png │ ├── java │ └── com │ │ └── zendesk │ │ └── rememberthedate │ │ ├── Constants.java │ │ ├── Global.java │ │ ├── LocalNotification.java │ │ ├── model │ │ ├── DateModel.java │ │ └── UserProfile.java │ │ ├── push │ │ ├── PushUtils.java │ │ ├── ZendeskFirebaseInstanceIdService.java │ │ └── ZendeskFirebaseMessagingService.java │ │ ├── storage │ │ └── AppStorage.java │ │ └── ui │ │ ├── CreateProfileActivity.java │ │ ├── DateFragment.java │ │ ├── EditDateActivity.java │ │ ├── HelpFragment.java │ │ ├── ImageUtils.java │ │ ├── MainActivity.java │ │ ├── ProfileFragment.java │ │ ├── SetDateActivity.java │ │ └── SetTimeActivity.java │ └── res │ ├── drawable │ ├── empty_profile.xml │ ├── ic_add_light.xml │ ├── ic_date.xml │ ├── ic_date_24dp.xml │ ├── ic_delete.xml │ ├── ic_edit_light.xml │ ├── ic_email.xml │ ├── ic_launcher_foreground.xml │ ├── ic_save.xml │ ├── ic_save_diasbled.xml │ └── zd_user_default_avatar.xml │ ├── layout │ ├── activity_create_profile.xml │ ├── activity_edit_date.xml │ ├── activity_main.xml │ ├── activity_set_date.xml │ ├── activity_set_time.xml │ ├── date_cell.xml │ ├── fragment_date.xml │ ├── fragment_main.xml │ ├── fragment_profile.xml │ └── toolbar.xml │ ├── menu │ ├── menu_edit_date.xml │ └── profile_menu.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── color.xml │ ├── dimens.xml │ ├── ic_launcher_background.xml │ ├── strings.xml │ ├── styles.xml │ └── zd.xml ├── build.gradle ├── docs ├── images │ └── commit_sha.png └── push_guide.md ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── scripts ├── before_deploy.sh ├── buildApp.sh └── rtd.jks.enc └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | build/ 3 | .gradle/ 4 | local.properties 5 | *iml 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | language: android 4 | group: edge 5 | jdk: 6 | - oraclejdk8 7 | before_cache: 8 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 9 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ 10 | cache: 11 | directories: 12 | - "$HOME/.gradle/caches/" 13 | - "$HOME/.gradle/wrapper/" 14 | android: 15 | components: 16 | - tools 17 | - android-29 18 | - platform-tools 19 | - build-tools-29.0.2 20 | - extra-android-m2repository 21 | - extra-google-m2repository 22 | - extra-google-google_play_services 23 | licenses: 24 | - android-sdk-preview-license-.+ 25 | - android-sdk-license-.+ 26 | - ".+" 27 | script: ./scripts/buildApp.sh 28 | before_deploy: 29 | - openssl aes-256-cbc -K $encrypted_218b70c0d15d_key -iv $encrypted_218b70c0d15d_iv -in scripts/rtd.jks.enc -out scripts/rtd.jks -d 30 | - ./scripts/before_deploy.sh 31 | env: 32 | global: 33 | - secure: GeIi5Obx4J1EqDpA5lnRhF2i0vjJj9SCXzbiSbyiy6gWo9/SuoV4QF46VJVMMU2cfD6b4Y4x6MlbnWnGD+8w3vqMLRN9gJxBukdv8fe8FHXLC2VYOi5lBQHAPip0KaqDlLTMp6U0tKD/2/y7FGXWNbu651SvjOzmwlDBiC0xKaM= 34 | - secure: oIgx29UwqqHlv1y3+Egh1ojXAdktTqNSZjcA4n/k9S9aXfDqw8XMsbDt3VY9yQPZXuRug6aOkTLSanlC+4IajcGX9fM6/3Va1bSpCt8bkWGVXA2enHyEEoVy6s/LfJmuD8lpIltr0yeKafQp6RU61xmqlEtw9DggxI0shg60d14= 35 | - secure: iT32cHpCue2TJvME/tJYjWR1TbyPJnTXzdaAuQ8FnIkZWBPhsWPZ5KLeUvsAKnsohSaD/qMw68s/NpEI9emob6xdsmML2IvyMK3EL7e8t60xnyYOM1RGOZXoP7XQ7G8+DB7GLx1O8YjNlAPQhR7LcoNKa9cKQ4iox0JfhQMM/BM= 36 | - secure: F09QUU7fDOZ9tIjB8k83s5PZYynIe6HI1ZxfUByTs1y6qnZvr40bBaI0hm7bNTcBDvtqhZuxxmuDCr29KWLDN1QH6ExJTUIx2ocAQ4kP9x2t2wr+eoMF19QQeNwqXZuvTa58EFA096hglom5/wLxdKsL8JjskRHekn2ARcK5ank= 37 | deploy: 38 | skip_cleanup: true 39 | provider: releases 40 | api_key: 41 | secure: $zdGithubDeployKey # This env var is configured in https://travis-ci.org/zendesk/sdk_demo_app_android/settings using our Mobile CI Github service account 42 | file: 43 | - ./app/build/outputs/apk/debug/app-debug.apk 44 | - ./app/build/outputs/apk/release/app-release.apk 45 | on: 46 | repo: zendesk/sdk_demo_app_android 47 | tags: true 48 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | By downloading or accessing this software, You agree to the [Zendesk Master Subscription Agreement](https://www.zendesk.com/company/customers-partners/#master-subscription-agreement) and [Application Developer and API License Agreement](https://www.zendesk.com/company/customers-partners/#application-developer-api-license-agreement) and acknowledge that such terms govern Your use of and access to the software. 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | :warning: *Use of this software is subject to important terms and conditions as set forth in the License file* :warning: 2 | 3 | [![Build Status](https://travis-ci.org/zendesk/sdk_demo_app_android.svg?branch=master)](https://travis-ci.org/zendesk/sdk_demo_app_android) 4 | 5 | # Zendesk Mobile SDK Android Demo app 6 | 7 | The "Remember the Date" demo app demonstrates how to use the Zendesk Mobile SDK to build native support into your mobile application. 8 | 9 | ##### The following Zendesk Mobile SDK features are demonstrated in the "Remember The Date" app. 10 | 11 | * Create/Submit a Zendesk ticket request 12 | * View an existing Zendesk ticket request 13 | * Access and search your Zendesk Help Center Self Service content 14 | * Accessing the Zendesk "Rate my app" feature 15 | 16 | Please submit bug reports to [Zendesk](https://rememberthedate.zendesk.com/requests/new). Pull requests are welcome. 17 | 18 | ### Licence: 19 | 20 | By downloading or using the Zendesk Mobile SDK, You agree to the Zendesk Master 21 | Subscription Agreement https://www.zendesk.com/company/customers-partners/#master-subscription-agreement and Application Developer and API License 22 | Agreement https://www.zendesk.com/company/customers-partners/#application-developer-api-license-agreement and 23 | acknowledge that such terms govern Your use of and access to the Mobile SDK. 24 | 25 | ## Releasing 26 | 27 | When you tag a build it will be released automatically. When you are happy with the codebase you can make a tag and push it to GitHub. You will need to know the commit sha that you want to tag and the version number you want to tag it as. 28 | 29 | The commit sha will usually be the latest state of master. You can go to the [master branch](https://github.com/zendesk/sdk_demo_app_android/tree/master) and copy the commit sha [as shown here](https://raw.githubusercontent.com/zendesk/sdk_demo_app_android/master/docs/images/commit_sha.png). 30 | 31 | In this example we will be releasing version 1.2.1. Please update the 1.2.1 below with the actual version that you are releasing. 32 | 33 | ```bash 34 | git fetch 35 | git tag -a "v1.2.1" -m "Release v1.2.1" 36 | git push origin --tags 37 | ``` 38 | 39 | After you push the tag a build will be started on the continuous integration server. You can [check the status](https://travis-ci.org/zendesk/sdk_demo_app_android/builds) of the build, which should take less than 10 minutes. 40 | 41 | When the build is finished the debug and release builds of the app will be published to the [releases section of the repository](https://github.com/zendesk/sdk_demo_app_android/releases/latest). `app-debug.apk` is a debug build that can be used for testing. `app-release.apk` is a signed release build that should be preferred. 42 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | 5 | compileSdkVersion 29 6 | 7 | defaultConfig { 8 | applicationId "com.zendesk.rememberthedate" 9 | minSdkVersion 16 10 | targetSdkVersion 29 11 | 12 | versionCode 25 13 | versionName "2.2.3" 14 | 15 | resValue "string", "google_api_key", System.getenv("zdGoogleApiKey") 16 | ?: rootProject.properties["zdGoogleApiKey"] 17 | ?: "replace me" 18 | resValue "string", "fcm_application_id", System.getenv("zdFcmApplicationId") 19 | ?: rootProject.properties["zdFcmApplicationId"] 20 | ?: "replace me" 21 | } 22 | 23 | signingConfigs { 24 | release { 25 | storeFile file("../scripts/rtd.jks") 26 | storePassword System.getenv("RTD_STORE_PASS") 27 | keyAlias System.getenv("RTD_ALIAS") 28 | keyPassword System.getenv("RTD_KEY_PASS") 29 | } 30 | } 31 | 32 | buildTypes { 33 | 34 | debug { 35 | minifyEnabled true 36 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 37 | } 38 | 39 | release { 40 | if (file("../scripts/rtd.jks").exists()) { 41 | signingConfig signingConfigs.release 42 | } 43 | minifyEnabled true 44 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 45 | } 46 | } 47 | 48 | compileOptions { 49 | sourceCompatibility JavaVersion.VERSION_1_8 50 | targetCompatibility JavaVersion.VERSION_1_8 51 | } 52 | 53 | lintOptions { 54 | // Don't change this to false 55 | abortOnError true 56 | lintConfig file("lint.xml") 57 | } 58 | } 59 | 60 | dependencies { 61 | implementation group: 'androidx.legacy', name: 'legacy-support-v4', version: '1.0.0' 62 | implementation group: 'com.google.android.material', name: 'material', version: '1.2.0' 63 | implementation group: 'androidx.appcompat', name: 'appcompat', version: '1.1.0' 64 | implementation group: 'androidx.recyclerview', name: 'recyclerview', version: '1.1.0' 65 | implementation group: 'androidx.cardview', name: 'cardview', version: '1.0.0' 66 | implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.5' 67 | 68 | implementation group: 'com.google.android.gms', name: 'play-services-gcm', version: '17.0.0' 69 | implementation group: 'com.google.firebase', name: 'firebase-messaging', version: '20.2.4' 70 | 71 | implementation group: 'com.zendesk', name: 'support', version: '5.0.0' 72 | implementation group: 'com.zendesk', name: 'messaging', version: '5.0.0' 73 | implementation group: 'com.zendesk', name: 'answerbot', version: '3.0.0' 74 | implementation group: 'com.zendesk', name: 'chat', version: '3.0.0' 75 | } -------------------------------------------------------------------------------- /app/lint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Keep classes which may be lost by Proguard or R8 when using Answer Bot 2 | 3 | -keep class zendesk.core.AuthenticationRequestWrapper { *; } 4 | -keep class zendesk.core.PushRegistrationRequestWrapper { *; } 5 | -keep class zendesk.core.PushRegistrationRequest { *; } 6 | -keep class zendesk.core.PushRegistrationResponse { *; } 7 | -keep class zendesk.core.ApiAnonymousIdentity { *; } 8 | 9 | -keep class zendesk.support.CreateRequestWrapper { *; } 10 | -keep class zendesk.support.Comment { *; } 11 | 12 | -keep class zendesk.answerbot.Deflection { *; } 13 | -keep class zendesk.answerbot.AnswerBotSettings { *; } 14 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/zendesk/rememberthedate/rememberthedate/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.zendesk.rememberthedate.rememberthedate; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 27 | 28 | 31 | 32 | 35 | 38 | 39 | 42 | 43 | 44 | 47 | 48 | 49 | 50 | 51 | 54 | 55 | 56 | 57 | 58 | 59 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zendesk/sdk_demo_app_android/51775d34fea8e3e3d97be83daa681c6aa50ec3b4/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/com/zendesk/rememberthedate/Constants.java: -------------------------------------------------------------------------------- 1 | package com.zendesk.rememberthedate; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.Locale; 5 | 6 | public final class Constants { 7 | public final static SimpleDateFormat HUMAN_READABLE_DATETIME = new SimpleDateFormat("EEEE, dd MMMM hh:mm a", Locale.getDefault()); 8 | public final static SimpleDateFormat HUMAN_READABLE_DATE = new SimpleDateFormat("EEEE, dd MMMM", Locale.getDefault()); 9 | public final static SimpleDateFormat HUMAN_READABLE_TIME = new SimpleDateFormat("hh:mm a ", Locale.getDefault()); 10 | public final static String ISO8601_DATE_PATTERN = "yyyy-MM-dd'T'HH:mm:ss'Z'"; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/zendesk/rememberthedate/Global.java: -------------------------------------------------------------------------------- 1 | package com.zendesk.rememberthedate; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import androidx.annotation.Nullable; 6 | import android.util.Log; 7 | 8 | import com.google.firebase.FirebaseApp; 9 | import com.google.firebase.FirebaseOptions; 10 | import com.squareup.picasso.Picasso; 11 | import com.zendesk.logger.Logger; 12 | import com.zendesk.rememberthedate.storage.AppStorage; 13 | import com.zendesk.util.StringUtils; 14 | 15 | import zendesk.answerbot.AnswerBot; 16 | import zendesk.chat.Chat; 17 | import zendesk.core.Zendesk; 18 | import zendesk.support.Guide; 19 | import zendesk.support.Support; 20 | 21 | public class Global extends Application { 22 | 23 | public final static String LOG_TAG = "RTD"; 24 | 25 | private AppStorage storage; 26 | 27 | public static AppStorage getStorage(@Nullable Context context) { 28 | if (context != null && context.getApplicationContext() instanceof Global) { 29 | return ((Global) context.getApplicationContext()).storage; 30 | } 31 | 32 | throw new IllegalArgumentException("Can't find global Application"); 33 | } 34 | 35 | @Override 36 | public void onCreate() { 37 | super.onCreate(); 38 | 39 | initialiseFcm(); 40 | 41 | storage = new AppStorage(this); 42 | 43 | Picasso.with(this).setLoggingEnabled(true); 44 | // Enable logging in Support and Chat SDK 45 | Logger.setLoggable(true); 46 | 47 | // Init Support SDK 48 | Zendesk.INSTANCE.init(this, getResources().getString(R.string.zd_url), 49 | getResources().getString(R.string.zd_appid), 50 | getResources().getString(R.string.zd_oauth)); 51 | Support.INSTANCE.init(Zendesk.INSTANCE); 52 | AnswerBot.INSTANCE.init(Zendesk.INSTANCE, Guide.INSTANCE); 53 | 54 | // Init Chat SDK 55 | if ("replace_me_chat_account_id".equals(getString(R.string.zopim_account_id))) { 56 | Log.w(LOG_TAG, "========================================================================================================================="); 57 | Log.w(LOG_TAG, "Zendesk chat is not connected to an account, if you wish to try chat please add your Zendesk Chat account key to 'zd.xml'"); 58 | Log.w(LOG_TAG, "========================================================================================================================="); 59 | } 60 | Chat.INSTANCE.init(this, getString(R.string.zopim_account_id)); 61 | } 62 | 63 | private void initialiseFcm() { 64 | String googleApiKey = getString(R.string.google_api_key); 65 | String fcmApplicationId = getString(R.string.fcm_application_id); 66 | 67 | if (StringUtils.isEmpty(googleApiKey) || StringUtils.isEmpty(fcmApplicationId)) { 68 | Log.w(LOG_TAG, "============================================================================================================"); 69 | Log.w(LOG_TAG, "Google API key and FCM application ID are not configured. If you wish to use push notifications, please add "); 70 | Log.w(LOG_TAG, "values for 'zdGoogleApiKey' and 'zdFcmApplicationId' to your 'gradle.properties'."); 71 | Log.w(LOG_TAG, "============================================================================================================"); 72 | } else { 73 | FirebaseApp.initializeApp(this, new FirebaseOptions.Builder() 74 | .setApiKey(googleApiKey) 75 | .setApplicationId(fcmApplicationId) 76 | .build()); 77 | } 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /app/src/main/java/com/zendesk/rememberthedate/LocalNotification.java: -------------------------------------------------------------------------------- 1 | package com.zendesk.rememberthedate; 2 | 3 | import android.app.Notification; 4 | import android.app.PendingIntent; 5 | import android.content.BroadcastReceiver; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import androidx.core.app.NotificationCompat; 9 | import androidx.core.app.NotificationManagerCompat; 10 | 11 | import com.zendesk.rememberthedate.ui.MainActivity; 12 | import com.zendesk.util.StringUtils; 13 | 14 | public class LocalNotification extends BroadcastReceiver { 15 | 16 | @Override 17 | public void onReceive(Context context, Intent intent) { 18 | final String message = intent.getExtras().getString("message", StringUtils.EMPTY_STRING); 19 | final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, new Intent(context, MainActivity.class), 0); 20 | final String channelId = context.getResources().getString(R.string.app_name); 21 | 22 | final Notification notification = new NotificationCompat.Builder(context, channelId) 23 | .setContentTitle(context.getString(R.string.app_name)) 24 | .setSmallIcon(R.drawable.ic_date_24dp) 25 | .setContentText(message) 26 | .setContentIntent(pendingIntent) 27 | .setAutoCancel(true) 28 | .build(); 29 | 30 | NotificationManagerCompat.from(context) 31 | .notify(0, notification); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/zendesk/rememberthedate/model/DateModel.java: -------------------------------------------------------------------------------- 1 | package com.zendesk.rememberthedate.model; 2 | 3 | import java.util.Date; 4 | 5 | public class DateModel { 6 | 7 | private final String title; 8 | private final long dateInMillis; 9 | 10 | public DateModel(String title, long dateInMillis ) { 11 | this.title = title; 12 | this.dateInMillis = dateInMillis; 13 | } 14 | 15 | public String getTitle() { 16 | return title; 17 | } 18 | 19 | public long getDateInMillis(){ 20 | return dateInMillis; 21 | } 22 | 23 | public Date getDate() { 24 | return new Date(dateInMillis); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/zendesk/rememberthedate/model/UserProfile.java: -------------------------------------------------------------------------------- 1 | package com.zendesk.rememberthedate.model; 2 | 3 | import android.net.Uri; 4 | 5 | /** 6 | * Data model for a user profile 7 | */ 8 | public class UserProfile { 9 | 10 | private final String name; 11 | private final String email; 12 | private final Uri uri; 13 | 14 | public UserProfile(String name, String email, Uri uri) { 15 | this.name = name; 16 | this.email = email; 17 | this.uri = uri; 18 | } 19 | 20 | public String getName() { 21 | return name; 22 | } 23 | 24 | public String getEmail() { 25 | return email; 26 | } 27 | 28 | public Uri getUri() { 29 | return uri; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/zendesk/rememberthedate/push/PushUtils.java: -------------------------------------------------------------------------------- 1 | package com.zendesk.rememberthedate.push; 2 | 3 | import android.app.Activity; 4 | import android.util.Log; 5 | import android.widget.Toast; 6 | 7 | import com.google.android.gms.common.ConnectionResult; 8 | import com.google.android.gms.common.GoogleApiAvailability; 9 | import com.google.firebase.iid.FirebaseInstanceId; 10 | import com.zendesk.rememberthedate.R; 11 | import com.zendesk.util.StringUtils; 12 | 13 | import zendesk.core.ProviderStore; 14 | import zendesk.core.Zendesk; 15 | 16 | import static com.zendesk.rememberthedate.Global.LOG_TAG; 17 | 18 | public class PushUtils { 19 | 20 | /** 21 | * Check if play services are installed and use able. 22 | * 23 | * @param activity An activity 24 | */ 25 | public static void checkPlayServices(Activity activity) { 26 | final GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance(); 27 | int errorCode = apiAvailability.isGooglePlayServicesAvailable(activity); 28 | if (errorCode != ConnectionResult.SUCCESS) { 29 | if (apiAvailability.isUserResolvableError(errorCode)) { 30 | apiAvailability.makeGooglePlayServicesAvailable(activity); 31 | } else { 32 | Toast.makeText(activity, R.string.push_error_device_not_compatible, Toast.LENGTH_SHORT).show(); 33 | } 34 | } 35 | } 36 | 37 | public static void registerWithZendesk() { 38 | final ProviderStore providerStore = Zendesk.INSTANCE.provider(); 39 | 40 | if (providerStore == null) { 41 | Log.e(LOG_TAG, "Zendesk Support SDK is not initialized"); 42 | return; 43 | } 44 | 45 | final String pushToken = FirebaseInstanceId.getInstance().getToken(); 46 | if (StringUtils.hasLength(pushToken)) { 47 | providerStore.pushRegistrationProvider().registerWithDeviceIdentifier(pushToken, null); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/zendesk/rememberthedate/push/ZendeskFirebaseInstanceIdService.java: -------------------------------------------------------------------------------- 1 | package com.zendesk.rememberthedate.push; 2 | 3 | import android.util.Log; 4 | 5 | import com.google.firebase.iid.FirebaseInstanceIdService; 6 | 7 | import static com.zendesk.rememberthedate.Global.LOG_TAG; 8 | 9 | 10 | public class ZendeskFirebaseInstanceIdService extends FirebaseInstanceIdService { 11 | 12 | @Override 13 | public void onTokenRefresh() { 14 | Log.d(LOG_TAG, "Firebase token updated"); 15 | PushUtils.registerWithZendesk(); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/zendesk/rememberthedate/push/ZendeskFirebaseMessagingService.java: -------------------------------------------------------------------------------- 1 | package com.zendesk.rememberthedate.push; 2 | 3 | import android.app.Notification; 4 | import android.app.NotificationChannel; 5 | import android.app.NotificationManager; 6 | import android.app.PendingIntent; 7 | import android.content.Context; 8 | import android.content.Intent; 9 | import android.graphics.Color; 10 | import androidx.core.app.NotificationCompat; 11 | 12 | import com.google.firebase.messaging.FirebaseMessagingService; 13 | import com.google.firebase.messaging.RemoteMessage; 14 | import com.zendesk.rememberthedate.R; 15 | import com.zendesk.rememberthedate.ui.MainActivity; 16 | import com.zendesk.util.StringUtils; 17 | 18 | import zendesk.core.Zendesk; 19 | import zendesk.support.Support; 20 | import zendesk.support.request.RequestActivity; 21 | 22 | public class ZendeskFirebaseMessagingService extends FirebaseMessagingService { 23 | 24 | private static final int NOTIFICATION_ID = 134345; 25 | private static final String ZD_REQUEST_ID_KEY = "zendesk_sdk_request_id"; 26 | private static final String ZD_MESSAGE_KEY = "message"; 27 | 28 | @Override 29 | public void onMessageReceived(RemoteMessage remoteMessage) { 30 | super.onMessageReceived(remoteMessage); 31 | 32 | final String requestId = remoteMessage.getData().get(ZD_REQUEST_ID_KEY); 33 | final String message = remoteMessage.getData().get(ZD_MESSAGE_KEY); 34 | 35 | if (StringUtils.hasLengthMany(requestId, message)) { 36 | handleZendeskSdkPush(requestId, message); 37 | } 38 | } 39 | 40 | private void handleZendeskSdkPush(String requestId, String message) { 41 | // Initialise the SDK 42 | // This IntentService could be called and any point. So, if the main app was killed, 43 | // there won't be any Zendesk login information. Moreover, we presume at this point, that 44 | // an valid identity was set. 45 | if (!Zendesk.INSTANCE.isInitialized()) { 46 | Context context = getApplicationContext(); 47 | Zendesk.INSTANCE.init(context, context.getString(R.string.zd_url), context.getString(R.string.zd_appid), context.getString(R.string.zd_oauth)); 48 | Support.INSTANCE.init(Zendesk.INSTANCE); 49 | } 50 | 51 | // If the Fragment with the pushed request id is visible, 52 | // this will cause a reload of the screen. 53 | // #refreshRequest(id) will return true if it was successful. 54 | if (Support.INSTANCE.refreshRequest(requestId, getApplicationContext())) { 55 | return; 56 | } 57 | 58 | showNotification(requestId, message); 59 | } 60 | 61 | private void showNotification(String requestId, String message) { 62 | final NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 63 | final String channelId = getApplicationContext().getResources().getString(R.string.app_name); 64 | createNotificationChannel(notificationManager, channelId); 65 | 66 | final Intent requestIntent = getDeepLinkIntent(requestId); 67 | final PendingIntent contentIntent = PendingIntent.getBroadcast(getApplicationContext(), 1, requestIntent, PendingIntent.FLAG_UPDATE_CURRENT); 68 | final Notification notification = new NotificationCompat.Builder(getApplicationContext(), channelId) 69 | .setSmallIcon(R.drawable.ic_date) 70 | .setDefaults(Notification.DEFAULT_ALL) 71 | .setContentTitle(getResources().getString(R.string.app_name)) 72 | .setContentText(message) 73 | .setAutoCancel(true) 74 | .setContentIntent(contentIntent) 75 | .build(); 76 | 77 | notificationManager.notify(NOTIFICATION_ID, notification); 78 | } 79 | 80 | private void createNotificationChannel(NotificationManager notificationManager, String channelId) { 81 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { 82 | // Create the notification channel. As per the documentation, "Attempting to create an 83 | // existing notification channel with its original values performs no operation, so it's safe 84 | // to perform the above sequence of steps when starting an app." 85 | // The user-visible name of the channel. 86 | CharSequence name = getString(R.string.app_name); 87 | // The user-visible description of the channel. 88 | String description = getString(R.string.push_notification_fallback_title); 89 | int importance = NotificationManager.IMPORTANCE_HIGH; 90 | NotificationChannel channel = new NotificationChannel(channelId, name, importance); 91 | // Configure the notification channel. 92 | channel.setDescription(description); 93 | channel.enableLights(true); 94 | // Sets the notification light color for notifications posted to this 95 | // channel, if the device supports this feature. 96 | channel.setLightColor(Color.RED); 97 | channel.enableVibration(true); 98 | channel.setVibrationPattern(new long[]{100, 200, 100, 200}); 99 | notificationManager.createNotificationChannel(channel); 100 | } 101 | } 102 | 103 | private Intent getDeepLinkIntent(String requestId) { 104 | 105 | // Utilize SDK's deep linking functionality to get an Intent which opens a specified request. 106 | // We'd like to achieve a certain behaviour, if the user navigates back from the request activity. 107 | // Expected: [Request] --> [Request list] -> [MainActivity | HelpFragment] 108 | 109 | 110 | // ZendeskDeepLinking.INSTANCE.getRequestIntent automatically pushed the request list activity into 111 | // backstack. So we just have to add MainActivity. 112 | 113 | final Intent mainActivity = new Intent(getApplicationContext(), MainActivity.class); 114 | mainActivity.putExtra(MainActivity.EXTRA_VIEWPAGER_POSITION, MainActivity.POS_HELP); 115 | 116 | return RequestActivity.builder() 117 | .withRequestId(requestId) 118 | .deepLinkIntent(getApplicationContext(), mainActivity); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /app/src/main/java/com/zendesk/rememberthedate/storage/AppStorage.java: -------------------------------------------------------------------------------- 1 | package com.zendesk.rememberthedate.storage; 2 | 3 | 4 | import android.content.Context; 5 | import android.content.SharedPreferences; 6 | import android.net.Uri; 7 | 8 | import com.google.gson.Gson; 9 | import com.google.gson.GsonBuilder; 10 | import com.google.gson.reflect.TypeToken; 11 | import com.zendesk.rememberthedate.Constants; 12 | import com.zendesk.rememberthedate.model.DateModel; 13 | import com.zendesk.rememberthedate.model.UserProfile; 14 | import com.zendesk.util.StringUtils; 15 | 16 | import java.lang.reflect.Type; 17 | import java.util.Collections; 18 | import java.util.HashMap; 19 | import java.util.Map; 20 | 21 | public class AppStorage { 22 | 23 | private static final String REMEMBER_THE_DATE_STORE = "rtd_dates"; 24 | 25 | // Profile keys 26 | private static final String NAME_KEY = "name"; 27 | private static final String EMAIL_KEY = "email"; 28 | private static final String IMAGE_DATA_KEY = "image_data"; 29 | 30 | // Dates 31 | private static final String DATES = "dates"; 32 | 33 | private final Gson gson; 34 | private final SharedPreferences storage; 35 | 36 | public AppStorage(Context context) { 37 | this.storage = context.getSharedPreferences(REMEMBER_THE_DATE_STORE, Context.MODE_PRIVATE); 38 | this.gson = new Gson(); 39 | } 40 | 41 | public void storeUserProfile(UserProfile user) { 42 | String avatarUri = null; 43 | 44 | if (user.getUri() != null) { 45 | avatarUri = user.getUri().toString(); 46 | } 47 | 48 | storage.edit() 49 | .putString(NAME_KEY, user.getName()) 50 | .putString(EMAIL_KEY, user.getEmail()) 51 | .putString(IMAGE_DATA_KEY, avatarUri) 52 | .apply(); 53 | } 54 | 55 | public UserProfile getUserProfile(){ 56 | final String name = storage.getString(NAME_KEY, ""); 57 | final String email = storage.getString(EMAIL_KEY, ""); 58 | final String avatarUri = storage.getString(IMAGE_DATA_KEY, ""); 59 | 60 | Uri uri = null; 61 | if (StringUtils.hasLength(avatarUri)) { 62 | uri = Uri.parse(avatarUri); 63 | } 64 | 65 | return new UserProfile(name, email, uri); 66 | } 67 | 68 | public void storeMapData(Map inputMap){ 69 | 70 | String jsonString = gson.toJson(inputMap); 71 | 72 | storage.edit() 73 | .putString(DATES, jsonString) 74 | .apply(); 75 | } 76 | 77 | public Map loadMapData(){ 78 | final String jsonString = storage.getString(DATES, null); 79 | if (jsonString != null) { 80 | try{ 81 | Type dateModelType = new TypeToken>(){}.getType(); 82 | return gson.fromJson(jsonString, dateModelType); 83 | }catch (Exception e){ 84 | e.printStackTrace(); 85 | } 86 | } 87 | return Collections.emptyMap(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /app/src/main/java/com/zendesk/rememberthedate/ui/CreateProfileActivity.java: -------------------------------------------------------------------------------- 1 | package com.zendesk.rememberthedate.ui; 2 | 3 | 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.net.Uri; 7 | import android.os.Bundle; 8 | import android.text.Editable; 9 | import android.text.TextWatcher; 10 | import android.view.Menu; 11 | import android.view.MenuItem; 12 | import android.widget.EditText; 13 | import android.widget.ImageView; 14 | import android.widget.Toast; 15 | 16 | import androidx.appcompat.app.AppCompatActivity; 17 | import androidx.appcompat.widget.Toolbar; 18 | 19 | import com.zendesk.rememberthedate.R; 20 | import com.zendesk.rememberthedate.model.UserProfile; 21 | import com.zendesk.rememberthedate.push.PushUtils; 22 | import com.zendesk.rememberthedate.storage.AppStorage; 23 | import com.zendesk.util.StringUtils; 24 | 25 | import java.util.List; 26 | 27 | import zendesk.belvedere.Belvedere; 28 | import zendesk.belvedere.Callback; 29 | import zendesk.belvedere.MediaResult; 30 | import zendesk.chat.Chat; 31 | import zendesk.chat.Providers; 32 | import zendesk.chat.VisitorInfo; 33 | import zendesk.core.JwtIdentity; 34 | import zendesk.core.Zendesk; 35 | 36 | import static com.zendesk.rememberthedate.Global.getStorage; 37 | 38 | 39 | public class CreateProfileActivity extends AppCompatActivity { 40 | 41 | 42 | private AppStorage storage; 43 | private Uri uri; 44 | private ImageView imageView; 45 | private EditText emailText, nameText; 46 | 47 | private boolean isProfileSettable; 48 | 49 | static void start(Context context) { 50 | context.startActivity(new Intent(context, CreateProfileActivity.class)); 51 | } 52 | 53 | @Override 54 | protected void onCreate(Bundle savedInstanceState) { 55 | super.onCreate(savedInstanceState); 56 | setContentView(R.layout.activity_create_profile); 57 | 58 | Toolbar toolbar = findViewById(R.id.toolbar); 59 | setSupportActionBar(toolbar); 60 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 61 | 62 | bindViews(); 63 | storage = getStorage(getApplicationContext()); 64 | uri = storage.getUserProfile().getUri(); 65 | } 66 | 67 | private void bindViews() { 68 | imageView = findViewById(R.id.imageButton); 69 | imageView.setOnLongClickListener(v -> { 70 | if (imageView.getDrawable() != null) { 71 | } 72 | return true; 73 | }); 74 | nameText = findViewById(R.id.nameText); 75 | 76 | emailText = findViewById(R.id.emailText); 77 | emailText.addTextChangedListener(new TextWatcher() { 78 | @Override 79 | public void beforeTextChanged(CharSequence s, int start, int count, int after) { 80 | } 81 | 82 | @Override 83 | public void onTextChanged(CharSequence s, int start, int before, int count) { 84 | 85 | } 86 | 87 | @Override 88 | public void afterTextChanged(Editable s) { 89 | if (s.length() == 0) { 90 | isProfileSettable = false; 91 | invalidateOptionsMenu(); 92 | } else { 93 | isProfileSettable = true; 94 | invalidateOptionsMenu(); 95 | } 96 | } 97 | }); 98 | } 99 | 100 | @Override 101 | protected void onResume() { 102 | super.onResume(); 103 | showStoredProfile(); 104 | } 105 | 106 | private void showStoredProfile() { 107 | UserProfile userProfile = storage.getUserProfile(); 108 | 109 | imageView.setOnClickListener(v -> Belvedere.from(getApplicationContext()) 110 | .document() 111 | .contentType("image/*").allowMultiple(false) 112 | .open(CreateProfileActivity.this)); 113 | 114 | if (userProfile.getUri() != null) { 115 | ImageUtils.loadProfilePicture(getApplicationContext(), userProfile.getUri(), imageView); 116 | } 117 | 118 | if (!StringUtils.hasLength(nameText.getText().toString())) { 119 | nameText.setText(userProfile.getName()); 120 | } 121 | 122 | if (!StringUtils.hasLength(emailText.getText().toString())) { 123 | emailText.setText(userProfile.getEmail()); 124 | } 125 | } 126 | 127 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 128 | super.onActivityResult(requestCode, resultCode, data); 129 | Belvedere.from(getApplicationContext()).getFilesFromActivityOnResult(requestCode, resultCode, data, new Callback>() { 130 | @Override 131 | public void success(List result) { 132 | if (result.size() > 0) { 133 | uri = result.get(0).getUri(); 134 | ImageUtils.loadProfilePicture(getApplicationContext(), uri, imageView); 135 | } 136 | } 137 | }); 138 | } 139 | 140 | @Override 141 | public boolean onCreateOptionsMenu(Menu menu) { 142 | // Inflate the menu; this adds items to the action bar if it is present. 143 | getMenuInflater().inflate(R.menu.profile_menu, menu); 144 | MenuItem saveItem = menu.findItem(R.id.action_edit); 145 | saveItem.setIcon(getResources().getDrawable(R.drawable.ic_save)); 146 | return true; 147 | } 148 | 149 | @Override 150 | public boolean onPrepareOptionsMenu(Menu menu) { 151 | MenuItem item = menu.findItem(R.id.action_edit); 152 | if (isProfileSettable) { 153 | item.setIcon(getResources().getDrawable(R.drawable.ic_save)); 154 | item.setEnabled(true); 155 | } else { 156 | item.setIcon(getResources().getDrawable(R.drawable.ic_save_diasbled)); 157 | item.setEnabled(false); 158 | 159 | Toast.makeText(getApplicationContext(), 160 | getResources().getString(R.string.fragment_profile_invalid_email), 161 | Toast.LENGTH_LONG) 162 | .show(); 163 | } 164 | return super.onPrepareOptionsMenu(menu); 165 | } 166 | 167 | @Override 168 | public boolean onOptionsItemSelected(MenuItem item) { 169 | switch (item.getItemId()) { 170 | case android.R.id.home: 171 | onBackPressed(); 172 | return true; 173 | 174 | case R.id.action_edit: 175 | final String email = emailText.getText().toString(); 176 | final String name = nameText.getText().toString(); 177 | 178 | if (isProfileSettable) { 179 | final UserProfile user = new UserProfile(name, email, uri); 180 | storage.storeUserProfile(user); 181 | updateIdentityInSdks(user); 182 | finish(); 183 | } 184 | return true; 185 | 186 | default: 187 | return super.onOptionsItemSelected(item); 188 | } 189 | } 190 | 191 | private void updateIdentityInSdks(UserProfile user) { 192 | 193 | // Update identity in Zendesk Support SDK 194 | Zendesk.INSTANCE.setIdentity(new JwtIdentity(user.getEmail())); 195 | 196 | // Register for push 197 | PushUtils.registerWithZendesk(); 198 | 199 | // Init Chat SDK with an identity 200 | final VisitorInfo.Builder build = VisitorInfo.builder() 201 | .withEmail(user.getEmail()); 202 | 203 | if (StringUtils.hasLength(user.getName())) { 204 | build.withName(user.getName()); 205 | } 206 | 207 | Providers providers = Chat.INSTANCE.providers(); 208 | if (providers != null) { 209 | providers.profileProvider().setVisitorInfo(build.build(), null); 210 | } 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /app/src/main/java/com/zendesk/rememberthedate/ui/DateFragment.java: -------------------------------------------------------------------------------- 1 | package com.zendesk.rememberthedate.ui; 2 | 3 | import android.app.AlarmManager; 4 | import android.app.AlertDialog; 5 | import android.app.PendingIntent; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.os.Bundle; 9 | import androidx.annotation.NonNull; 10 | import androidx.fragment.app.Fragment; 11 | import androidx.recyclerview.widget.DividerItemDecoration; 12 | import androidx.recyclerview.widget.LinearLayoutManager; 13 | import androidx.recyclerview.widget.RecyclerView; 14 | import androidx.recyclerview.widget.RecyclerView.ViewHolder; 15 | import android.util.Log; 16 | import android.view.LayoutInflater; 17 | import android.view.View; 18 | import android.view.ViewGroup; 19 | import android.widget.ImageView; 20 | import android.widget.TextView; 21 | 22 | import com.zendesk.rememberthedate.Constants; 23 | import com.zendesk.rememberthedate.Global; 24 | import com.zendesk.rememberthedate.LocalNotification; 25 | import com.zendesk.rememberthedate.R; 26 | import com.zendesk.rememberthedate.model.DateModel; 27 | import com.zendesk.rememberthedate.storage.AppStorage; 28 | 29 | import java.util.ArrayList; 30 | import java.util.Date; 31 | import java.util.List; 32 | import java.util.Map; 33 | 34 | /** 35 | * A fragment representing a list of Items. 36 | */ 37 | public class DateFragment extends Fragment { 38 | 39 | public static final String FRAGMENT_TITLE = "Date"; 40 | 41 | public static DateFragment newInstance() { 42 | return new DateFragment(); 43 | } 44 | 45 | private AppStorage storage; 46 | private RecyclerView recyclerView; 47 | private DateAdapter dateAdapter; 48 | private ImageView imageView; 49 | private TextView textView; 50 | 51 | @Override 52 | public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 53 | final View view = inflater.inflate(R.layout.fragment_date, container, false); 54 | recyclerView = view.findViewById(R.id.date_list); 55 | imageView = view.findViewById(R.id.image_view); 56 | textView = view.findViewById(R.id.text_view); 57 | return view; 58 | } 59 | 60 | @Override 61 | public void onStart() { 62 | super.onStart(); 63 | storage = Global.getStorage(getActivity()); 64 | 65 | dateAdapter = new DateAdapter(new OnDateClickListener() { 66 | @Override 67 | public void onClick(DateModel item) { 68 | EditDateActivity.start(getActivity(), Long.toString(item.getDateInMillis())); 69 | } 70 | 71 | @Override 72 | public void onLongClick(DateModel item) { 73 | showRemoveDialog(item); 74 | } 75 | }); 76 | 77 | recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); 78 | recyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), 0)); 79 | recyclerView.setAdapter(dateAdapter); 80 | } 81 | 82 | @Override 83 | public void onResume() { 84 | super.onResume(); 85 | reloadAdapter(); 86 | } 87 | 88 | private void reloadAdapter() { 89 | final Map mapData = storage.loadMapData(); 90 | final List data = new ArrayList<>(); 91 | 92 | for (Map.Entry entry : mapData.entrySet()) { 93 | data.add(entry.getValue()); 94 | } 95 | 96 | if (data.size() > 0) { 97 | imageView.setVisibility(View.INVISIBLE); 98 | textView.setVisibility(View.INVISIBLE); 99 | recyclerView.setVisibility(View.VISIBLE); 100 | dateAdapter.update(data); 101 | 102 | } else { 103 | imageView.setVisibility(View.VISIBLE); 104 | textView.setVisibility(View.VISIBLE); 105 | recyclerView.setVisibility(View.INVISIBLE); 106 | } 107 | } 108 | 109 | private void showRemoveDialog(final DateModel item) { 110 | 111 | AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity()); 112 | 113 | // set title 114 | alertDialogBuilder 115 | .setTitle("Confirm") 116 | .setMessage("Remove this date?") 117 | .setCancelable(true) 118 | .setPositiveButton("Yes", (dialog, id) -> { 119 | //data.remove(arg2); 120 | 121 | long millis = item.getDateInMillis(); 122 | 123 | AlarmManager alarmManager = (AlarmManager) DateFragment.this.getActivity().getSystemService(Context.ALARM_SERVICE); 124 | Intent intent = new Intent(DateFragment.this.getActivity(), LocalNotification.class); 125 | intent.putExtra("message", item.getTitle()); 126 | 127 | PendingIntent pendingIntent = PendingIntent.getBroadcast(DateFragment.this.getActivity(), (int) millis, intent, PendingIntent.FLAG_ONE_SHOT); 128 | 129 | Map mapData = storage.loadMapData(); 130 | mapData.remove(Long.toString(item.getDateInMillis())); 131 | storage.storeMapData(mapData); 132 | 133 | alarmManager.cancel(pendingIntent); 134 | 135 | reloadAdapter(); 136 | 137 | }) 138 | .setNegativeButton("No", (dialogInterface, i) -> dialogInterface.dismiss()); 139 | 140 | // create alert dialog 141 | alertDialogBuilder.create().show(); 142 | } 143 | 144 | private static class DateAdapter extends RecyclerView.Adapter { 145 | 146 | private final OnDateClickListener clickListener; 147 | private List items; 148 | 149 | private DateAdapter(OnDateClickListener clickListener) { 150 | this.clickListener = clickListener; 151 | this.items = new ArrayList<>(); 152 | } 153 | 154 | void update(List items) { 155 | this.items = new ArrayList<>(items); 156 | notifyDataSetChanged(); 157 | } 158 | 159 | @NonNull 160 | @Override 161 | public MyViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, int viewType) { 162 | final LayoutInflater li = LayoutInflater.from(parent.getContext()); 163 | final View view = li.inflate(R.layout.date_cell, parent, false); 164 | return new MyViewHolder(view) { 165 | }; 166 | } 167 | 168 | @Override 169 | public void onBindViewHolder(@NonNull final MyViewHolder holder, int position) { 170 | Log.i("DateAdapter", items.get(position).getTitle()); 171 | DateModel item = items.get(position); 172 | 173 | holder.bindData(item); 174 | 175 | holder.itemView.setOnClickListener(v -> clickListener.onClick(item)); 176 | holder.itemView.setOnLongClickListener(v -> { 177 | clickListener.onLongClick(item); 178 | return true; 179 | }); 180 | } 181 | 182 | @Override 183 | public int getItemCount() { 184 | return items.size(); 185 | } 186 | 187 | public static class MyViewHolder extends ViewHolder { 188 | final TextView titleView; 189 | final TextView dateView; 190 | 191 | MyViewHolder(View view) { 192 | super(view); 193 | this.titleView = view.findViewById(R.id.title_view); 194 | this.dateView = view.findViewById(R.id.date_view); 195 | } 196 | 197 | private void bindData(DateModel dateModel) { 198 | titleView.setText(dateModel.getTitle()); 199 | Date date = dateModel.getDate(); 200 | dateView.setText(Constants.HUMAN_READABLE_DATETIME.format(date)); 201 | } 202 | } 203 | 204 | } 205 | 206 | interface OnDateClickListener { 207 | void onClick(DateModel item); 208 | 209 | void onLongClick(DateModel item); 210 | } 211 | 212 | } 213 | -------------------------------------------------------------------------------- /app/src/main/java/com/zendesk/rememberthedate/ui/EditDateActivity.java: -------------------------------------------------------------------------------- 1 | package com.zendesk.rememberthedate.ui; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import androidx.appcompat.app.AppCompatActivity; 7 | import android.view.Menu; 8 | import android.view.MenuItem; 9 | import android.widget.EditText; 10 | import android.widget.TextView; 11 | import android.widget.Toast; 12 | 13 | import com.zendesk.rememberthedate.Constants; 14 | import com.zendesk.rememberthedate.Global; 15 | import com.zendesk.rememberthedate.R; 16 | import com.zendesk.rememberthedate.model.DateModel; 17 | import com.zendesk.rememberthedate.storage.AppStorage; 18 | 19 | import java.util.Calendar; 20 | import java.util.Date; 21 | import java.util.GregorianCalendar; 22 | import java.util.HashMap; 23 | 24 | public class EditDateActivity extends AppCompatActivity { 25 | 26 | private EditText title; 27 | private TextView dateView, timeView; 28 | private AppStorage storage; 29 | private Calendar currentlySelectedDate; 30 | private Date currentlySelectedTime = null; 31 | private String key = null; 32 | 33 | public static void start(Context context) { 34 | context.startActivity(new Intent(context, EditDateActivity.class)); 35 | } 36 | 37 | static void start(Context context, String id) { 38 | final Intent intent = new Intent(context, EditDateActivity.class); 39 | intent.putExtra("key", id); 40 | context.startActivity(intent); 41 | } 42 | 43 | @Override 44 | protected void onCreate(Bundle savedInstanceState) { 45 | super.onCreate(savedInstanceState); 46 | setContentView(R.layout.activity_edit_date); 47 | 48 | bindViews(); 49 | 50 | setSupportActionBar(findViewById(R.id.toolbar)); 51 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 52 | 53 | if (getIntent().getExtras() != null) { 54 | key = getIntent().getExtras().getString("key"); 55 | } 56 | 57 | storage = Global.getStorage(getApplicationContext()); 58 | 59 | DateModel selectedDate = storage.loadMapData().get(key); 60 | if (selectedDate != null) { 61 | Date date = selectedDate.getDate(); 62 | currentlySelectedDate = new GregorianCalendar(); 63 | currentlySelectedDate.setTime(date); 64 | currentlySelectedTime = date; 65 | 66 | 67 | String dateString = Constants.HUMAN_READABLE_DATE.format(date); 68 | String timeString = Constants.HUMAN_READABLE_TIME.format(date); 69 | 70 | title.setText(selectedDate.getTitle()); 71 | dateView.setText(dateString); 72 | timeView.setText(timeString); 73 | } 74 | } 75 | 76 | private void bindViews() { 77 | title = findViewById(R.id.add_title); 78 | 79 | dateView = findViewById(R.id.add_date); 80 | dateView.setOnClickListener(v -> SetDateActivity.startForResult(this, SetDateActivity.REQUEST_CODE)); 81 | 82 | timeView = findViewById(R.id.add_time); 83 | timeView.setOnClickListener(v -> SetTimeActivity.startForResult(this, SetTimeActivity.REQUEST_CODE)); 84 | } 85 | 86 | @Override 87 | public boolean onCreateOptionsMenu(Menu menu) { 88 | getMenuInflater().inflate(R.menu.menu_edit_date, menu); 89 | return true; 90 | } 91 | 92 | @Override 93 | public boolean onOptionsItemSelected(MenuItem item) { 94 | HashMap dateMap = new HashMap<>(storage.loadMapData()); 95 | 96 | switch (item.getItemId()) { 97 | case R.id.action_save: 98 | 99 | if (title.getText().toString().matches("")) { 100 | Toast.makeText(this, "Enter a title to save", Toast.LENGTH_LONG).show(); 101 | return true; 102 | } 103 | if (currentlySelectedTime == null || currentlySelectedDate == null) { 104 | Toast.makeText(this, 105 | "Enter date and time field to save", 106 | Toast.LENGTH_LONG) 107 | .show(); 108 | return true; 109 | } 110 | 111 | Calendar newCalendar = new GregorianCalendar(currentlySelectedDate.get(Calendar.YEAR), 112 | currentlySelectedDate.get(Calendar.MONTH), 113 | currentlySelectedDate.get(Calendar.DAY_OF_MONTH), 114 | currentlySelectedTime.getHours(), 115 | currentlySelectedTime.getMinutes()); 116 | 117 | long dateLong = newCalendar.getTimeInMillis(); 118 | String calendarKey = Long.toString(dateLong); 119 | 120 | // Delete previous instance of item, if changed 121 | if (dateMap.containsKey(key)) { 122 | dateMap.remove(key); 123 | } 124 | 125 | DateModel dateModel = new DateModel(title.getText().toString(), dateLong); 126 | dateMap.put(calendarKey, dateModel); 127 | 128 | storage.storeMapData(dateMap); 129 | finish(); 130 | return true; 131 | 132 | case R.id.action_delete: 133 | if (dateMap.containsKey(key)) { 134 | dateMap.remove(key); 135 | storage.storeMapData(dateMap); 136 | Toast.makeText(this, title.getText().toString() + " deleted.", Toast.LENGTH_LONG).show(); 137 | finish(); 138 | } else { 139 | Toast.makeText(this, 140 | "Date not stored yet.", 141 | Toast.LENGTH_LONG) 142 | .show(); 143 | } 144 | 145 | return true; 146 | 147 | default: 148 | return super.onOptionsItemSelected(item); 149 | } 150 | 151 | } 152 | 153 | @Override 154 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 155 | super.onActivityResult(requestCode, resultCode, data); 156 | 157 | if (requestCode == SetDateActivity.REQUEST_CODE) { 158 | if (resultCode == RESULT_OK) { 159 | currentlySelectedDate = SetDateActivity.getCalendarFromResultIntent(data); 160 | dateView.setText(Constants.HUMAN_READABLE_DATE.format(currentlySelectedDate.getTime())); 161 | } 162 | } 163 | 164 | if (requestCode == SetTimeActivity.REQUEST_CODE) { 165 | if (resultCode == RESULT_OK) { 166 | currentlySelectedTime = SetTimeActivity.getTimeFromResultIntent(data).getTime(); 167 | timeView.setText(Constants.HUMAN_READABLE_TIME.format(currentlySelectedTime)); 168 | } 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /app/src/main/java/com/zendesk/rememberthedate/ui/HelpFragment.java: -------------------------------------------------------------------------------- 1 | package com.zendesk.rememberthedate.ui; 2 | 3 | import android.app.AlertDialog; 4 | import android.content.Context; 5 | import android.os.Bundle; 6 | import android.os.Environment; 7 | import android.os.StatFs; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.Button; 12 | 13 | import androidx.annotation.NonNull; 14 | import androidx.fragment.app.Fragment; 15 | 16 | import com.zendesk.rememberthedate.BuildConfig; 17 | import com.zendesk.rememberthedate.R; 18 | import com.zendesk.util.FileUtils; 19 | 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | import java.util.Locale; 23 | 24 | import zendesk.answerbot.AnswerBotEngine; 25 | import zendesk.chat.ChatConfiguration; 26 | import zendesk.chat.ChatEngine; 27 | import zendesk.chat.PreChatFormFieldStatus; 28 | import zendesk.configurations.Configuration; 29 | import zendesk.core.Zendesk; 30 | import zendesk.messaging.MessagingActivity; 31 | import zendesk.support.CustomField; 32 | import zendesk.support.SupportEngine; 33 | import zendesk.support.guide.HelpCenterActivity; 34 | import zendesk.support.request.RequestActivity; 35 | import zendesk.support.requestlist.RequestListActivity; 36 | 37 | 38 | /** 39 | * A placeholder fragment containing a simple view. 40 | */ 41 | public class HelpFragment extends Fragment { 42 | 43 | public static final String FRAGMENT_TITLE = "Help"; 44 | 45 | private static final long TICKET_FORM_ID = 62599L; 46 | private static final long TICKET_FIELD_APP_VERSION = 24328555L; 47 | private static final long TICKET_FIELD_DEVICE_FREE_SPACE = 24274009L; 48 | 49 | private Button helpCenter, contactUs, requestList, chat; 50 | 51 | public static HelpFragment newInstance() { 52 | return new HelpFragment(); 53 | } 54 | 55 | @Override 56 | public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, 57 | Bundle savedInstanceState) { 58 | final View view = inflater.inflate(R.layout.fragment_main, container, false); 59 | helpCenter = view.findViewById(R.id.fragment_main_btn_knowledge_base); 60 | contactUs = view.findViewById(R.id.fragment_main_btn_contact_us); 61 | requestList = view.findViewById(R.id.fragment_main_btn_my_tickets); 62 | chat = view.findViewById(R.id.fragment_main_btn_chat); 63 | return view; 64 | } 65 | 66 | @Override 67 | public void onStart() { 68 | super.onStart(); 69 | final Context context = getActivity(); 70 | if (context != null) { 71 | helpCenter.setOnClickListener(new LoggedInClickListener(v -> openHelpCenter(context))); 72 | contactUs.setOnClickListener(new LoggedInClickListener(v -> openMessaging(context))); 73 | requestList.setOnClickListener(new LoggedInClickListener(v -> openRequestList(context))); 74 | chat.setOnClickListener(new LoggedInClickListener(v -> openChat(context))); 75 | } 76 | } 77 | 78 | private void openHelpCenter(Context context) { 79 | Configuration config = RequestActivity.builder() 80 | .withTicketForm(TICKET_FORM_ID, getCustomFields()) 81 | .config(); 82 | 83 | HelpCenterActivity.builder() 84 | .show(context, config); 85 | } 86 | 87 | private void openRequestList(Context context) { 88 | Configuration config = RequestActivity.builder() 89 | .withTicketForm(TICKET_FORM_ID, getCustomFields()) 90 | .config(); 91 | 92 | RequestListActivity.builder() 93 | .show(context, config); 94 | } 95 | 96 | private void openMessaging(Context context) { 97 | MessagingActivity.builder() 98 | .withEngines(AnswerBotEngine.engine(), SupportEngine.engine(), ChatEngine.engine()) 99 | .show(context); 100 | } 101 | 102 | private void openChat(Context context) { 103 | ChatConfiguration chatConfiguration = ChatConfiguration.builder() 104 | .withNameFieldStatus(PreChatFormFieldStatus.REQUIRED) 105 | .withEmailFieldStatus(PreChatFormFieldStatus.REQUIRED) 106 | .withPhoneFieldStatus(PreChatFormFieldStatus.OPTIONAL) 107 | .build(); 108 | 109 | MessagingActivity.builder() 110 | .withEngines(ChatEngine.engine()) 111 | .show(context, chatConfiguration); 112 | } 113 | 114 | private List getCustomFields() { 115 | final String appVersion = String.format( 116 | Locale.US, 117 | "version_%s", 118 | BuildConfig.VERSION_NAME 119 | ); 120 | 121 | final StatFs stat = new StatFs(Environment.getDataDirectory().getPath()); 122 | final long bytesAvailable = (long) stat.getBlockSize() * (long) stat.getAvailableBlocks(); 123 | final String freeSpace = FileUtils.humanReadableFileSize(bytesAvailable); 124 | 125 | 126 | final List customFields = new ArrayList<>(); 127 | customFields.add(new CustomField(TICKET_FIELD_APP_VERSION, appVersion)); 128 | customFields.add(new CustomField(TICKET_FIELD_DEVICE_FREE_SPACE, freeSpace)); 129 | 130 | return customFields; 131 | } 132 | 133 | private static class LoggedInClickListener implements View.OnClickListener { 134 | 135 | final private View.OnClickListener onClickListener; 136 | 137 | LoggedInClickListener(View.OnClickListener onClickListener) { 138 | this.onClickListener = onClickListener; 139 | } 140 | 141 | @Override 142 | public void onClick(View view) { 143 | if (Zendesk.INSTANCE.isInitialized() && Zendesk.INSTANCE.getIdentity() != null) { 144 | onClickListener.onClick(view); 145 | } else { 146 | showDialog(view.getContext()); 147 | } 148 | } 149 | 150 | private void showDialog(Context context) { 151 | AlertDialog.Builder builder = new AlertDialog.Builder(context); 152 | builder.setMessage(R.string.dialog_auth_title) 153 | .setPositiveButton(R.string.dialog_auth_positive_btn, (dialog, id) -> CreateProfileActivity.start(context)) 154 | .setNegativeButton(R.string.dialog_auth_negative_btn, null); 155 | builder.create().show(); 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /app/src/main/java/com/zendesk/rememberthedate/ui/ImageUtils.java: -------------------------------------------------------------------------------- 1 | package com.zendesk.rememberthedate.ui; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapShader; 6 | import android.graphics.Canvas; 7 | import android.graphics.Color; 8 | import android.graphics.Paint; 9 | import android.graphics.Path; 10 | import android.graphics.RectF; 11 | import android.graphics.Shader; 12 | import android.net.Uri; 13 | import androidx.annotation.DimenRes; 14 | import android.widget.ImageView; 15 | 16 | import com.squareup.picasso.Picasso; 17 | import com.squareup.picasso.Transformation; 18 | import com.zendesk.rememberthedate.R; 19 | 20 | import java.util.Locale; 21 | 22 | 23 | class ImageUtils { 24 | static void loadProfilePicture(Context context, Uri uri, ImageView imageView) { 25 | int diameter = context.getResources().getDimensionPixelSize(R.dimen.image_diameter); 26 | Picasso.with(context) 27 | .load(uri) 28 | .resize(diameter, diameter) 29 | .centerCrop() 30 | .transform(new RoundedTransformation(diameter / 2)) 31 | .into(imageView); 32 | } 33 | 34 | private static class RoundedTransformation implements Transformation { 35 | 36 | static Transformation get(Context context, @DimenRes int radius) { 37 | return new RoundedTransformation(context.getResources().getDimensionPixelOffset(radius)); 38 | } 39 | 40 | private final int radius; 41 | private final int strokeWidth; 42 | private final int strokeColor; 43 | 44 | private RoundedTransformation(int radius) { 45 | this(radius, -1); 46 | } 47 | 48 | private RoundedTransformation(int radius, int strokeWidth) { 49 | this.radius = radius; 50 | this.strokeColor = Color.TRANSPARENT; 51 | this.strokeWidth = strokeWidth; 52 | } 53 | 54 | @Override 55 | public Bitmap transform(final Bitmap source) { 56 | 57 | // draw border 58 | if (strokeWidth > 0) { 59 | final Canvas canvas = new Canvas(source); 60 | final Paint paint = strokePaint(); 61 | 62 | final Path circle = new Path(); 63 | circle.setFillType(Path.FillType.INVERSE_EVEN_ODD); 64 | 65 | final RectF borderMask = getMask(source.getWidth(), source.getHeight(), strokeWidth); 66 | circle.addRoundRect(borderMask, radius, radius, Path.Direction.CW); 67 | 68 | canvas.drawPath(circle, paint); 69 | } 70 | 71 | // cut out 72 | final Bitmap output = Bitmap.createBitmap(source.getWidth(), source.getHeight(), Bitmap.Config.ARGB_8888); 73 | final Canvas canvas = new Canvas(output); 74 | final Paint shapePaint = shapePaint(source); 75 | final RectF mask = getMask(source.getWidth(), source.getHeight(), 0); 76 | canvas.drawRoundRect(mask, radius, radius, shapePaint); 77 | 78 | if (source != output) { 79 | source.recycle(); 80 | } 81 | 82 | return output; 83 | } 84 | 85 | private RectF getMask(int width, int height, int offset) { 86 | return new RectF(offset, offset, width - offset, height - offset); 87 | } 88 | 89 | private Paint shapePaint(Bitmap source) { 90 | final Paint paint = new Paint(); 91 | paint.setAntiAlias(true); 92 | paint.setShader(new BitmapShader(source, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)); 93 | return paint; 94 | } 95 | 96 | private Paint strokePaint() { 97 | final Paint paint = new Paint(); 98 | paint.setAntiAlias(true); 99 | paint.setStyle(Paint.Style.FILL); 100 | paint.setColor(strokeColor); 101 | return paint; 102 | } 103 | 104 | @Override 105 | public String key() { 106 | String keyFormat = "rounded-%s-%s-%s"; 107 | 108 | return String.format(Locale.US, keyFormat, radius, strokeColor, strokeWidth); 109 | } 110 | } 111 | } 112 | 113 | -------------------------------------------------------------------------------- /app/src/main/java/com/zendesk/rememberthedate/ui/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.zendesk.rememberthedate.ui; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import com.google.android.material.floatingactionbutton.FloatingActionButton; 6 | import com.google.android.material.tabs.TabLayout; 7 | 8 | import androidx.annotation.NonNull; 9 | import androidx.fragment.app.Fragment; 10 | import androidx.fragment.app.FragmentManager; 11 | import androidx.fragment.app.FragmentPagerAdapter; 12 | import androidx.core.content.ContextCompat; 13 | import androidx.viewpager.widget.ViewPager; 14 | import androidx.appcompat.app.AppCompatActivity; 15 | import androidx.appcompat.widget.Toolbar; 16 | 17 | import com.zendesk.rememberthedate.Global; 18 | import com.zendesk.rememberthedate.R; 19 | import com.zendesk.rememberthedate.model.UserProfile; 20 | import com.zendesk.rememberthedate.push.PushUtils; 21 | import com.zendesk.rememberthedate.storage.AppStorage; 22 | import com.zendesk.util.StringUtils; 23 | 24 | import zendesk.chat.Chat; 25 | import zendesk.chat.VisitorInfo; 26 | 27 | 28 | public class MainActivity extends AppCompatActivity { 29 | 30 | private static final int POS_DATE_LIST = 0; 31 | private static final int POS_PROFILE = 1; 32 | public static final int POS_HELP = 2; 33 | 34 | public static final String EXTRA_VIEWPAGER_POSITION = "extra_viewpager_pos"; 35 | 36 | private AppStorage storage; 37 | private ViewPager viewPager; 38 | private TabLayout tabLayout; 39 | private Toolbar toolbar; 40 | private FloatingActionButton fab; 41 | 42 | @Override 43 | protected void onCreate(Bundle savedInstanceState) { 44 | super.onCreate(savedInstanceState); 45 | setContentView(R.layout.activity_main); 46 | bindViews(); 47 | setSupportActionBar(toolbar); 48 | 49 | storage = Global.getStorage(getApplicationContext()); 50 | initialiseChatSdk(); 51 | 52 | final SectionsPagerAdapter sectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager()); 53 | viewPager.addOnPageChangeListener(new PageChangeListener(this, fab)); 54 | viewPager.setAdapter(sectionsPagerAdapter); 55 | tabLayout.setupWithViewPager(viewPager); 56 | 57 | 58 | final int viewPagerPos = getIntent().getIntExtra(EXTRA_VIEWPAGER_POSITION, POS_DATE_LIST); 59 | viewPager.setCurrentItem(viewPagerPos); 60 | } 61 | 62 | private void bindViews() { 63 | toolbar = findViewById(R.id.toolbar); 64 | viewPager = findViewById(R.id.pager); 65 | tabLayout = findViewById(R.id.tabs); 66 | fab = findViewById(R.id.action_bar_add); 67 | } 68 | 69 | @Override 70 | protected void onResume() { 71 | super.onResume(); 72 | PushUtils.checkPlayServices(this); 73 | } 74 | 75 | private void initialiseChatSdk() { 76 | final UserProfile profile = storage.getUserProfile(); 77 | if (StringUtils.hasLength(profile.getEmail())) { 78 | // Init Zopim Visitor info 79 | final VisitorInfo.Builder build = VisitorInfo.builder() 80 | .withEmail(profile.getEmail()); 81 | 82 | if (StringUtils.hasLength(profile.getName())) { 83 | build.withName(profile.getName()); 84 | } 85 | 86 | Chat.INSTANCE.providers().profileProvider().setVisitorInfo(build.build(), null); 87 | } 88 | } 89 | 90 | private static class SectionsPagerAdapter extends FragmentPagerAdapter { 91 | 92 | SectionsPagerAdapter(FragmentManager fm) { 93 | super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); 94 | } 95 | 96 | @Override 97 | public Fragment getItem(int position) { 98 | switch (position) { 99 | case POS_DATE_LIST: { 100 | return DateFragment.newInstance(); 101 | } 102 | case POS_PROFILE: { 103 | return ProfileFragment.newInstance(); 104 | } 105 | case POS_HELP: { 106 | return HelpFragment.newInstance(); 107 | } 108 | } 109 | 110 | return null; 111 | } 112 | 113 | @Override 114 | public int getCount() { 115 | return 3; 116 | } 117 | 118 | @Override 119 | public CharSequence getPageTitle(int position) { 120 | switch (position) { 121 | case POS_DATE_LIST: { 122 | return DateFragment.FRAGMENT_TITLE; 123 | } 124 | case POS_PROFILE: { 125 | return ProfileFragment.FRAGMENT_TITLE; 126 | } 127 | case POS_HELP: { 128 | return HelpFragment.FRAGMENT_TITLE; 129 | } 130 | } 131 | return null; 132 | } 133 | } 134 | 135 | private static class PageChangeListener implements ViewPager.OnPageChangeListener { 136 | 137 | private final Activity context; 138 | private final FloatingActionButton fab; 139 | 140 | private PageChangeListener(Activity context, FloatingActionButton fab) { 141 | this.context = context; 142 | this.fab = fab; 143 | configureFabForDateList(); 144 | } 145 | 146 | @Override 147 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 148 | // Intentionally empty 149 | } 150 | 151 | @Override 152 | public void onPageSelected(int position) { 153 | switch (position) { 154 | 155 | case POS_DATE_LIST: 156 | configureFabForDateList(); 157 | break; 158 | 159 | default: 160 | fab.hide(); 161 | break; 162 | 163 | } 164 | } 165 | 166 | private void configureFabForDateList() { 167 | fab.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_add_light)); 168 | fab.setOnClickListener(view -> EditDateActivity.start(context)); 169 | fab.show(); 170 | } 171 | 172 | @Override 173 | public void onPageScrollStateChanged(int state) { 174 | // Intentionally empty 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /app/src/main/java/com/zendesk/rememberthedate/ui/ProfileFragment.java: -------------------------------------------------------------------------------- 1 | package com.zendesk.rememberthedate.ui; 2 | 3 | import android.os.Build; 4 | import android.os.Bundle; 5 | import androidx.annotation.NonNull; 6 | import androidx.fragment.app.Fragment; 7 | import androidx.cardview.widget.CardView; 8 | import android.view.LayoutInflater; 9 | import android.view.Menu; 10 | import android.view.MenuInflater; 11 | import android.view.MenuItem; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | import android.widget.ImageView; 15 | import android.widget.TextView; 16 | 17 | import com.zendesk.rememberthedate.Global; 18 | import com.zendesk.rememberthedate.R; 19 | import com.zendesk.rememberthedate.model.UserProfile; 20 | import com.zendesk.rememberthedate.storage.AppStorage; 21 | 22 | public class ProfileFragment extends Fragment { 23 | 24 | public static final String FRAGMENT_TITLE = "Profile"; 25 | 26 | private ImageView imageView; 27 | private TextView userName; 28 | private TextView email; 29 | 30 | public static ProfileFragment newInstance() { 31 | return new ProfileFragment(); 32 | } 33 | 34 | @Override 35 | public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 36 | // Inflate the layout for this fragment 37 | View view = inflater.inflate(R.layout.fragment_profile, container, false); 38 | setHasOptionsMenu(true); 39 | imageView = view.findViewById(R.id.imageView); 40 | userName = view.findViewById(R.id.userName); 41 | email = view.findViewById(R.id.emailView); 42 | CardView cardView = view.findViewById(R.id.card_view); 43 | 44 | cardView.setOnClickListener(v -> CreateProfileActivity.start(getContext())); 45 | return view; 46 | } 47 | 48 | @Override 49 | public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 50 | inflater.inflate(R.menu.profile_menu, menu); 51 | super.onCreateOptionsMenu(menu, inflater); 52 | } 53 | 54 | @Override 55 | public boolean onOptionsItemSelected(MenuItem item) { 56 | switch (item.getItemId()) { 57 | case R.id.action_edit: 58 | CreateProfileActivity.start(getActivity()); 59 | return true; 60 | default: 61 | return super.onOptionsItemSelected(item); 62 | } 63 | } 64 | 65 | @Override 66 | public void onStart() { 67 | super.onStart(); 68 | 69 | AppStorage storage = Global.getStorage(getContext()); 70 | UserProfile userProfile = storage.getUserProfile(); 71 | 72 | if (userProfile.getUri() != null) { 73 | ImageUtils.loadProfilePicture(getContext(), userProfile.getUri(), imageView); 74 | } 75 | 76 | userName.setText(userProfile.getName()); 77 | email.setText(userProfile.getEmail()); 78 | 79 | 80 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 81 | userName.setTextColor(getResources().getColor(R.color.primaryTextColor, getActivity().getTheme())); 82 | email.setTextColor(getResources().getColor(R.color.primaryTextColor, getActivity().getTheme())); 83 | } 84 | } 85 | } 86 | 87 | -------------------------------------------------------------------------------- /app/src/main/java/com/zendesk/rememberthedate/ui/SetDateActivity.java: -------------------------------------------------------------------------------- 1 | package com.zendesk.rememberthedate.ui; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import androidx.appcompat.app.AppCompatActivity; 7 | import android.widget.Button; 8 | import android.widget.DatePicker; 9 | 10 | import com.zendesk.rememberthedate.R; 11 | 12 | import java.util.Calendar; 13 | import java.util.GregorianCalendar; 14 | 15 | public class SetDateActivity extends AppCompatActivity { 16 | 17 | private static final String CALENDAR_RESULT_KEY = "CALENDAR_RESULT_KEY"; 18 | public static final int REQUEST_CODE = 21; 19 | private Button ok; 20 | private Button cancel; 21 | private DatePicker datePicker; 22 | private Calendar calendar; 23 | 24 | static Calendar getCalendarFromResultIntent(Intent intent) { 25 | return (Calendar) intent.getSerializableExtra(CALENDAR_RESULT_KEY); 26 | } 27 | 28 | static void startForResult(Activity activity, int requestCode) { 29 | activity.startActivityForResult(new Intent(activity, SetDateActivity.class), requestCode); 30 | } 31 | 32 | @Override 33 | protected void onCreate(Bundle savedInstanceState) { 34 | super.onCreate(savedInstanceState); 35 | setContentView(R.layout.activity_set_date); 36 | bindViews(); 37 | 38 | ok.setOnClickListener(v -> { 39 | calendar = new GregorianCalendar(datePicker.getYear(), 40 | datePicker.getMonth(), 41 | datePicker.getDayOfMonth()); 42 | 43 | Intent intent = new Intent(); 44 | intent.putExtra(CALENDAR_RESULT_KEY, calendar); 45 | setResult(RESULT_OK, intent); 46 | finish(); 47 | }); 48 | cancel.setOnClickListener(v -> { 49 | setResult(RESULT_CANCELED); 50 | finish(); 51 | }); 52 | } 53 | 54 | private void bindViews() { 55 | ok = findViewById(R.id.ok_button); 56 | cancel = findViewById(R.id.cancel_button); 57 | datePicker = findViewById(R.id.date_picker); 58 | } 59 | 60 | 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/com/zendesk/rememberthedate/ui/SetTimeActivity.java: -------------------------------------------------------------------------------- 1 | package com.zendesk.rememberthedate.ui; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.os.Build; 6 | import android.os.Bundle; 7 | import androidx.appcompat.app.AppCompatActivity; 8 | import android.widget.Button; 9 | import android.widget.TimePicker; 10 | 11 | import com.zendesk.rememberthedate.R; 12 | 13 | import java.util.Calendar; 14 | import java.util.GregorianCalendar; 15 | import java.util.Locale; 16 | import java.util.TimeZone; 17 | 18 | public class SetTimeActivity extends AppCompatActivity { 19 | 20 | private static final String TIME_REQUEST_CODE = "TIME_REQUEST_CODE"; 21 | public static final int REQUEST_CODE = 22; 22 | 23 | private Button ok; 24 | private Button cancel; 25 | private TimePicker timePicker; 26 | 27 | private Calendar calendar; 28 | 29 | static Calendar getTimeFromResultIntent(Intent intent) { 30 | return (Calendar) intent.getSerializableExtra(TIME_REQUEST_CODE); 31 | } 32 | 33 | static void startForResult(Activity activity, int requestCode) { 34 | activity.startActivityForResult(new Intent(activity, SetTimeActivity.class), requestCode); 35 | } 36 | 37 | @Override 38 | protected void onCreate(Bundle savedInstanceState) { 39 | super.onCreate(savedInstanceState); 40 | setContentView(R.layout.activity_set_time); 41 | bindViews(); 42 | 43 | ok.setOnClickListener(v -> { 44 | calendar = new GregorianCalendar(TimeZone.getDefault(), Locale.getDefault()); 45 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 46 | calendar.set(0, 0, 0, timePicker.getHour(), timePicker.getMinute()); 47 | } else { 48 | calendar.set(0, 0, 0, timePicker.getCurrentHour(), timePicker.getCurrentMinute()); 49 | } 50 | 51 | Intent intent = new Intent(); 52 | intent.putExtra(TIME_REQUEST_CODE, calendar); 53 | setResult(RESULT_OK, intent); 54 | finish(); 55 | }); 56 | cancel.setOnClickListener(v -> { 57 | setResult(RESULT_CANCELED); 58 | finish(); 59 | }); 60 | } 61 | 62 | private void bindViews() { 63 | ok = findViewById(R.id.ok_button); 64 | cancel = findViewById(R.id.cancel_button); 65 | timePicker = findViewById(R.id.time_picker); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/empty_profile.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 14 | 18 | 22 | 25 | 30 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add_light.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_date.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_date_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_delete.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_edit_light.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_email.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_save.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_save_diasbled.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/zd_user_default_avatar.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_create_profile.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | 13 | 14 | 15 | 24 | 25 | 27 | 38 | 39 | 49 | 50 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_edit_date.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 24 | 25 | 33 | 34 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | 12 | 13 | 14 | 23 | 24 | 25 | 26 | 32 | 33 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_set_date.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 12 | 13 |