├── .DS_Store ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── TwitterDemoGIF.gif ├── _config.yml ├── app ├── .DS_Store ├── build.gradle └── src │ ├── .DS_Store │ └── main │ ├── .DS_Store │ ├── AndroidManifest.xml │ ├── ic_compose-web.png │ ├── ic_face_placeholder-web.png │ ├── ic_launcher-web.png │ ├── java │ └── com │ │ └── codepath │ │ └── apps │ │ └── restclienttemplate │ │ ├── ComposeActivity.java │ │ ├── LoginActivity.java │ │ ├── MyDatabase.java │ │ ├── TimelineActivity.java │ │ ├── TweetAdapter.java │ │ ├── TweetDetailsActivity.java │ │ ├── TwitterApp.java │ │ ├── TwitterClient.java │ │ ├── UserProfileActivity.java │ │ ├── fragments │ │ ├── HomeTimelineFragment.java │ │ ├── MentionsTimelineFragment.java │ │ ├── TweetsListFragment.java │ │ ├── TweetsPagerAdapter.java │ │ └── UserTimelineFragment.java │ │ └── models │ │ ├── Media.java │ │ ├── SampleModel.java │ │ ├── Tweet.java │ │ ├── TweetText.java │ │ └── User.java │ └── res │ ├── .DS_Store │ ├── drawable-hdpi │ ├── .DS_Store │ ├── ic_face_black_24dp.png │ ├── ic_face_placeholder.png │ ├── ic_heart_clear_light_blue.png │ ├── ic_heart_clear_red.png │ ├── ic_heart_solid_light_blue.png │ ├── ic_launcher.png │ ├── ic_mode_edit_black_48dp.png │ ├── ic_person_v1.png │ ├── ic_person_v2.png │ ├── ic_person_v3.png │ ├── ic_picture_placeholder.png │ ├── ic_reply.png │ ├── ic_reply_accent.png │ ├── ic_reply_black_48dp.png │ ├── ic_retweet.png │ ├── ic_retweet_green.png │ ├── ic_tweet.png │ ├── ic_tweet_accent.png │ ├── ic_tweet_quil.png │ └── ic_tweet_quil_accent.png │ ├── drawable-mdpi │ ├── ic_face_black_24dp.png │ ├── ic_face_placeholder.png │ ├── ic_heart_clear_light_blue.png │ ├── ic_heart_clear_red.png │ ├── ic_heart_solid_light_blue.png │ ├── ic_launcher.png │ ├── ic_mode_edit_black_48dp.png │ ├── ic_person_v1.png │ ├── ic_person_v2.png │ ├── ic_person_v3.png │ ├── ic_picture_placeholder.png │ ├── ic_reply.png │ ├── ic_reply_accent.png │ ├── ic_reply_black_48dp.png │ ├── ic_retweet.png │ ├── ic_retweet_green.png │ ├── ic_tweet.png │ ├── ic_tweet_accent.png │ ├── ic_tweet_quil.png │ └── ic_tweet_quil_accent.png │ ├── drawable-xhdpi │ ├── ic_face_black_24dp.png │ ├── ic_face_placeholder.png │ ├── ic_heart_clear_light_blue.png │ ├── ic_heart_clear_red.png │ ├── ic_heart_solid_light_blue.png │ ├── ic_launcher.png │ ├── ic_mode_edit_black_48dp.png │ ├── ic_person_v1.png │ ├── ic_person_v2.png │ ├── ic_person_v3.png │ ├── ic_picture_placeholder.png │ ├── ic_reply.png │ ├── ic_reply_accent.png │ ├── ic_reply_black_48dp.png │ ├── ic_retweet.png │ ├── ic_retweet_green.png │ ├── ic_tweet.png │ ├── ic_tweet_accent.png │ ├── ic_tweet_quil.png │ └── ic_tweet_quil_accent.png │ ├── drawable-xxhdpi │ ├── ic_face_black_24dp.png │ ├── ic_face_placeholder.png │ ├── ic_heart_clear_light_blue.png │ ├── ic_heart_clear_red.png │ ├── ic_heart_solid_light_blue.png │ ├── ic_launcher.png │ ├── ic_mode_edit_black_48dp.png │ ├── ic_person_v1.png │ ├── ic_person_v2.png │ ├── ic_person_v3.png │ ├── ic_picture_placeholder.png │ ├── ic_reply.png │ ├── ic_reply_accent.png │ ├── ic_reply_black_48dp.png │ ├── ic_retweet.png │ ├── ic_retweet_green.png │ ├── ic_tweet.png │ ├── ic_tweet_accent.png │ ├── ic_tweet_quil.png │ └── ic_tweet_quil_accent.png │ ├── drawable-xxxhdpi │ ├── ic_face_black_24dp.png │ ├── ic_face_placeholder.png │ ├── ic_heart_clear_light_blue.png │ ├── ic_heart_clear_red.png │ ├── ic_heart_solid_light_blue.png │ ├── ic_mode_edit_black_48dp.png │ ├── ic_person_v1.png │ ├── ic_person_v2.png │ ├── ic_person_v3.png │ ├── ic_picture_placeholder.png │ ├── ic_reply.png │ ├── ic_reply_accent.png │ ├── ic_reply_black_48dp.png │ ├── ic_retweet.png │ ├── ic_retweet_green.png │ ├── ic_tweet.png │ ├── ic_tweet_accent.png │ ├── ic_tweet_quil.png │ └── ic_tweet_quil_accent.png │ ├── drawable │ ├── .DS_Store │ ├── button_follow.xml │ ├── button_unfollow.xml │ ├── ic_back_button_accent.xml │ ├── ic_back_button_light_blue.xml │ ├── ic_close_accent.xml │ ├── ic_heart_clear_light_blue_svg.xml │ ├── ic_heart_clear_red_svg.xml │ ├── ic_heart_solid_light_blue_svg.xml │ ├── ic_heart_solid_red_svg.xml │ ├── ic_person_v1_svg.xml │ ├── ic_person_v2_svg.xml │ ├── ic_person_v3_svg.xml │ ├── ic_picture_placeholder_svg.xml │ ├── ic_reply_accent_svg.xml │ ├── ic_reply_svg.xml │ ├── ic_retweet_green_svg.xml │ ├── ic_retweet_svg.xml │ ├── ic_tweet_accent_svg.xml │ ├── ic_tweet_quil_accent_svg.xml │ ├── ic_tweet_quil_svg.xml │ ├── ic_tweet_svg.xml │ └── rounded_corners.xml │ ├── layout │ ├── activity_compose.xml │ ├── activity_login.xml │ ├── activity_timeline.xml │ ├── activity_tweet_details.xml │ ├── activity_user_profile.xml │ ├── fragments_tweets_list.xml │ └── item_tweet.xml │ ├── menu │ ├── login.xml │ └── menu_timeline.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ ├── values-sw600dp │ └── dimens.xml │ ├── values-sw720dp-land │ └── dimens.xml │ ├── values-v14 │ └── styles.xml │ └── values │ ├── .DS_Store │ ├── colors.xml │ ├── dimens.xml │ ├── secrets.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle ├── lint.xml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── success.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Source: https://gist.github.com/nesquena/5617544/raw/53710b374e7df3302df43b552488d876040ada3d/.gitignore 2 | 3 | # built application files 4 | *.apk 5 | *.ap_ 6 | 7 | # files for the dex VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # generated files 14 | bin/ 15 | gen/ 16 | 17 | # Local configuration file (sdk path, etc) 18 | local.properties 19 | 20 | # Eclipse project files 21 | .classpath 22 | .project 23 | 24 | # Proguard folder generated by Eclipse 25 | proguard/ 26 | 27 | # Intellij project files 28 | *.iml 29 | *.ipr 30 | *.iws 31 | .idea/ 32 | 33 | *.pydevproject 34 | .project 35 | .metadata 36 | bin/** 37 | tmp/** 38 | tmp/**/* 39 | *.tmp 40 | *.bak 41 | *.swp 42 | *~.nib 43 | local.properties 44 | .classpath 45 | .settings/ 46 | .loadpath 47 | 48 | # External tool builders 49 | .externalToolBuilders/ 50 | 51 | # Locally stored "Eclipse launch configurations" 52 | *.launch 53 | 54 | # CDT-specific 55 | .cproject 56 | 57 | # PDT-specific 58 | .buildpath 59 | 60 | # Android Studio project files 61 | *.iml 62 | .gradle 63 | .idea 64 | build 65 | import-summary.txt 66 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | jdk: 3 | - oraclejdk8 4 | sudo: false 5 | android: 6 | components: 7 | - tools 8 | - platform-tools 9 | - build-tools-25.0.3 10 | - android-25 11 | licenses: 12 | - android-sdk-license-.+ 13 | script: 14 | - "./gradlew build check --daemon" 15 | after_failure: "cat $TRAVIS_BUILD_DIR/app/build/outputs/lint-results-debug.xml" 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 CodePath 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Project 3 - *Twitter* 2 | 3 | **Twitter** is an android app that allows a user to view his Twitter timeline and post a new tweet. The app utilizes [Twitter REST API](https://dev.twitter.com/rest/public). 4 | This app was made for learning purposes. 5 | 6 | 7 | Time spent: **35** hours spent in total (for two weeks) 8 | 9 | Elements used: 10 | - Icons from Material.io/icons/ 11 | 12 | ## User Stories 13 | 14 | The following **required** functionality is completed: 15 | 16 | * [x] User can **sign in to Twitter** using OAuth login 17 | * [x] User can **view tweets from their home timeline** 18 | * [x] User is displayed the username, name, and body for each tweet 19 | * [x] User is displayed the [relative timestamp](https://gist.github.com/nesquena/f786232f5ef72f6e10a7) for each tweet "8m", "7h" 20 | * [x] User can **compose and post a new tweet** 21 | * [x] User can click a “Compose” icon in the Action Bar on the top right 22 | * [x] I actually used a floating action button isntead 23 | * [x] User can then enter a new tweet and post this to twitter 24 | * [x] User is taken back to home timeline with **new tweet visible** in timeline 25 | * [x] Newly created tweet should be manually inserted into the timeline and not rely on a full refresh 26 | * [x] User can switch between Timeline and Mention views using tabs. 27 | * [x] User can view their home timeline tweets. 28 | * [x] User can view the recent mentions of their username. 29 | * [x] User can compose tweets. See this conceptual guide for passing data into a timeline fragment. 30 | * [x] User can navigate to view their own profile. 31 | * [x] I actually placed the user click on the left of the screen. 32 | * [x] User can see picture, tagline, # of followers, # of following, and tweets on their profile. 33 | * [x] The users/verify_credentials endpoint can be used to access this information. 34 | * [x] User can click on the profile image in any tweet to see another user's profile. 35 | * [x] User can see picture, tagline, # of followers, # of following, and tweets of clicked user. 36 | * [x] Profile view should include that user's timeline. 37 | * [x] The users/show endpoint can be used to access this information. 38 | 39 | The following **optional** features are implemented: 40 | 41 | * [x] User can **see a counter with total number of characters left for tweet** on compose tweet page 42 | * [x] User can **pull down to refresh tweets timeline** 43 | * [x] User is using **"Twitter branded" colors and styles** 44 | * [x] Used the quite recent twitter theme *Night Mode*, which is why my app is dark blue. 45 | * [x] I also used a different set of icons that were nicer in my opinion. 46 | * [ ] User sees an **indeterminate progress indicator** when any background or network task is happening 47 | * [x] User can **select "reply" from detail view to respond to a tweet** 48 | * [x] User that wrote the original tweet is **automatically "@" replied in compose** 49 | * [x] User can tap a tweet to **open a detailed tweet view** 50 | * [x] User can **take favorite (and unfavorite) or reweet** actions on a tweet 51 | * [x] User can retweet/unretweet in detailed tweet mode. 52 | * [x] User can **see embedded image media within a tweet** on list or detail view. 53 | * [x] User can **click a link within a tweet body** on tweet details view. The click will launch the web browser with relevant page opened. 54 | 55 | The following **bonus** features are implemented: 56 | 57 | * [x] User can view more tweets as they scroll with infinite pagination 58 | * [ ] Compose tweet functionality is build using modal overlay 59 | * [x] Use Parcelable instead of Serializable using the popular [Parceler library](http://guides.codepath.com/android/Using-Parceler). 60 | * [x] Replace all icon drawables and other static image assets with [vector drawables](http://guides.codepath.com/android/Drawables#vector-drawables) where appropriate. 61 | * [ ] User can view following / followers list through any profile they view. 62 | * [x] User can see embedded image media within the tweet detail view 63 | * [ ] Use the popular ButterKnife annotation library to reduce view boilerplate. 64 | * [ ] On the Twitter timeline, leverage the [CoordinatorLayout](http://guides.codepath.com/android/Handling-Scrolls-with-CoordinatorLayout#responding-to-scroll-events) to apply scrolling behavior that [hides / shows the toolbar](http://guides.codepath.com/android/Using-the-App-ToolBar#reacting-to-scroll). 65 | * [ ] User can **open the twitter app offline and see last loaded tweets**. Persisted in SQLite tweets are refreshed on every application launch. While "live data" is displayed when app can get it from Twitter API, it is also saved for use in offline mode. 66 | 67 | 68 | The following **additional** features are implemented: 69 | 70 | * [ ] List anything else that you can get done to improve the app functionality! 71 | * [x] Set up to retrieve tweet counts and like counts 72 | * [x] Display activity text instead of AppName ("TWITTER"). e.g.: Home, New Tweet / In Reply To , Tweet (for detailed tweet) 73 | * [x] Improve the quality of the images received 74 | * [x] Remove Toast Statements on onClick events from TweetAdapter 75 | * [x] On reply, set handler to tweet id (in reply to, check twitter API) so that replies work in actual replies 76 | * [x] For images of tweets, have a different placeholder than the one of profile photos (similar to Flixster app placeholder) 77 | * [ ] Make infinite pagination on user profile's tweets 78 | * [x] Make the follow button actually work 79 | * [x] Add tabs to user profile 80 | * [x] User can see a user's profile by clicking on the profile picture of the user. 81 | * [ ] (!!!) Fix bugs resulting from infinite scroll and repeating the same tweets over and over when web error occurs 82 | * [ ] Figure out why maxid does not update 83 | * [x] Add a backpress button when user enters reply to a tweet (instead of letting be for the regular android backPress) 84 | * [ ] When click on backPress in the timeline, it just restart the application instead of quitting it. Fixing this bug would be a goal 85 | * [x] Make the follow button work on user profiles 86 | * [ ] On the profile view, display an action bar as the user scrolls down (with a back button) 87 | * [ ] Add search tab with various search functions implemented (mentions, tags, users, etc) 88 | * [ ] Fix the indeterminate progress bar 89 | * [ ] Add an icon for clicking on the profile (if possible, user an image instead of icon) 90 | 91 | ## Video Walkthrough 92 | 93 | Here's a walkthrough of implemented user stories: 94 | 95 | ![Twitter Demo](TwitterDemoGIF.gif) 96 | 97 | GIF created with [LiceCap](http://www.cockos.com/licecap/). 98 | 99 | ## Notes 100 | 101 | Describe any challenges encountered while building the app. 102 | 103 | I was done with the requirements early-ish (although a bit later than most of my table). It was quite challenging to figure out how to take control of the UI in this app (spread things equally, load better quality images, add line separators for each tweet, etc). However, I was able to get it done up to how I wanted it to be, so I am happy about that. In addition, whenever I tried to implement something that I haven't implemented, it was difficult to figure out how to do it and having to search through at least 6 StackOverflow and Android Documentations pages altogether for each implementation. However, I feel like I got better at that. Finally, I really enjoyed taking more advantage of the android studio app: e.g.: using the debugger to find where the code breaks, using the monitor to check the log statements with TAGs, and using shortcuts to increase my efficiency/delivery. 104 | 105 | I really enjoyed building this app. I learned a lot and am more confident about my skill in android development. 106 | 107 | ## Open-source libraries used 108 | 109 | - [Android Async HTTP](https://github.com/loopj/android-async-http) - Simple asynchronous HTTP requests with JSON parsing 110 | - [Glide](https://github.com/bumptech/glide) - Image loading and caching library for Android 111 | 112 | ## License 113 | 114 | Copyright [2017] [Robert M. Vunabandi] 115 | 116 | Licensed under the Apache License, Version 2.0 (the "License"); 117 | you may not use this file except in compliance with the License. 118 | You may obtain a copy of the License at 119 | 120 | http://www.apache.org/licenses/LICENSE-2.0 121 | 122 | Unless required by applicable law or agreed to in writing, software 123 | distributed under the License is distributed on an "AS IS" BASIS, 124 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 125 | See the License for the specific language governing permissions and 126 | limitations under the License. 127 | 128 | -------------------------------------------------------------------------------- /TwitterDemoGIF.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/TwitterDemoGIF.gif -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-minimal -------------------------------------------------------------------------------- /app/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/.DS_Store -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.3" 6 | 7 | defaultConfig { 8 | applicationId "com.codepath.apps.restclienttemplate" 9 | minSdkVersion 17 10 | targetSdkVersion 25 11 | vectorDrawables.useSupportLibrary = true 12 | } 13 | 14 | // Related to https://github.com/scribejava/scribejava/issues/480 15 | // Scribe expects Java 7 or this custom Apache library 16 | lintOptions { 17 | lintConfig rootProject.file('gradle/lint.xml') 18 | } 19 | 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' 24 | } 25 | } 26 | } 27 | 28 | repositories { 29 | jcenter() 30 | maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } 31 | } 32 | 33 | def dbflow_version = "4.0.3" 34 | 35 | dependencies { 36 | compile fileTree(dir: 'libs', include: '*.jar') 37 | // Glide for remote image loading 38 | // Android Async Http for sending async network requests 39 | // DBFlow for simple persistence with an ORM 40 | annotationProcessor "com.github.Raizlabs.DBFlow:dbflow-processor:${dbflow_version}" 41 | compile "com.github.Raizlabs.DBFlow:dbflow-core:${dbflow_version}" 42 | compile "com.github.Raizlabs.DBFlow:dbflow:${dbflow_version}" 43 | compile 'com.github.bumptech.glide:glide:3.8.0' 44 | compile 'com.github.bumptech.glide:glide:3.8.0' 45 | compile 'com.codepath.libraries:android-oauth-handler:1.2.5' 46 | compile 'com.android.support:appcompat-v7:25.3.1' 47 | compile 'com.loopj.android:android-async-http:1.4.9' 48 | compile 'com.android.support.constraint:constraint-layout:1.0.2' 49 | compile 'com.android.support:recyclerview-v7:25.3.1' 50 | compile 'jp.wasabeef:glide-transformations:2.0.2' 51 | compile 'jp.co.cyberagent.android.gpuimage:gpuimage-library:1.4.1' 52 | compile 'org.parceler:parceler-api:1.1.6' 53 | compile 'com.android.support:design:25.3.1' 54 | compile 'com.jakewharton:butterknife:8.4.0' 55 | annotationProcessor 'org.parceler:parceler:1.1.6' 56 | annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0' 57 | compile "com.android.support:design:25.3.1" 58 | } 59 | -------------------------------------------------------------------------------- /app/src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/.DS_Store -------------------------------------------------------------------------------- /app/src/main/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/.DS_Store -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 16 | 17 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 33 | 34 | 35 | 36 | 37 | 40 | 41 | 42 | 46 | 47 | 48 | 51 | 52 | 53 | 56 | 57 | -------------------------------------------------------------------------------- /app/src/main/ic_compose-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/ic_compose-web.png -------------------------------------------------------------------------------- /app/src/main/ic_face_placeholder-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/ic_face_placeholder-web.png -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/com/codepath/apps/restclienttemplate/ComposeActivity.java: -------------------------------------------------------------------------------- 1 | package com.codepath.apps.restclienttemplate; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.v4.content.ContextCompat; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.support.v7.widget.Toolbar; 8 | import android.text.Editable; 9 | import android.text.TextWatcher; 10 | import android.util.Log; 11 | import android.view.View; 12 | import android.widget.Button; 13 | import android.widget.EditText; 14 | import android.widget.ImageView; 15 | import android.widget.ProgressBar; 16 | import android.widget.TextView; 17 | import android.widget.Toast; 18 | 19 | import com.bumptech.glide.Glide; 20 | import com.codepath.apps.restclienttemplate.models.Tweet; 21 | import com.codepath.apps.restclienttemplate.models.User; 22 | import com.loopj.android.http.JsonHttpResponseHandler; 23 | 24 | import org.json.JSONArray; 25 | import org.json.JSONException; 26 | import org.json.JSONObject; 27 | import org.parceler.Parcels; 28 | 29 | import cz.msebera.android.httpclient.Header; 30 | import jp.wasabeef.glide.transformations.RoundedCornersTransformation; 31 | 32 | public class ComposeActivity extends AppCompatActivity { 33 | private TwitterClient client; 34 | // private final int REQUEST_CODE = 200; 35 | 36 | public TextView tvCharacterCount; 37 | public EditText etTweet; 38 | public Button bTweet; 39 | private ProgressBar pb; 40 | public String text; 41 | public long reply_uid; 42 | 43 | public static final String TAG = "ComposeActivity"; 44 | 45 | // toolbar stuffs 46 | Toolbar compose_toolbar; 47 | TextView compose_toolbar_title; 48 | ImageView compose_toolbar_image, compose_toolbar_cancel_button; 49 | User using_user; 50 | 51 | @Override 52 | protected void onCreate(Bundle savedInstanceState) { 53 | super.onCreate(savedInstanceState); 54 | setContentView(R.layout.activity_compose); 55 | // set the client up 56 | client = TwitterApp.getRestClient(); 57 | 58 | // pick up different views 59 | tvCharacterCount = (TextView) findViewById(R.id.tvCharacterCount); 60 | etTweet = (EditText) findViewById(R.id.etTweet); 61 | bTweet = (Button) findViewById(R.id.bTweet); 62 | pb = (ProgressBar) findViewById(R.id.pbLoading); 63 | 64 | 65 | // get the text sent from the intent, this text could be a user to reply to 66 | text = getIntent().getStringExtra("text"); 67 | // get/set the toolbar, set the title to home 68 | compose_toolbar = (Toolbar) findViewById(R.id.compose_toolbar); 69 | setSupportActionBar(compose_toolbar); 70 | compose_toolbar.setTitleTextColor(getResources().getColor(R.color.colorWhite)); 71 | getSupportActionBar().setTitle(""); // remove the default title 72 | compose_toolbar_title = (TextView) findViewById(R.id.compose_toolbar_title); 73 | compose_toolbar_cancel_button = (ImageView) findViewById(R.id.compose_toolbar_cancel_button); 74 | compose_toolbar_image = (ImageView) findViewById(R.id.compose_toolbar_image); 75 | // cancel button to finish 76 | compose_toolbar_cancel_button.setOnClickListener(new View.OnClickListener() { 77 | @Override 78 | public void onClick(View v) { 79 | finish(); 80 | } 81 | }); 82 | getUsingUser(); 83 | 84 | if (text != null){ 85 | // I know I need to better check, but when i had "!text.isEmpty() &&", it would crash the app 86 | // in case the text is not empty (meaning no reply), then we set the text of etTweet to that text (for replies) 87 | etTweet.setText("@"+text+" "); 88 | //place the cursor at the end of the text 89 | etTweet.setSelection(etTweet.getText().length()); 90 | reply_uid = getIntent().getLongExtra("reply_uid", 0); 91 | // set title of toolbar 92 | compose_toolbar_title.setText("In reply to "+ text); 93 | } 94 | 95 | 96 | 97 | etTweet.addTextChangedListener(new TextWatcher() { 98 | @Override 99 | public void beforeTextChanged(CharSequence s, int start, int count, int after) { 100 | 101 | } 102 | 103 | @Override 104 | public void onTextChanged(CharSequence s, int start, int before, int count) { 105 | 106 | } 107 | 108 | @Override 109 | public void afterTextChanged(Editable s) { 110 | int length = 140 - s.length(); 111 | tvCharacterCount.setText(Integer.toString(length)); 112 | //String redLightString = getString(R.color.colorRedLight).substring(1); 113 | 114 | int redLight = ContextCompat.getColor(getApplicationContext(), R.color.colorRedLight); 115 | int redDark = ContextCompat.getColor(getApplicationContext(), R.color.colorRed); 116 | int blueLight = ContextCompat.getColor(getApplicationContext(), R.color.colorPrimaryDarkLight); 117 | int whiteColor = ContextCompat.getColor(getApplicationContext(), R.color.colorWhite); 118 | 119 | // Log.i(TAG, String.format("Hey BUTTON %s", bTweet.getText())); 120 | if (length < 20) { 121 | tvCharacterCount.setTextColor(redLight); 122 | if (length < 0) { 123 | bTweet.setEnabled(false); 124 | bTweet.setTextColor(redLight); 125 | bTweet.setBackgroundColor(redDark); // 0xFF992020 126 | } else { 127 | bTweet.setEnabled(true); 128 | bTweet.setTextColor(whiteColor); 129 | bTweet.setBackgroundColor(blueLight); // 0xFF173144 130 | } 131 | } else { 132 | tvCharacterCount.setTextColor(whiteColor); 133 | } 134 | } 135 | }); 136 | } 137 | 138 | public void onSubmit(View v) { 139 | pb.setVisibility(ProgressBar.VISIBLE); // set the progress bar to visible 140 | EditText tweet = (EditText) findViewById(R.id.etTweet); 141 | // String text = String.valueOf(tweet.getText()); 142 | 143 | 144 | client.sendTweet(tweet.getText().toString(), reply_uid, new JsonHttpResponseHandler() { 145 | @Override 146 | public void onSuccess(int statusCode, Header[] headers, JSONObject response) { 147 | // super.onSuccess(statusCode, headers, response); 148 | 149 | Tweet tweet; 150 | // data.putExtra("code", REQUEST_CODE); 151 | try { 152 | Intent data = new Intent(); 153 | tweet = Tweet.fromJSON(response); 154 | 155 | // after getting the tweet parced, we parcialize it with Parcels.wrap(tweet) 156 | data.putExtra("tweet", Parcels.wrap(tweet)); 157 | if (text != null) { 158 | // if the text is not null, then we start from another activity 159 | Intent timelineIntent = new Intent(ComposeActivity.this, TimelineActivity.class); 160 | startActivity(timelineIntent); 161 | } else { 162 | setResult(RESULT_OK, data); 163 | finish(); 164 | } 165 | } catch (JSONException e) { 166 | //data.putExtra("code", 404); 167 | //setResult(RESULT_CANCELED, data); 168 | e.printStackTrace(); 169 | } 170 | 171 | } 172 | 173 | @Override 174 | public void onSuccess(int statusCode, Header[] headers, JSONArray response) { 175 | super.onSuccess(statusCode, headers, response); 176 | } 177 | 178 | @Override 179 | public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONObject errorResponse) { 180 | // super.onFailure(statusCode, headers, throwable, errorResponse); 181 | Log.e(client.TAG, "ERRR ! !" + errorResponse.toString()); 182 | throwable.printStackTrace(); 183 | } 184 | 185 | @Override 186 | public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONArray errorResponse) { 187 | // super.onFailure(statusCode, headers, throwable, errorResponse); 188 | Log.e(client.TAG, "ERRR ( (" + errorResponse.toString()); 189 | throwable.printStackTrace(); 190 | } 191 | 192 | @Override 193 | public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { 194 | // super.onFailure(statusCode, headers, responseString, throwable); 195 | Log.e(client.TAG, "ERRR < <" + responseString.toString()); 196 | throwable.printStackTrace(); 197 | } 198 | }); 199 | // closes the activity and returns to first screen 200 | } 201 | 202 | public void getUsingUser(){ 203 | client.getUsingUser(new JsonHttpResponseHandler() { 204 | @Override 205 | public void onSuccess(int statusCode, Header[] headers, JSONObject response) { 206 | try { 207 | using_user = User.fromJSON(response); 208 | // set the using user profile image 209 | Glide.with(getBaseContext()).load(using_user.profileImageUrl) 210 | .bitmapTransform(new RoundedCornersTransformation(getBaseContext(), 2000, 0)) 211 | .placeholder(R.drawable.ic_person_v1_svg) 212 | .error(R.drawable.ic_person_v1_svg) 213 | .override(2048, 2048) 214 | .into(compose_toolbar_image); 215 | } catch (JSONException e) { 216 | Toast.makeText(getBaseContext(), String.format("Error occurred."), Toast.LENGTH_SHORT).show(); 217 | e.printStackTrace(); 218 | } 219 | } 220 | @Override 221 | public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONObject errorResponse) { 222 | Log.e(TAG +" : "+ client.TAG, String.format("Error JSONObject: %s" , errorResponse.toString())); 223 | throwable.printStackTrace(); 224 | 225 | Toast.makeText(getBaseContext(), String.format("An error occurred."), Toast.LENGTH_SHORT).show(); 226 | } 227 | @Override 228 | public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONArray errorResponse) { 229 | Log.e(TAG +" : "+ client.TAG, String.format("Error JSONArray: %s" , errorResponse.toString())); 230 | throwable.printStackTrace(); 231 | 232 | Toast.makeText(getBaseContext(), String.format("An error occurred."), Toast.LENGTH_SHORT).show(); 233 | } 234 | @Override 235 | public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { 236 | Log.e(TAG +" : "+ client.TAG, String.format("Error String: %s" , responseString)); 237 | throwable.printStackTrace(); 238 | 239 | Toast.makeText(getBaseContext(), String.format("An error occurred."), Toast.LENGTH_SHORT).show(); 240 | } 241 | }); 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /app/src/main/java/com/codepath/apps/restclienttemplate/LoginActivity.java: -------------------------------------------------------------------------------- 1 | package com.codepath.apps.restclienttemplate; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.view.Menu; 6 | import android.view.View; 7 | 8 | import com.codepath.oauth.OAuthLoginActionBarActivity; 9 | 10 | public class LoginActivity extends OAuthLoginActionBarActivity { 11 | 12 | @Override 13 | protected void onCreate(Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | setContentView(R.layout.activity_login); 16 | } 17 | 18 | 19 | // Inflate the menu; this adds items to the action bar if it is present. 20 | @Override 21 | public boolean onCreateOptionsMenu(Menu menu) { 22 | getMenuInflater().inflate(R.menu.login, menu); 23 | return true; 24 | } 25 | 26 | // OAuth authenticated successfully, launch primary authenticated activity 27 | // i.e Display application "homepage" 28 | @Override 29 | public void onLoginSuccess() { 30 | Intent timeline = new Intent(this, TimelineActivity.class); 31 | startActivity(timeline); 32 | } 33 | 34 | // OAuth authentication flow failed, handle the error 35 | // i.e Display an error dialog or toast 36 | @Override 37 | public void onLoginFailure(Exception e) { 38 | e.printStackTrace(); 39 | } 40 | 41 | // Click handler method for the button used to start OAuth flow 42 | // Uses the client to initiate OAuth authorization 43 | // This should be tied to a button used to login 44 | public void loginToRest(View view) { 45 | getClient().connect(); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/codepath/apps/restclienttemplate/MyDatabase.java: -------------------------------------------------------------------------------- 1 | package com.codepath.apps.restclienttemplate; 2 | 3 | import com.raizlabs.android.dbflow.annotation.Database; 4 | 5 | @Database(name = MyDatabase.NAME, version = MyDatabase.VERSION) 6 | public class MyDatabase { 7 | 8 | public static final String NAME = "RestClientDatabase"; 9 | 10 | public static final int VERSION = 1; 11 | } -------------------------------------------------------------------------------- /app/src/main/java/com/codepath/apps/restclienttemplate/TimelineActivity.java: -------------------------------------------------------------------------------- 1 | package com.codepath.apps.restclienttemplate; 2 | 3 | import android.content.Intent; 4 | import android.content.res.ColorStateList; 5 | import android.os.Bundle; 6 | import android.support.design.widget.FloatingActionButton; 7 | import android.support.design.widget.TabLayout; 8 | import android.support.v4.content.ContextCompat; 9 | import android.support.v4.view.ViewPager; 10 | import android.support.v7.app.AppCompatActivity; 11 | import android.support.v7.widget.Toolbar; 12 | import android.util.Log; 13 | import android.view.View; 14 | import android.view.animation.AccelerateInterpolator; 15 | import android.view.animation.DecelerateInterpolator; 16 | import android.widget.ImageView; 17 | import android.widget.ProgressBar; 18 | import android.widget.TextView; 19 | import android.widget.Toast; 20 | 21 | import com.bumptech.glide.Glide; 22 | import com.codepath.apps.restclienttemplate.fragments.HomeTimelineFragment; 23 | import com.codepath.apps.restclienttemplate.fragments.TweetsPagerAdapter; 24 | import com.codepath.apps.restclienttemplate.models.Tweet; 25 | import com.codepath.apps.restclienttemplate.models.User; 26 | import com.loopj.android.http.JsonHttpResponseHandler; 27 | 28 | import org.json.JSONArray; 29 | import org.json.JSONException; 30 | import org.json.JSONObject; 31 | import org.parceler.Parcels; 32 | 33 | import cz.msebera.android.httpclient.Header; 34 | import jp.wasabeef.glide.transformations.RoundedCornersTransformation; 35 | 36 | public class TimelineActivity extends AppCompatActivity { 37 | 38 | // toolbar stuffs 39 | Toolbar home_toolbar; 40 | TextView home_toolbar_title; 41 | ImageView home_toolbar_image; 42 | User using_user; 43 | 44 | // twitter client 45 | private TwitterClient client; 46 | public ViewPager vpPager; 47 | public TweetsPagerAdapter pagerAdapter; 48 | 49 | private final int REQUEST_CODE = 200; 50 | 51 | public FloatingActionButton fabCompose; 52 | 53 | 54 | private ProgressBar pb; // TODO - Take care of pb, place it in fragment or something! 55 | 56 | public static final String TAG = "TimelineActivityTAG"; 57 | 58 | // For scroll hiding and showing 59 | // https://mzgreen.github.io/2015/02/15/How-to-hideshow-Toolbar-when-list-is-scroling%28part1%29/ 60 | 61 | @Override 62 | protected void onCreate(Bundle savedInstanceState) { 63 | super.onCreate(savedInstanceState); 64 | setContentView(R.layout.activity_timeline); 65 | // get the client 66 | client = TwitterApp.getRestClient(); 67 | 68 | // get/set the toolbar, set the title to home 69 | home_toolbar = (Toolbar) findViewById(R.id.home_toolbar); 70 | setSupportActionBar(home_toolbar); 71 | home_toolbar.setTitleTextColor(getResources().getColor(R.color.colorWhite)); 72 | getSupportActionBar().setTitle(""); // remove the default title 73 | home_toolbar_title = (TextView) findViewById(R.id.home_toolbar_title); 74 | home_toolbar_image = (ImageView) findViewById(R.id.home_toolbar_image); 75 | getUsingUser(); 76 | // create the link to the using user's profile 77 | home_toolbar_image.setOnClickListener(new View.OnClickListener() { 78 | @Override 79 | public void onClick(View v) { 80 | onProfileView(); 81 | } 82 | }); 83 | 84 | // attach the ProgressBar and the FloatingActionBar 85 | pb = (ProgressBar) findViewById(R.id.pbLoading); 86 | pb.setVisibility(ProgressBar.INVISIBLE); // remove the progress bar 87 | fabCompose = (FloatingActionButton) findViewById(R.id.fabCompose); 88 | 89 | // get the view pager 90 | vpPager = (ViewPager) findViewById(R.id.viewpager); 91 | // set the adapter for the pager 92 | pagerAdapter = new TweetsPagerAdapter(getSupportFragmentManager(), this); 93 | vpPager.setAdapter(pagerAdapter); 94 | // set up the tab layout to use the view pager 95 | TabLayout tabLayout = (TabLayout) findViewById(R.id.sliding_tabs); 96 | tabLayout.setupWithViewPager(vpPager); 97 | tabLayout.setSelectedTabIndicatorColor(ContextCompat.getColor(getBaseContext(), R.color.colorAccent)); 98 | tabLayout.setBackgroundColor(ContextCompat.getColor(getBaseContext(), R.color.colorPrimary)); 99 | tabLayout.setTabTextColors(ColorStateList.valueOf(ContextCompat.getColor(getBaseContext(), R.color.colorWhite))); 100 | 101 | fabCompose.setOnClickListener(new View.OnClickListener() { 102 | @Override 103 | public void onClick(View v) { 104 | composeMessage(null); 105 | } 106 | }); 107 | } 108 | 109 | public void getUsingUser(){ 110 | client.getUsingUser(new JsonHttpResponseHandler() { 111 | @Override 112 | public void onSuccess(int statusCode, Header[] headers, JSONObject response) { 113 | try { 114 | using_user = User.fromJSON(response); 115 | // set the using user profile image 116 | Glide.with(getBaseContext()).load(using_user.profileImageUrl) 117 | .bitmapTransform(new RoundedCornersTransformation(getBaseContext(), 2000, 0)) 118 | .placeholder(R.drawable.ic_person_v1_svg) 119 | .error(R.drawable.ic_person_v1_svg) 120 | .override(2048, 2048) 121 | .into(home_toolbar_image); 122 | } catch (JSONException e) { 123 | Toast.makeText(getBaseContext(), String.format("Error occurred."), Toast.LENGTH_SHORT).show(); 124 | e.printStackTrace(); 125 | } 126 | } 127 | @Override 128 | public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONObject errorResponse) { 129 | Log.e(TAG +" : "+ client.TAG, String.format("Error JSONObject: %s" , errorResponse.toString())); 130 | throwable.printStackTrace(); 131 | 132 | Toast.makeText(getBaseContext(), String.format("An error occurred."), Toast.LENGTH_SHORT).show(); 133 | } 134 | @Override 135 | public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONArray errorResponse) { 136 | Log.e(TAG +" : "+ client.TAG, String.format("Error JSONArray: %s" , errorResponse.toString())); 137 | throwable.printStackTrace(); 138 | 139 | Toast.makeText(getBaseContext(), String.format("An error occurred."), Toast.LENGTH_SHORT).show(); 140 | } 141 | @Override 142 | public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { 143 | Log.e(TAG +" : "+ client.TAG, String.format("Error String: %s" , responseString)); 144 | throwable.printStackTrace(); 145 | 146 | Toast.makeText(getBaseContext(), String.format("An error occurred."), Toast.LENGTH_SHORT).show(); 147 | } 148 | }); 149 | } 150 | 151 | public void onProfileView() { 152 | // launch the profile view 153 | Intent i = new Intent(TimelineActivity.this, UserProfileActivity.class); 154 | i.putExtra("user_uid", using_user.uid); 155 | i.putExtra("screenName", using_user.screenName); 156 | i.putExtra("using_user", true); 157 | startActivity(i); 158 | } 159 | 160 | public void composeMessage(String text) { 161 | Intent i = new Intent(TimelineActivity.this, ComposeActivity.class); 162 | i.putExtra("text", text); 163 | startActivityForResult(i, REQUEST_CODE); 164 | } 165 | 166 | @Override 167 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 168 | // REQUEST_CODE is defined above 169 | Log.d(TAG, String.format("%s %s %s RESULT_OK: %s", "TIMELINE START", resultCode, requestCode, RESULT_OK)); 170 | if (resultCode == RESULT_OK && requestCode == REQUEST_CODE) { 171 | // Extract name value from result extras 172 | Tweet tweet = Parcels.unwrap(data.getParcelableExtra("tweet")); 173 | // add the tweet to the set of tweets at position 0 174 | ((HomeTimelineFragment) pagerAdapter.getItem(vpPager.getCurrentItem())).addTweet(tweet); 175 | // fragmentTweetsList.addItemOne(tweet, 0); 176 | pb.setVisibility(ProgressBar.INVISIBLE); // remove the progress bar 177 | } 178 | } 179 | 180 | private void hideViews() { 181 | // hides the toolbar 182 | home_toolbar.animate().translationY(-home_toolbar.getHeight()).setInterpolator(new AccelerateInterpolator(2)); 183 | } 184 | 185 | private void showViews() { 186 | // shows the toolbar 187 | home_toolbar.animate().translationY(0).setInterpolator(new DecelerateInterpolator(2)); 188 | } 189 | /**/ 190 | 191 | /* 192 | @Override 193 | public boolean onOptionsItemSelected(MenuItem item) { 194 | // Handle presses on the action bar items 195 | switch (item.getItemId()) { 196 | case R.id.aCompose: 197 | composeMessage(null); 198 | return true; 199 | default: 200 | return super.onOptionsItemSelected(item); 201 | } 202 | }*/ 203 | 204 | /* 205 | REALLY GOOD HELP I GOT FROM STACK OVERFLOW 206 | =========================================== 207 | LINK: https://stackoverflow.com/questions/26543131/how-to-implement-endless-list-with-recyclerview 208 | 209 | LinearLayoutManager mLayoutManager; 210 | mLayoutManager = new LinearLayoutManager(this); 211 | mRecyclerView.setLayoutManager(mLayoutManager); 212 | * */ 213 | } 214 | -------------------------------------------------------------------------------- /app/src/main/java/com/codepath/apps/restclienttemplate/TweetDetailsActivity.java: -------------------------------------------------------------------------------- 1 | package com.codepath.apps.restclienttemplate; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.v4.content.ContextCompat; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.support.v7.widget.Toolbar; 8 | import android.text.Html; 9 | import android.view.View; 10 | import android.widget.ImageView; 11 | import android.widget.TextView; 12 | import android.widget.Toast; 13 | 14 | import com.bumptech.glide.Glide; 15 | import com.codepath.apps.restclienttemplate.models.TweetText; 16 | import com.loopj.android.http.AsyncHttpResponseHandler; 17 | 18 | import cz.msebera.android.httpclient.Header; 19 | import jp.wasabeef.glide.transformations.RoundedCornersTransformation; 20 | 21 | public class TweetDetailsActivity extends AppCompatActivity { 22 | public ImageView ivProfileImageD, ibReplyD, ivRetweetD, ivLikeD, ivTweetedImageD; 23 | public TextView tvNameD, tvScreenNameD, tvBodyD, tvCreatedAtD, tvRetweetsD, tvLikesD; 24 | public long tUid; 25 | public boolean tRetweeted, tLiked, tMediaFound; 26 | private static TwitterClient client; 27 | 28 | public String tName, tScreenName, tBody, tCreatedAt, tProfileImageUrl; 29 | public long tRetweets, tLikes; 30 | 31 | // toolbar stuffs 32 | Toolbar tweet_detail_toolbar; 33 | TextView tweet_detail_toolbar_title; 34 | ImageView tweet_detail_toolbar_back_button; 35 | 36 | @Override 37 | protected void onCreate(Bundle savedInstanceState) { 38 | super.onCreate(savedInstanceState); 39 | setContentView(R.layout.activity_tweet_details); 40 | 41 | // get the twitter client 42 | client = TwitterApp.getRestClient(); 43 | 44 | // get/set the toolbar, set the title to home 45 | tweet_detail_toolbar = (Toolbar) findViewById(R.id.tweet_detail_toolbar); 46 | setSupportActionBar(tweet_detail_toolbar); 47 | tweet_detail_toolbar.setTitleTextColor(getResources().getColor(R.color.colorWhite)); 48 | getSupportActionBar().setTitle(""); // remove the default title 49 | tweet_detail_toolbar_title = (TextView) findViewById(R.id.tweet_detail_toolbar_title); 50 | tweet_detail_toolbar_back_button = (ImageView) findViewById(R.id.tweet_detail_toolbar_back_button); 51 | // create the link to the using user's profile 52 | tweet_detail_toolbar_back_button.setOnClickListener(new View.OnClickListener() { 53 | @Override 54 | public void onClick(View v) { 55 | finish(); 56 | } 57 | }); 58 | 59 | // set those variables to values received from the intent 60 | tName = getIntent().getStringExtra("tName"); 61 | tScreenName = getIntent().getStringExtra("tScreenName"); 62 | tBody = getIntent().getStringExtra("tBody"); 63 | tCreatedAt = getIntent().getStringExtra("tCreatedAt"); 64 | tProfileImageUrl = getIntent().getStringExtra("tProfileImageUrl"); 65 | tRetweets = getIntent().getLongExtra("tRetweets", 0); 66 | tLikes = getIntent().getLongExtra("tLikes", 0); 67 | tUid = getIntent().getLongExtra("tUid", -1); 68 | tLiked = getIntent().getBooleanExtra("tLiked", false); 69 | tRetweeted = getIntent().getBooleanExtra("tRetweeted", false); 70 | tMediaFound = getIntent().getBooleanExtra("tMediaFound", false); 71 | 72 | // create the correct variables 73 | ivProfileImageD = (ImageView) findViewById(R.id.ivProfileImageD); 74 | tvNameD = (TextView) findViewById(R.id.tvNameD); 75 | tvScreenNameD = (TextView) findViewById(R.id.tvScreenNameD); 76 | tvBodyD = (TextView) findViewById(R.id.tvBodyD); 77 | tvCreatedAtD = (TextView) findViewById(R.id.tvCreatedAtD); 78 | ibReplyD = (ImageView) findViewById(R.id.ibReplyD); 79 | 80 | tvRetweetsD = (TextView) findViewById(R.id.tvRetweetsD); 81 | tvLikesD = (TextView) findViewById(R.id.tvLikesD); 82 | ivRetweetD = (ImageView) findViewById(R.id.ivRetweetD); 83 | ivLikeD = (ImageView) findViewById(R.id.ivLoveTweetD); 84 | ivTweetedImageD = (ImageView) findViewById(R.id.ivTweetedImageD); 85 | 86 | // set the texts 87 | tvNameD.setText(tName); 88 | tvScreenNameD.setText("@"+tScreenName); 89 | TweetText finalTweet = new TweetText(tBody); 90 | tvBodyD.setText(Html.fromHtml(finalTweet.finalText)); 91 | tvCreatedAtD.setText(tCreatedAt); 92 | tvRetweetsD.setText(Long.toString(tRetweets)); 93 | tvLikesD.setText(Long.toString(tLikes)); 94 | 95 | if (tRetweeted) ivRetweetD.setImageDrawable(ContextCompat.getDrawable(TweetDetailsActivity.this, R.drawable.ic_retweet_green_svg)); 96 | else ivRetweetD.setImageDrawable(ContextCompat.getDrawable(TweetDetailsActivity.this, R.drawable.ic_retweet_svg)); 97 | if (tLiked) ivLikeD.setImageDrawable(ContextCompat.getDrawable(TweetDetailsActivity.this, R.drawable.ic_heart_solid_red_svg)); 98 | else ivLikeD.setImageDrawable(ContextCompat.getDrawable(TweetDetailsActivity.this, R.drawable.ic_heart_clear_light_blue_svg)); 99 | if (tMediaFound) { 100 | ivTweetedImageD.setVisibility(View.VISIBLE); 101 | Glide.with(TweetDetailsActivity.this).load(getIntent().getStringExtra("tMediaUrlHTTPS")) 102 | .bitmapTransform(new RoundedCornersTransformation(TweetDetailsActivity.this, 20, 0)) 103 | .placeholder(R.drawable.ic_picture_placeholder_svg) 104 | .error(R.drawable.ic_picture_placeholder_svg) 105 | .into(ivTweetedImageD); 106 | } else { 107 | ivTweetedImageD.setVisibility(View.GONE); 108 | } 109 | 110 | // set the image profile 111 | Glide.with(this).load(tProfileImageUrl) 112 | .bitmapTransform(new RoundedCornersTransformation(this, 2000, 0)) 113 | .placeholder(R.drawable.ic_person_v3_svg) 114 | .error(R.drawable.ic_person_v3_svg) 115 | .into(ivProfileImageD); 116 | // .override(2048, 2048) 117 | 118 | // Reply listener event 119 | ibReplyD.setOnClickListener(new View.OnClickListener() { 120 | @Override 121 | public void onClick(View v) { 122 | Intent i = new Intent(TweetDetailsActivity.this, ComposeActivity.class); 123 | i.putExtra("text", tScreenName); 124 | startActivity(i); 125 | } 126 | }); 127 | 128 | // Retweet listener event 129 | ivRetweetD.setOnClickListener(new View.OnClickListener() { 130 | @Override 131 | public void onClick(View v) { 132 | if (!tRetweeted){ 133 | client.retweetTweet(tUid, new AsyncHttpResponseHandler() { 134 | @Override 135 | public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { 136 | tvRetweetsD.setText(Long.toString(tRetweets + 1)); 137 | tRetweeted = true; 138 | ivRetweetD.setImageDrawable(ContextCompat.getDrawable(TweetDetailsActivity.this, R.drawable.ic_retweet_green_svg)); 139 | } 140 | 141 | @Override 142 | public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { 143 | Toast.makeText(TweetDetailsActivity.this, "Error occured while processing retweet action", Toast.LENGTH_SHORT).show(); 144 | } 145 | }); 146 | } else { 147 | client.unretweetTweet(tUid, new AsyncHttpResponseHandler() { 148 | @Override 149 | public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { 150 | tvRetweetsD.setText(Long.toString(tRetweets)); 151 | tRetweeted = false; 152 | ivRetweetD.setImageDrawable(ContextCompat.getDrawable(TweetDetailsActivity.this, R.drawable.ic_retweet_svg)); 153 | } 154 | 155 | @Override 156 | public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { 157 | Toast.makeText(TweetDetailsActivity.this, "Error occured while processing unretweet action", Toast.LENGTH_SHORT).show(); 158 | } 159 | }); 160 | } 161 | } 162 | }); 163 | 164 | // Like listener event 165 | ivLikeD.setOnClickListener(new View.OnClickListener() { 166 | @Override 167 | public void onClick(View v) { 168 | if (!tLiked){ 169 | client.likeTweet(tUid, new AsyncHttpResponseHandler() { 170 | @Override 171 | public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { 172 | tvLikesD.setText(Long.toString(tLikes + 1)); 173 | tLiked = true; 174 | ivLikeD.setImageDrawable(ContextCompat.getDrawable(TweetDetailsActivity.this, R.drawable.ic_heart_solid_red_svg)); 175 | } 176 | 177 | @Override 178 | public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { 179 | Toast.makeText(TweetDetailsActivity.this, "Error occured while processing retweet action", Toast.LENGTH_SHORT).show(); 180 | } 181 | }); 182 | } else { 183 | client.unlikeTweet(tUid, new AsyncHttpResponseHandler() { 184 | @Override 185 | public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { 186 | tvLikesD.setText(Long.toString(tLikes)); 187 | tLiked = false; 188 | ivLikeD.setImageDrawable(ContextCompat.getDrawable(TweetDetailsActivity.this, R.drawable.ic_heart_clear_light_blue_svg)); 189 | } 190 | 191 | @Override 192 | public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { 193 | Toast.makeText(TweetDetailsActivity.this, "Error occured while processing unretweet action", Toast.LENGTH_SHORT).show(); 194 | } 195 | }); 196 | } 197 | } 198 | }); 199 | 200 | ivProfileImageD.setOnClickListener(new View.OnClickListener() { 201 | @Override 202 | public void onClick(View v) { 203 | Intent i = new Intent(TweetDetailsActivity.this, UserProfileActivity.class); 204 | i.putExtra("user_uid", tUid); 205 | i.putExtra("screenName", tScreenName); 206 | startActivity(i); 207 | } 208 | }); 209 | } 210 | 211 | @Override 212 | public void onBackPressed() { 213 | // super.onBackPressed(); 214 | finish(); 215 | // Intent i = new Intent(TweetDetailsActivity.this, TimelineActivity.class); 216 | // startActivity(i); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /app/src/main/java/com/codepath/apps/restclienttemplate/TwitterApp.java: -------------------------------------------------------------------------------- 1 | package com.codepath.apps.restclienttemplate; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | import com.raizlabs.android.dbflow.config.FlowConfig; 7 | import com.raizlabs.android.dbflow.config.FlowLog; 8 | import com.raizlabs.android.dbflow.config.FlowManager; 9 | 10 | /* 11 | * This is the Android application itself and is used to configure various settings 12 | * including the image cache in memory and on disk. This also adds a singleton 13 | * for accessing the relevant rest client. 14 | * 15 | * RestClient client = RestApplication.getRestClient(); 16 | * // use client to send requests to API 17 | * 18 | */ 19 | public class TwitterApp extends Application { 20 | private static Context context; 21 | 22 | @Override 23 | public void onCreate() { 24 | super.onCreate(); 25 | 26 | FlowManager.init(new FlowConfig.Builder(this).build()); 27 | FlowLog.setMinimumLoggingLevel(FlowLog.Level.V); 28 | 29 | TwitterApp.context = this; 30 | } 31 | 32 | public static TwitterClient getRestClient() { 33 | return (TwitterClient) TwitterClient.getInstance(TwitterClient.class, TwitterApp.context); 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/java/com/codepath/apps/restclienttemplate/TwitterClient.java: -------------------------------------------------------------------------------- 1 | package com.codepath.apps.restclienttemplate; 2 | 3 | import android.content.Context; 4 | 5 | import com.codepath.oauth.OAuthBaseClient; 6 | import com.github.scribejava.apis.TwitterApi; 7 | import com.github.scribejava.core.builder.api.BaseApi; 8 | import com.loopj.android.http.AsyncHttpResponseHandler; 9 | import com.loopj.android.http.RequestParams; 10 | 11 | /* 12 | * 13 | * This is the object responsible for communicating with a REST API. 14 | * Specify the constants below to change the API being communicated with. 15 | * See a full list of supported API classes: 16 | * https://github.com/scribejava/scribejava/tree/master/scribejava-apis/src/main/java/com/github/scribejava/apis 17 | * Key and Secret are provided by the developer site for the given API i.e dev.twitter.com 18 | * Add methods for each relevant endpoint in the API. 19 | * 20 | * NOTE: You may want to rename this object based on the service i.e TwitterClient or FlickrClient 21 | * 22 | */ 23 | public class TwitterClient extends OAuthBaseClient { 24 | private static final BaseApi REST_API_INSTANCE = TwitterApi.instance(); // Change this 25 | private static final String REST_URL = "https://api.twitter.com/1.1"; // Change this, base API URL 26 | private static final String REST_CONSUMER_KEY = "PXZuhfIR5Vk7hi1X4BKOzf8Jv"; // Change this TODO - GIT IGNORE THIS FILE OR CHANGE KEY WHEN UPLOAD 27 | private static final String REST_CONSUMER_SECRET = "2xCkNFtQy4evatLxCblAI8RlbSPuFsXLb207NzZVAp5jcByxU3"; // Change this TODO - GIT IGNORE THIS FILE 28 | 29 | // TAG for logging stuffs 30 | public static final String TAG = "TwitterClient"; 31 | 32 | // Landing page to indicate the OAuth flow worked in case Chrome for Android 25+ blocks navigation back to the app. 33 | private static final String FALLBACK_URL = "https://codepath.github.io/android-rest-client-template/success.html"; 34 | 35 | // See https://developer.chrome.com/multidevice/android/intents 36 | private static final String REST_CALLBACK_URL_TEMPLATE = "intent://%s#Intent;action=android.intent.action.VIEW;scheme=%s;package=%s;S.browser_fallback_url=%s;end"; 37 | 38 | public TwitterClient(Context context) { 39 | super(context, REST_API_INSTANCE, 40 | REST_URL, 41 | REST_CONSUMER_KEY, // key 42 | REST_CONSUMER_SECRET, 43 | String.format(REST_CALLBACK_URL_TEMPLATE, context.getString(R.string.intent_host), 44 | context.getString(R.string.intent_scheme), context.getPackageName(), FALLBACK_URL)); 45 | } 46 | 47 | // DEFINE METHODS for different API endpoints here 48 | public void getHomeTimeline(int count, AsyncHttpResponseHandler handler) { 49 | String apiUrl = getApiUrl("statuses/home_timeline.json"); 50 | // Can specify query string params directly or through RequestParams. 51 | RequestParams params = new RequestParams(); 52 | params.put("count", count); 53 | params.put("include_entities", true); 54 | params.put("exclude_replies", false); 55 | // params.put("since_id", 1); 56 | // params.put("max_id", 1); 57 | client.get(apiUrl, params, handler); 58 | } 59 | 60 | public void addToTimeline(long maxid, AsyncHttpResponseHandler handler) { 61 | String apiUrl = getApiUrl("statuses/home_timeline.json"); 62 | RequestParams params = new RequestParams(); 63 | params.put("count", 15); 64 | params.put("max_id", maxid-1); 65 | params.put("include_entities", true); 66 | client.get(apiUrl, params, handler); 67 | } 68 | 69 | public void getMentionsTimeline(int count, AsyncHttpResponseHandler handler) { 70 | String apiUrl = getApiUrl("statuses/mentions_timeline.json"); 71 | // Can specify query string params directly or through RequestParams. 72 | RequestParams params = new RequestParams(); 73 | params.put("count", count); 74 | params.put("include_entities", true); 75 | client.get(apiUrl, params, handler); 76 | } 77 | 78 | public void addToMentionsTimeline(long maxid, AsyncHttpResponseHandler handler) { 79 | String apiUrl = getApiUrl("statuses/mentions_timeline.json"); 80 | RequestParams params = new RequestParams(); 81 | params.put("count", 15); 82 | params.put("max_id", maxid-1); 83 | params.put("include_entities", true); 84 | client.get(apiUrl, params, handler); 85 | } 86 | 87 | // Can specify query string params directly or through RequestParams. 88 | public void sendTweet(String message, long reply_uid, AsyncHttpResponseHandler handler) { 89 | String apiUrl = getApiUrl("statuses/update.json"); 90 | RequestParams params = new RequestParams(); 91 | params.put("status", message); 92 | params.put("in_reply_to_status_id", reply_uid); 93 | client.post(apiUrl, params, handler); 94 | } 95 | 96 | public void retweetTweet(long uid, AsyncHttpResponseHandler handler) { 97 | // String apiUrl = getApiUrl("statuses/retweet/:id.json"); // TODO - does this work? 98 | String apiUrl = getApiUrl("statuses/retweet/"+Long.toString(uid)+".json"); 99 | // RequestParams params = new RequestParams(); 100 | // params.put("id", uid); 101 | client.post(apiUrl, null, handler); 102 | } 103 | 104 | public void unretweetTweet(long uid, AsyncHttpResponseHandler handler) { 105 | 106 | // String apiUrl = getApiUrl("statuses/unretweet/:id.json"); // TODO - does this work? 107 | String apiUrl = getApiUrl("statuses/unretweet/"+Long.toString(uid)+".json"); // TODO - does this work? 108 | //"statuses/unretweet/"+Long.toString(uid)+".json" 109 | // RequestParams params = new RequestParams(); 110 | // params.put("id", uid); 111 | client.post(apiUrl, null, handler); 112 | } 113 | 114 | 115 | public void likeTweet(long uid, AsyncHttpResponseHandler handler) { 116 | String apiUrl = getApiUrl("favorites/create.json"); 117 | RequestParams params = new RequestParams(); 118 | params.put("id", uid); 119 | client.post(apiUrl, params, handler); 120 | } 121 | 122 | public void unlikeTweet(long uid, AsyncHttpResponseHandler handler) { 123 | String apiUrl = getApiUrl("favorites/destroy.json"); 124 | RequestParams params = new RequestParams(); 125 | params.put("id", uid); 126 | client.post(apiUrl, params, handler); 127 | } 128 | 129 | public void getUsingUser(AsyncHttpResponseHandler handler) { 130 | String apiUrl = getApiUrl("account/verify_credentials.json"); 131 | RequestParams params = new RequestParams(); 132 | params.put("include_entities", true); 133 | client.get(apiUrl, params, handler); 134 | } 135 | 136 | public void user(long uid, String screenName, AsyncHttpResponseHandler handler) { 137 | // returns the info about a users profile 138 | String apiUrl = getApiUrl("users/show.json"); 139 | RequestParams params = new RequestParams(); 140 | params.put("user_id", uid); 141 | params.put("screen_name", screenName); 142 | params.put("include_entities", true); 143 | client.get(apiUrl, params, handler); 144 | } 145 | 146 | public void getUserTimeline(String screenName, long count, AsyncHttpResponseHandler handler) { 147 | // returns the statuses of the user specified by the uid, set count to 20 and maxId to LONG.MAXVALUE for now 148 | String apiUrl = getApiUrl("statuses/user_timeline.json"); 149 | RequestParams params = new RequestParams(); 150 | params.put("screen_name", screenName); 151 | params.put("count", count); // how many tweets to get 152 | // params.put("include_rts", true); 153 | // params.put("exclude_replies", false); 154 | client.get(apiUrl, params, handler); 155 | } 156 | 157 | public void addToUserTimeline(String screenName, long maxId, AsyncHttpResponseHandler handler) { 158 | // returns the statuses of the user specified by the uid, set count to 20 and maxId to LONG.MAXVALUE for now 159 | String apiUrl = getApiUrl("statuses/user_timeline.json"); 160 | RequestParams params = new RequestParams(); 161 | params.put("max_id", maxId); // used for infinite paginations 162 | params.put("screen_name", screenName); 163 | params.put("count", 30); // how many tweets to get 164 | params.put("include_rts", true); 165 | params.put("exclude_replies", false); 166 | client.get(apiUrl, params, handler); 167 | } 168 | 169 | public void userLookup(String screenNames, AsyncHttpResponseHandler handler) { 170 | // returns whether the current user follows the users in the list of longs 171 | // screenNames is a concatenation of user screen names separated by just commas 172 | String apiUrl = getApiUrl("friendships/lookup.json"); 173 | RequestParams params = new RequestParams(); 174 | params.put("screen_name", screenNames); 175 | client.get(apiUrl, params, handler); 176 | } 177 | 178 | public void followToggle(String screenName, boolean following, AsyncHttpResponseHandler handler){ 179 | String apiUrl; 180 | if (!following) apiUrl = getApiUrl("friendships/create.json"); 181 | else apiUrl = getApiUrl("friendships/destroy.json"); 182 | RequestParams params = new RequestParams(); 183 | params.put("screen_name", screenName); 184 | client.post(apiUrl, params, handler); 185 | } 186 | 187 | public void userFollowers(){ 188 | // https://dev.twitter.com/rest/reference/get/followers/list 189 | // get the users followers 190 | String apiUrl = getApiUrl("followers/list.json"); 191 | RequestParams params = new RequestParams(); 192 | } 193 | 194 | public void userFollowings(){ 195 | // https://dev.twitter.com/rest/reference/get/friends/list 196 | // get the users followings 197 | String apiUrl = getApiUrl("friends/list.json"); 198 | RequestParams params = new RequestParams(); 199 | } 200 | 201 | 202 | 203 | /* 1. Define the endpoint URL with getApiUrl and pass a relative path to the endpoint 204 | * i.e getApiUrl("statuses/home_timeline.json"); 205 | * 2. Define the parameters to pass to the request (query or body) 206 | * i.e RequestParams params = new RequestParams("foo", "bar"); 207 | * 3. Define the request method and make a call to the client 208 | * i.e client.get(apiUrl, params, handler); 209 | * i.e client.post(apiUrl, params, handler); 210 | */ 211 | } 212 | -------------------------------------------------------------------------------- /app/src/main/java/com/codepath/apps/restclienttemplate/UserProfileActivity.java: -------------------------------------------------------------------------------- 1 | package com.codepath.apps.restclienttemplate; 2 | 3 | import android.os.Bundle; 4 | import android.support.design.widget.FloatingActionButton; 5 | import android.support.v4.app.FragmentTransaction; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.util.Log; 8 | import android.view.View; 9 | import android.widget.Button; 10 | import android.widget.ImageView; 11 | import android.widget.ProgressBar; 12 | import android.widget.TextView; 13 | import android.widget.Toast; 14 | 15 | import com.bumptech.glide.Glide; 16 | import com.codepath.apps.restclienttemplate.fragments.UserTimelineFragment; 17 | import com.codepath.apps.restclienttemplate.models.User; 18 | import com.loopj.android.http.JsonHttpResponseHandler; 19 | 20 | import org.json.JSONArray; 21 | import org.json.JSONException; 22 | import org.json.JSONObject; 23 | 24 | import java.text.SimpleDateFormat; 25 | import java.util.Calendar; 26 | 27 | import cz.msebera.android.httpclient.Header; 28 | import jp.wasabeef.glide.transformations.RoundedCornersTransformation; 29 | 30 | public class UserProfileActivity extends AppCompatActivity { 31 | public User user; 32 | private TwitterClient client; 33 | public Long userId; 34 | public String screenName, TAG = "UserProfileActivity"; 35 | 36 | // logging times 37 | Calendar CAL = Calendar.getInstance(); 38 | SimpleDateFormat HMS = new SimpleDateFormat("HH:mm:ss"); 39 | 40 | private ProgressBar pb; 41 | 42 | // get the views 43 | public TextView tvUserNameP, tvScreenNameP, tvSinceP, tvFollowersCount, tvFollowers, tvFollowingsCount, tvFollowings, tvUserDescription, tvFollowsYou; 44 | public ImageView bannerImage; 45 | public Button followButton; 46 | public FloatingActionButton ivProfileImageP; 47 | 48 | @Override 49 | protected void onCreate(Bundle savedInstanceState) { 50 | super.onCreate(savedInstanceState); 51 | setContentView(R.layout.activity_user_profile); 52 | 53 | client = TwitterApp.getRestClient(); 54 | 55 | pb = (ProgressBar) findViewById(R.id.pbLoading); 56 | 57 | userId = getIntent().getLongExtra("user_uid", -1); 58 | screenName = getIntent().getStringExtra("screenName"); 59 | if (userId == -1){ 60 | onError(0, null); 61 | } else if (screenName == null) { 62 | onError(0, "Screen name was null"); 63 | } 64 | 65 | // set the title to the screen name 66 | // getSupportActionBar().setTitle(screenName); 67 | 68 | tvUserNameP = (TextView) findViewById(R.id.tvUserNameP); 69 | tvScreenNameP = (TextView) findViewById(R.id.tvScreenNameP); 70 | tvSinceP = (TextView) findViewById(R.id.tvSinceP); 71 | tvFollowersCount = (TextView) findViewById(R.id.tvFollowersCount); 72 | tvFollowers = (TextView) findViewById(R.id.tvFollowers); 73 | tvFollowingsCount = (TextView) findViewById(R.id.tvFollowingsCount); 74 | tvFollowings = (TextView) findViewById(R.id.tvFollowings); 75 | tvUserDescription = (TextView) findViewById(R.id.tvUserDescription); 76 | tvFollowsYou = (TextView) findViewById(R.id.tvFollowsYou); 77 | 78 | followButton = (Button) findViewById(R.id.followButton); 79 | 80 | ivProfileImageP = (FloatingActionButton) findViewById(R.id.ivProfileImageP); 81 | bannerImage = (ImageView) findViewById(R.id.bannerImage); 82 | // get the user from the following request 83 | client.user(userId, screenName, new JsonHttpResponseHandler() { 84 | @Override 85 | public void onSuccess(int statusCode, Header[] headers, JSONObject response) { 86 | try { 87 | user = User.fromJSON(response); 88 | 89 | // populate the headline, see method below 90 | populateUserHeadline(user); 91 | 92 | } catch (JSONException e) { 93 | onError(1, null); 94 | e.printStackTrace(); 95 | } 96 | 97 | // create the fragment 98 | UserTimelineFragment userTimelineFragment = UserTimelineFragment.newInstance(screenName); 99 | // display the user timeline fragment inside the container 100 | FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); 101 | // make change 102 | ft.replace(R.id.flContainer, userTimelineFragment); 103 | // commit 104 | ft.commit(); 105 | 106 | pb.setVisibility(View.INVISIBLE); // remove the progress bar 107 | } 108 | }); 109 | 110 | } 111 | 112 | public void populateUserHeadline(final User user) { 113 | // populate the screen with info gathered 114 | tvUserNameP.setText(user.name); 115 | tvScreenNameP.setText("@"+user.screenName); 116 | tvSinceP.setText("On Twitter since " + user.createdAt); 117 | tvFollowersCount.setText(Long.toString(user.followersCount)); 118 | tvFollowingsCount.setText(Long.toString(user.followingsCount)); 119 | tvUserDescription.setText(user.description); 120 | if (user.description.length() <= 1) { 121 | tvUserDescription.setVisibility(View.GONE); 122 | } 123 | // add the images 124 | Glide.with(getBaseContext()).load(user.profileImageUrl) 125 | .bitmapTransform(new RoundedCornersTransformation(getBaseContext(), 2000, 0)) 126 | .placeholder(R.drawable.ic_person_v1_svg) 127 | .error(R.drawable.ic_person_v1_svg) 128 | .into(ivProfileImageP); 129 | 130 | Glide.with(getBaseContext()).load(user.profileBannerUrl) 131 | .placeholder(Integer.parseInt(user.profileBackgroundColor, 16)+0xFF000000) 132 | .error(Integer.parseInt(user.profileBackgroundColor, 16)+0xFF000000) 133 | .into(bannerImage); 134 | if (getIntent().getBooleanExtra("using_user", false)) { 135 | followButton.setVisibility(View.GONE); 136 | } else { 137 | followButton.setVisibility(View.VISIBLE); 138 | client.userLookup(screenName, new JsonHttpResponseHandler() { 139 | @Override 140 | public void onSuccess(int statusCode, Header[] headers, JSONArray response) { 141 | Log.e(TAG, response.toString()); 142 | try { 143 | JSONArray connections = response.getJSONObject(0).getJSONArray("connections"); 144 | for (int i = 0; i < connections.length(); i++){ 145 | if (connections.get(i).equals("following")) { 146 | user.following = true; 147 | followButton.setBackground(getResources().getDrawable(R.drawable.button_unfollow)); 148 | followButton.setTextColor(getResources().getColor(R.color.colorWhite)); 149 | followButton.setText("Unfollow"); 150 | } 151 | if (connections.get(i).equals("followed_by")) { 152 | tvFollowsYou.setVisibility(View.VISIBLE); 153 | } 154 | } 155 | } catch (JSONException e) { 156 | e.printStackTrace(); 157 | Log.e(TAG, String.format("Error occured while parsing json in unfollow/follow")); 158 | } 159 | } 160 | 161 | @Override 162 | public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONArray errorResponse) { 163 | Log.e(TAG, errorResponse.toString()); 164 | } 165 | @Override 166 | public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONObject errorResponse) { 167 | Log.e(TAG, errorResponse.toString()); 168 | } 169 | @Override 170 | public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { 171 | Log.e(TAG, responseString); 172 | } 173 | }); 174 | } 175 | followButton.setOnClickListener(new View.OnClickListener() { 176 | @Override 177 | public void onClick(View v) { 178 | // toggle to button to not following (to false) the person (thus to "follow") if we follow the person 179 | // toggle to button to following (to true) the person (thus to "unfollow") if we don't follow the person 180 | toggleFollowButton(!user.following); 181 | 182 | // true means we follow the user and want to unfollow them, 183 | // changes the status of following for the user if good 184 | followToggle(user.following, screenName, user); 185 | } 186 | }); 187 | 188 | } 189 | 190 | public void toggleFollowButton(boolean toFollowing) { 191 | if (toFollowing) { 192 | followButton.setBackground(getResources().getDrawable(R.drawable.button_unfollow)); 193 | followButton.setTextColor(getResources().getColor(R.color.colorWhite)); 194 | followButton.setText("Unfollow"); 195 | } else { 196 | followButton.setBackground(getResources().getDrawable(R.drawable.button_follow)); 197 | followButton.setTextColor(getResources().getColor(R.color.colorAccent)); 198 | followButton.setText("Follow"); 199 | } 200 | } 201 | 202 | public void followToggle(final boolean followsUser, String screenName, final User user) { 203 | final String text = followsUser ? "unfollowing" : "following"; 204 | client.followToggle(screenName, followsUser, new JsonHttpResponseHandler() { 205 | @Override 206 | public void onSuccess(int statusCode, Header[] headers, JSONObject response) { 207 | super.onSuccess(statusCode, headers, response); 208 | Toast.makeText(getBaseContext(), String.format("Success %s", text), Toast.LENGTH_SHORT).show(); 209 | // if it worked, change the status of user to the opposite of what it used to be 210 | user.following = !followsUser; 211 | } 212 | 213 | @Override 214 | public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONObject errorResponse) { 215 | Log.e(client.TAG, String.format("Failure %s: %s", text, errorResponse.toString())); 216 | throwable.printStackTrace(); 217 | Toast.makeText(getBaseContext(), String.format("Failure %s", text), Toast.LENGTH_SHORT).show(); 218 | toggleFollowButton(followsUser); 219 | } 220 | 221 | @Override 222 | public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONArray errorResponse) { 223 | Log.e(client.TAG, String.format("Failure %s: %s", text, errorResponse.toString())); 224 | throwable.printStackTrace(); 225 | Toast.makeText(getBaseContext(), String.format("Failure %s", text), Toast.LENGTH_SHORT).show(); 226 | toggleFollowButton(followsUser); 227 | } 228 | 229 | @Override 230 | public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { 231 | Log.e(client.TAG, String.format("Failure %s: %s", text, responseString)); 232 | throwable.printStackTrace(); 233 | Toast.makeText(getBaseContext(), String.format("Failure %s", text), Toast.LENGTH_SHORT).show(); 234 | toggleFollowButton(followsUser); 235 | } 236 | }); 237 | } 238 | 239 | public void onError(int code, String message) { 240 | String onErrorString = "onError error occurred"; 241 | switch (code) { 242 | case 0: { 243 | // error while parsing the userId 244 | Toast.makeText(getBaseContext(), String.format("Error occurred while parsing the userId. %s", message), Toast.LENGTH_LONG).show(); 245 | break; 246 | } 247 | case 1: { 248 | // error while parsing the json from twitter server response 249 | Toast.makeText(getBaseContext(), String.format("Error occurred while while parsing the json from the twitter server response. %s", message), Toast.LENGTH_LONG).show(); 250 | break; 251 | } 252 | case 2: { 253 | // error while sending a request to twitter server 254 | Toast.makeText(getBaseContext(), String.format("Error occurred while while sending a request to the twitter server. %s", message), Toast.LENGTH_LONG).show(); 255 | break; 256 | } 257 | default: { 258 | // unidentified error 259 | Toast.makeText(getBaseContext(), String.format("Unidentified error occured."), Toast.LENGTH_LONG).show(); 260 | break; 261 | } 262 | } 263 | Log.e(TAG, String.format("%s at %s: %s", onErrorString, code, message)); 264 | finish(); // finish because bad error 265 | } 266 | 267 | 268 | // Toast statement skeleton 269 | // Toast.makeText(getBaseContext(), String.format("%s"), Toast.LENGTH_LONG).show(); 270 | } 271 | 272 | 273 | -------------------------------------------------------------------------------- /app/src/main/java/com/codepath/apps/restclienttemplate/fragments/HomeTimelineFragment.java: -------------------------------------------------------------------------------- 1 | package com.codepath.apps.restclienttemplate.fragments; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v4.widget.SwipeRefreshLayout; 6 | import android.support.v7.widget.RecyclerView; 7 | import android.util.Log; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.Toast; 12 | 13 | import com.codepath.apps.restclienttemplate.R; 14 | import com.codepath.apps.restclienttemplate.TwitterApp; 15 | import com.codepath.apps.restclienttemplate.TwitterClient; 16 | import com.codepath.apps.restclienttemplate.models.Tweet; 17 | import com.loopj.android.http.JsonHttpResponseHandler; 18 | 19 | import org.json.JSONArray; 20 | import org.json.JSONObject; 21 | 22 | import cz.msebera.android.httpclient.Header; 23 | 24 | /** 25 | * Created by robertvunabandi on 7/3/17. 26 | */ 27 | 28 | public class HomeTimelineFragment extends TweetsListFragment { 29 | private TwitterClient client; 30 | int totalTweets = 10; 31 | 32 | @Override 33 | public void onCreate(@Nullable Bundle savedInstanceState) { 34 | super.onCreate(savedInstanceState); 35 | 36 | client = TwitterApp.getRestClient(); 37 | populateTimeline(totalTweets); 38 | } 39 | 40 | @Nullable 41 | @Override 42 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 43 | // return super.onCreateView(inflater, container, savedInstanceState); 44 | View v = inflater.inflate(R.layout.fragments_tweets_list, container, false); 45 | rvTweets = (RecyclerView) v.findViewById(R.id.rvTweet); 46 | 47 | // swipe container 48 | swipeContainer = (SwipeRefreshLayout) v.findViewById(R.id.swipeContainerRefresher); 49 | swipeContainer.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { 50 | @Override 51 | public void onRefresh() { 52 | // Toast.makeText(getContext(), "Error occurred. This function is not implemented.", Toast.LENGTH_SHORT).show(); 53 | reinitializeTweetsAndAdapter(); 54 | populateTimeline(getTweetSize()); 55 | swipeContainer.setRefreshing(false); 56 | } 57 | }); 58 | swipeContainer.setColorSchemeResources(R.color.colorPrimaryDarkLight, R.color.colorLightGrey, R.color.colorAccent, R.color.colorPrimaryLight); 59 | 60 | // infinite scroll 61 | rvTweets.addOnScrollListener(new RecyclerView.OnScrollListener() { 62 | @Override 63 | public void onScrolled(RecyclerView recyclerView, int dx, int dy) { 64 | if (dy > 0) { 65 | int lastPosition = layoutManager.findLastVisibleItemPosition(); 66 | if (lastPosition > tweets.size() - 2) { 67 | addToTimeline(); 68 | } 69 | } 70 | } 71 | }); 72 | 73 | reinitializeTweetsAndAdapter(); 74 | return v; 75 | 76 | } 77 | 78 | public void populateTimeline(int count) { 79 | // here, the progress bar is not set to visible because this function is always 80 | // called after reinitializeTweetsAndAdapter(), which makes the progress bar visible 81 | // so we get straight into it with the client.getHomeTimeline 82 | 83 | // set progressbar to visible 84 | client.getHomeTimeline(count, new JsonHttpResponseHandler() { 85 | @Override 86 | public void onSuccess(int statusCode, Header[] headers, JSONObject response) { 87 | // FOR JSON OBJECTS 88 | Log.d(client.TAG, response.toString()); 89 | } 90 | @Override 91 | public void onSuccess(int statusCode, Header[] headers, JSONArray response) { 92 | // iterate through the JSON array, for each entry deserialize the JSON Object 93 | Log.d(TAG, String.format("PopulateTimeline | RESPONSE LENGTH %s SUCCESS at %s", response.length(), HMS.format(CAL.getTime()))); 94 | addItems(response, true); // we know we follow the people on our timeline 95 | } 96 | @Override 97 | public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { 98 | Log.e(client.TAG, String.format("REGULAR FAILURE: %s" , responseString)); 99 | throwable.printStackTrace(); 100 | 101 | Toast.makeText(getContext(), String.format("An error occurred while acquiring the data. Please try again in 2 minutes."), Toast.LENGTH_SHORT).show(); 102 | } 103 | @Override 104 | public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONObject errorResponse) { 105 | Log.e(client.TAG, String.format("JSON OBJECT FAILURE: %s" ,errorResponse.toString())); 106 | throwable.printStackTrace(); 107 | 108 | Toast.makeText(getContext(), String.format("An error occurred while acquiring the data. Please try again in 2 minutes."), Toast.LENGTH_SHORT).show(); 109 | } 110 | @Override 111 | public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONArray errorResponse) { 112 | Log.e(client.TAG, String.format("JSON ARRAY FAILURE: %s" ,errorResponse.toString())); 113 | throwable.printStackTrace(); 114 | 115 | Toast.makeText(getContext(), String.format("An error occurred while acquiring the data. Please try again in 2 minutes."), Toast.LENGTH_SHORT).show(); 116 | } 117 | }); 118 | } 119 | 120 | public void addToTimeline() { 121 | Log.d(TAG, String.format("addToTimeline at %s", HMS.format(CAL.getTime()))); 122 | 123 | client.addToTimeline(max_id, new JsonHttpResponseHandler() { 124 | @Override 125 | public void onSuccess(int statusCode, Header[] headers, JSONArray response) { 126 | addItems(response, true); // we know we follow the people on our timeline 127 | totalTweets = getTweetSize(); // changes the number of total tweets 128 | Log.d(TAG, String.format("tweet size: %s, and maxid: %s, PopulateTimeline Home at %s", totalTweets, max_id, HMS.format(CAL.getTime()))); 129 | } 130 | @Override 131 | public void onSuccess(int statusCode, Header[] headers, JSONObject response) { 132 | Log.d(concatTag(TAG, client.TAG), response.toString()); 133 | } 134 | @Override 135 | public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONObject errorResponse) { 136 | Log.e(concatTag(TAG, client.TAG), errorResponse.toString() + "Damn 1"); 137 | throwable.printStackTrace(); 138 | 139 | Toast.makeText(getContext(), String.format("An error occurred. Unable to load more tweets."), Toast.LENGTH_SHORT).show(); 140 | } 141 | @Override 142 | public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONArray errorResponse) { 143 | Log.e(concatTag(TAG, client.TAG), errorResponse.toString() + "Damn 2"); 144 | throwable.printStackTrace(); 145 | 146 | Toast.makeText(getContext(), String.format("An error occurred. Unable to load more tweets."), Toast.LENGTH_SHORT).show(); 147 | } 148 | @Override 149 | public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { 150 | Log.e(concatTag(TAG, client.TAG), responseString + "Damn 3"); 151 | throwable.printStackTrace(); 152 | 153 | Toast.makeText(getContext(), String.format("An error occurred. Unable to load more tweets."), Toast.LENGTH_SHORT).show(); 154 | } 155 | }); 156 | } 157 | 158 | public void addTweet(Tweet tweet){ 159 | addItemOne(tweet, 0); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /app/src/main/java/com/codepath/apps/restclienttemplate/fragments/MentionsTimelineFragment.java: -------------------------------------------------------------------------------- 1 | package com.codepath.apps.restclienttemplate.fragments; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v4.widget.SwipeRefreshLayout; 6 | import android.support.v7.widget.RecyclerView; 7 | import android.util.Log; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.Toast; 12 | 13 | import com.codepath.apps.restclienttemplate.R; 14 | import com.codepath.apps.restclienttemplate.TwitterApp; 15 | import com.codepath.apps.restclienttemplate.TwitterClient; 16 | import com.loopj.android.http.JsonHttpResponseHandler; 17 | 18 | import org.json.JSONArray; 19 | import org.json.JSONObject; 20 | 21 | import cz.msebera.android.httpclient.Header; 22 | 23 | /** 24 | * Created by robertvunabandi on 7/3/17. 25 | */ 26 | 27 | public class MentionsTimelineFragment extends TweetsListFragment { 28 | // swipe container 29 | private TwitterClient client; 30 | int totalTweets = 15; 31 | 32 | @Override 33 | public void onCreate(@Nullable Bundle savedInstanceState) { 34 | super.onCreate(savedInstanceState); 35 | 36 | client = TwitterApp.getRestClient(); 37 | populateTimeline(totalTweets); 38 | } 39 | 40 | @Nullable 41 | @Override 42 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 43 | // return super.onCreateView(inflater, container, savedInstanceState); 44 | View v = inflater.inflate(R.layout.fragments_tweets_list, container, false); 45 | rvTweets = (RecyclerView) v.findViewById(R.id.rvTweet); 46 | 47 | // swipe container 48 | swipeContainer = (SwipeRefreshLayout) v.findViewById(R.id.swipeContainerRefresher); 49 | swipeContainer.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { 50 | @Override 51 | public void onRefresh() { 52 | // TODO - Implement this functions 53 | // Toast.makeText(getContext(), "Error occurred. This function is not implemented.", Toast.LENGTH_SHORT).show(); 54 | reinitializeTweetsAndAdapter(); 55 | populateTimeline(getTweetSize()); 56 | swipeContainer.setRefreshing(false); 57 | } 58 | }); 59 | swipeContainer.setColorSchemeResources(R.color.colorPrimaryDarkLight, R.color.colorLightGrey, R.color.colorAccent, R.color.colorPrimaryLight); 60 | 61 | // infinite scroll 62 | rvTweets.addOnScrollListener(new RecyclerView.OnScrollListener() { 63 | @Override 64 | public void onScrolled(RecyclerView recyclerView, int dx, int dy) { 65 | if (dy > 0) { 66 | int lastPosition = layoutManager.findLastVisibleItemPosition(); 67 | if (lastPosition > tweets.size() - 3) { 68 | addToTimeline(); 69 | } 70 | } 71 | } 72 | }); 73 | 74 | reinitializeTweetsAndAdapter(); 75 | return v; 76 | 77 | } 78 | 79 | public void populateTimeline(int count) { 80 | // here, the progress bar is not set to visible because this function is always 81 | // called after reinitializeTweetsAndAdapter(), which makes the progress bar visible 82 | // so we get straight into it with the client.getHomeTimeline 83 | 84 | client.getMentionsTimeline(count, new JsonHttpResponseHandler() { 85 | @Override 86 | public void onSuccess(int statusCode, Header[] headers, JSONObject response) { 87 | // FOR JSON OBJECTS 88 | Log.d(client.TAG, response.toString()); 89 | } 90 | @Override 91 | public void onSuccess(int statusCode, Header[] headers, JSONArray response) { 92 | // iterate through the JSON array, for each entry deserialize the JSON Object 93 | Log.d(TAG, String.format("PopulateTimeline | RESPONSE LENGTH %s SUCCESS at %s", response.length(), HMS.format(CAL.getTime()))); 94 | addItems(response, false); // we don't know if we follow these people or not 95 | } 96 | @Override 97 | public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { 98 | Log.e(client.TAG, String.format("REGULAR FAILURE: %s" , responseString)); 99 | throwable.printStackTrace(); 100 | 101 | Toast.makeText(getContext(), String.format("An error occurred while acquiring the data. Please try again in 2 minutes."), Toast.LENGTH_LONG).show(); 102 | } 103 | @Override 104 | public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONObject errorResponse) { 105 | Log.e(client.TAG, String.format("JSON OBJECT FAILURE: %s" ,errorResponse.toString())); 106 | throwable.printStackTrace(); 107 | 108 | Toast.makeText(getContext(), String.format("An error occurred while acquiring the data. Please try again in 2 minutes."), Toast.LENGTH_LONG).show(); 109 | } 110 | @Override 111 | public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONArray errorResponse) { 112 | Log.e(client.TAG, String.format("JSON ARRAY FAILURE: %s" ,errorResponse.toString())); 113 | throwable.printStackTrace(); 114 | 115 | Toast.makeText(getContext(), String.format("An error occurred while acquiring the data. Please try again in 2 minutes."), Toast.LENGTH_LONG).show(); 116 | } 117 | }); 118 | } 119 | 120 | public void addToTimeline() { 121 | Log.d(TAG, String.format("addToTimeline at %s", HMS.format(CAL.getTime()))); 122 | 123 | client.addToMentionsTimeline(max_id, new JsonHttpResponseHandler() { 124 | @Override 125 | public void onSuccess(int statusCode, Header[] headers, JSONArray response) { 126 | addItems(response, false); // we don't know if we follow these people or not 127 | totalTweets = getTweetSize(); // changes the number of total tweets 128 | Log.d(TAG, String.format("tweet size: %s, and maxid: %s, PopulateTimelin Mentions at %s", totalTweets, max_id, HMS.format(CAL.getTime()))); 129 | } 130 | @Override 131 | public void onSuccess(int statusCode, Header[] headers, JSONObject response) { 132 | Log.d(client.TAG, response.toString()); 133 | } 134 | @Override 135 | public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONObject errorResponse) { 136 | Log.e(client.TAG, errorResponse.toString()); 137 | throwable.printStackTrace(); 138 | 139 | Toast.makeText(getContext(), String.format("An error occurred while acquiring the data. Please try again in 15 minutes."), Toast.LENGTH_LONG).show(); 140 | } 141 | @Override 142 | public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONArray errorResponse) { 143 | Log.e(client.TAG, errorResponse.toString()); 144 | throwable.printStackTrace(); 145 | 146 | Toast.makeText(getContext(), String.format("An error occurred while acquiring the data. Please try again in 15 minutes."), Toast.LENGTH_LONG).show(); 147 | } 148 | @Override 149 | public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { 150 | Log.e(client.TAG, responseString); 151 | throwable.printStackTrace(); 152 | 153 | Toast.makeText(getContext(), String.format("An error occurred while acquiring the data. Please try again in 15 minutes."), Toast.LENGTH_LONG).show(); 154 | } 155 | }); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /app/src/main/java/com/codepath/apps/restclienttemplate/fragments/TweetsListFragment.java: -------------------------------------------------------------------------------- 1 | package com.codepath.apps.restclienttemplate.fragments; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v4.app.Fragment; 6 | import android.support.v4.widget.SwipeRefreshLayout; 7 | import android.support.v7.widget.LinearLayoutManager; 8 | import android.support.v7.widget.RecyclerView; 9 | import android.util.Log; 10 | import android.view.LayoutInflater; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | 14 | import com.codepath.apps.restclienttemplate.R; 15 | import com.codepath.apps.restclienttemplate.TweetAdapter; 16 | import com.codepath.apps.restclienttemplate.models.Tweet; 17 | 18 | import org.json.JSONArray; 19 | import org.json.JSONException; 20 | 21 | import java.text.SimpleDateFormat; 22 | import java.util.ArrayList; 23 | import java.util.Calendar; 24 | 25 | /** 26 | * Created by robertvunabandi on 7/3/17. 27 | */ 28 | 29 | public class TweetsListFragment extends Fragment { 30 | 31 | public final int REQUEST_CODE = 200; 32 | public static final String TAG = "TweetsListFragment"; 33 | public long max_id = Long.MAX_VALUE; // lowest id 34 | 35 | // for swiping to refresh tweets 36 | public SwipeRefreshLayout swipeContainer; 37 | 38 | // declare important variables 39 | TweetAdapter tweetAdapter; 40 | ArrayList tweets; 41 | public RecyclerView rvTweets; 42 | 43 | private HomeTimelineFragment timelineFragment; 44 | private MentionsTimelineFragment mentionFragment; 45 | 46 | // the linear layout 47 | LinearLayoutManager layoutManager = new LinearLayoutManager(getContext()); 48 | 49 | // Timer 50 | Calendar CAL = Calendar.getInstance(); 51 | SimpleDateFormat HMS = new SimpleDateFormat("HH:mm:ss"); 52 | 53 | // inflation happens inside onCreateView 54 | @Nullable 55 | @Override 56 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 57 | // inflate layout 58 | View v = inflater.inflate(R.layout.fragments_tweets_list, container, false); 59 | rvTweets = (RecyclerView) v.findViewById(R.id.rvTweet); 60 | 61 | // swipe container 62 | swipeContainer = (SwipeRefreshLayout) v.findViewById(R.id.swipeContainerRefresher); 63 | swipeContainer.setColorSchemeResources(R.color.colorPrimaryDarkLight, R.color.colorLightGrey, R.color.colorAccent, R.color.colorPrimaryLight); 64 | 65 | reinitializeTweetsAndAdapter(); 66 | return v; 67 | } 68 | 69 | public void reinitializeTweetsAndAdapter(){ 70 | tweets = new ArrayList<>(); // init the array list (data source) 71 | tweetAdapter = new TweetAdapter(tweets); // construct adapter from data source 72 | // tweetAdapter.max_id = max_id_from_timeline; // TEMP 73 | rvTweets.setLayoutManager(layoutManager); // RecyclerView setup (layout manager, use adapter) 74 | rvTweets.setAdapter(tweetAdapter); // set the Adapter 75 | 76 | Log.d(TAG, String.format("Tweets reinitialized at %s", HMS.format(CAL.getTime()))); 77 | } 78 | 79 | public void addItems(JSONArray response, boolean following){ 80 | for (int i = 0; i < response.length(); i++){ 81 | // convert each object into a tweet model inserted in the following tweet object 82 | Tweet tweet; 83 | try { 84 | tweet = Tweet.fromJSON(response.getJSONObject(i)); 85 | tweet.user.following = following; 86 | // Log.d(TAG, String.format("New tweet at index %s of response on addItems. MAXID: %s, Tweet's id: %s", i, max_id, tweet.uid)); 87 | // add the tweet model to our data source 88 | tweets.add(tweet); 89 | // notify the adapter that we've added an item 90 | tweetAdapter.notifyItemInserted(tweets.size() - 1); 91 | // bc we start from 0, the total number of tweets added to our array list will be the size of tweets - 1 92 | 93 | // update the max id of this adapter 94 | max_id = tweet.uid < max_id ? tweet.uid : max_id; 95 | Log.d(TAG, String.format("PopulateTimeline | Tweet at index %s SUCCESS at %s", i, HMS.format(CAL.getTime()))); 96 | } catch (JSONException e) { 97 | Log.d(TAG, String.format("PopulateTimeline | Tweet at index %s FAILED at %s", i, HMS.format(CAL.getTime()))); 98 | e.printStackTrace(); 99 | } 100 | 101 | } 102 | Log.d(TAG, String.format("PopulateTimeline SUCCESS at %s", HMS.format(CAL.getTime()))); 103 | } 104 | 105 | public void addItemOne(Tweet tweet, int position) { 106 | tweets.add(position, tweet); 107 | tweetAdapter.notifyItemInserted(position); 108 | // scrolls back to position 0 to see the tweet 109 | rvTweets.scrollToPosition(position); 110 | } 111 | 112 | public long getMaxId() { 113 | return max_id - 1; 114 | } 115 | public int getTweetSize() { 116 | return tweets.size(); 117 | } 118 | 119 | public String concatTag(String TAG1, String TAG2) { 120 | return TAG1 + ": " + TAG2; 121 | } 122 | 123 | private HomeTimelineFragment getTimelineFragment() { 124 | if (timelineFragment == null) { 125 | timelineFragment = new HomeTimelineFragment(); 126 | } 127 | return timelineFragment; 128 | } 129 | 130 | private MentionsTimelineFragment getMentionFragment() { 131 | if (timelineFragment == null) { 132 | mentionFragment = new MentionsTimelineFragment(); 133 | } 134 | return mentionFragment; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /app/src/main/java/com/codepath/apps/restclienttemplate/fragments/TweetsPagerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.codepath.apps.restclienttemplate.fragments; 2 | 3 | import android.content.Context; 4 | import android.support.v4.app.Fragment; 5 | import android.support.v4.app.FragmentManager; 6 | import android.support.v4.app.FragmentPagerAdapter; 7 | 8 | /** 9 | * Created by robertvunabandi on 7/3/17. 10 | */ 11 | 12 | public class TweetsPagerAdapter extends FragmentPagerAdapter { 13 | 14 | // TODO - Add a search fragment 15 | private String[] tabTitles = new String[]{"Home", "Mentions"}; 16 | private Context context; 17 | 18 | private HomeTimelineFragment timelineFragment; 19 | private MentionsTimelineFragment mentionsFragment; 20 | 21 | public TweetsPagerAdapter(FragmentManager fm, Context context) { 22 | super(fm); 23 | timelineFragment = new HomeTimelineFragment(); 24 | mentionsFragment = new MentionsTimelineFragment(); 25 | this.context = context; 26 | } 27 | 28 | // return the total number of fragments there are (similar to getCount from RecyclerView 29 | @Override 30 | public int getCount() { 31 | return 2; 32 | } 33 | 34 | // return the fragment to user depending on the position 35 | @Override 36 | public Fragment getItem(int position) { 37 | // if (position == 0) return new HomeTimelineFragment(); 38 | // else if (position == 1) return new MentionsTimelineFragment(); 39 | 40 | if (position == 0) return timelineFragment; 41 | else if (position == 1) return mentionsFragment; 42 | else return null; 43 | } 44 | 45 | // return the fragment title 46 | @Override 47 | public CharSequence getPageTitle(int position) { 48 | return tabTitles[position]; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/codepath/apps/restclienttemplate/fragments/UserTimelineFragment.java: -------------------------------------------------------------------------------- 1 | package com.codepath.apps.restclienttemplate.fragments; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v4.widget.SwipeRefreshLayout; 6 | import android.support.v7.widget.RecyclerView; 7 | import android.util.Log; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.Toast; 12 | 13 | import com.codepath.apps.restclienttemplate.R; 14 | import com.codepath.apps.restclienttemplate.TwitterApp; 15 | import com.codepath.apps.restclienttemplate.TwitterClient; 16 | import com.loopj.android.http.JsonHttpResponseHandler; 17 | 18 | import org.json.JSONArray; 19 | import org.json.JSONObject; 20 | 21 | import cz.msebera.android.httpclient.Header; 22 | 23 | /** 24 | * Created by robertvunabandi on 7/4/17. 25 | */ 26 | 27 | public class UserTimelineFragment extends TweetsListFragment { 28 | private TwitterClient client; 29 | int totalTweets = 10; 30 | 31 | public static UserTimelineFragment newInstance(String screenName) { 32 | UserTimelineFragment userTimelineFragment = new UserTimelineFragment(); 33 | Bundle args = new Bundle(); 34 | args.putString("screen_name", screenName); 35 | userTimelineFragment.setArguments(args); 36 | return userTimelineFragment; 37 | } 38 | 39 | @Override 40 | public void onCreate(@Nullable Bundle savedInstanceState) { 41 | super.onCreate(savedInstanceState); 42 | 43 | client = TwitterApp.getRestClient(); 44 | populateTimeline(totalTweets); 45 | } 46 | 47 | @Nullable 48 | @Override 49 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 50 | // return super.onCreateView(inflater, container, savedInstanceState); 51 | View v = inflater.inflate(R.layout.fragments_tweets_list, container, false); 52 | rvTweets = (RecyclerView) v.findViewById(R.id.rvTweet); 53 | 54 | // swipe container 55 | swipeContainer = (SwipeRefreshLayout) v.findViewById(R.id.swipeContainerRefresher); 56 | swipeContainer.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { 57 | @Override 58 | public void onRefresh() { 59 | // Toast.makeText(getContext(), "Error occurred. This function is not implemented.", Toast.LENGTH_SHORT).show(); 60 | reinitializeTweetsAndAdapter(); 61 | populateTimeline(getTweetSize()); 62 | swipeContainer.setRefreshing(false); 63 | } 64 | }); 65 | swipeContainer.setColorSchemeResources(R.color.colorPrimaryDarkLight, R.color.colorLightGrey, R.color.colorAccent, R.color.colorPrimaryLight); 66 | 67 | // infinite scroll 68 | rvTweets.addOnScrollListener(new RecyclerView.OnScrollListener() { 69 | @Override 70 | public void onScrolled(RecyclerView recyclerView, int dx, int dy) { 71 | if (dy > 0) { 72 | int lastPosition = layoutManager.findLastVisibleItemPosition(); 73 | if (lastPosition > tweets.size() - 2) { 74 | addToTimeline(); 75 | } 76 | } 77 | } 78 | }); 79 | 80 | reinitializeTweetsAndAdapter(); 81 | return v; 82 | 83 | } 84 | 85 | public void populateTimeline(int count) { 86 | // here, the progress bar is not set to visible because this function is always 87 | // called after reinitializeTweetsAndAdapter(), which makes the progress bar visible 88 | // so we get straight into it with the client.getHomeTimeline 89 | 90 | // set progressbar to visible 91 | client.getUserTimeline(getArguments().getString("screen_name"), count, new JsonHttpResponseHandler() { 92 | @Override 93 | public void onSuccess(int statusCode, Header[] headers, JSONObject response) { 94 | // FOR JSON OBJECTS 95 | Log.d(client.TAG, response.toString()); 96 | } 97 | @Override 98 | public void onSuccess(int statusCode, Header[] headers, JSONArray response) { 99 | // iterate through the JSON array, for each entry deserialize the JSON Object 100 | Log.d(TAG, String.format("PopulateTimeline | RESPONSE LENGTH %s SUCCESS at %s", response.length(), HMS.format(CAL.getTime()))); 101 | addItems(response, false); // we don't know follow ourselves 102 | } 103 | @Override 104 | public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { 105 | Log.e(client.TAG, String.format("REGULAR FAILURE: %s" , responseString)); 106 | throwable.printStackTrace(); 107 | 108 | Toast.makeText(getContext(), String.format("An error occurred while acquiring the data. Please try again in 2 minutes."), Toast.LENGTH_LONG).show(); 109 | } 110 | @Override 111 | public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONObject errorResponse) { 112 | Log.e(client.TAG, String.format("JSON OBJECT FAILURE: %s" ,errorResponse.toString())); 113 | throwable.printStackTrace(); 114 | 115 | Toast.makeText(getContext(), String.format("An error occurred while acquiring the data. Please try again in 2 minutes."), Toast.LENGTH_LONG).show(); 116 | } 117 | @Override 118 | public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONArray errorResponse) { 119 | Log.e(client.TAG, String.format("JSON ARRAY FAILURE: %s" ,errorResponse.toString())); 120 | throwable.printStackTrace(); 121 | 122 | Toast.makeText(getContext(), String.format("An error occurred while acquiring the data. Please try again in 2 minutes."), Toast.LENGTH_LONG).show(); 123 | } 124 | }); 125 | } 126 | 127 | public void addToTimeline() { 128 | Log.d(TAG, String.format("addToTimeline at %s", HMS.format(CAL.getTime()))); 129 | 130 | client.addToUserTimeline(getArguments().getString("screen_name"), max_id, new JsonHttpResponseHandler() { 131 | @Override 132 | public void onSuccess(int statusCode, Header[] headers, JSONArray response) { 133 | addItems(response, false); // we don't know follow ourselves 134 | totalTweets = getTweetSize(); // changes the number of total tweets 135 | Log.d(TAG, String.format("PopulateTimeline SUCCESS at %s", HMS.format(CAL.getTime()))); 136 | Log.d(TAG, String.format("tweet size: %s, and maxid: %s, PopulateTimeline User at %s", totalTweets, max_id, HMS.format(CAL.getTime()))); 137 | } 138 | @Override 139 | public void onSuccess(int statusCode, Header[] headers, JSONObject response) { 140 | Log.d(client.TAG, response.toString()); 141 | } 142 | @Override 143 | public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONObject errorResponse) { 144 | Log.e(client.TAG, errorResponse.toString() + "Damn 1"); 145 | throwable.printStackTrace(); 146 | 147 | Toast.makeText(getContext(), String.format("An error occurred. Unable to load more tweets."), Toast.LENGTH_SHORT).show(); 148 | } 149 | @Override 150 | public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONArray errorResponse) { 151 | Log.e(client.TAG, errorResponse.toString() + "Damn 2"); 152 | throwable.printStackTrace(); 153 | 154 | Toast.makeText(getContext(), String.format("An error occurred. Unable to load more tweets."), Toast.LENGTH_SHORT).show(); 155 | } 156 | @Override 157 | public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { 158 | Log.e(client.TAG, responseString + "Damn 3"); 159 | throwable.printStackTrace(); 160 | 161 | Toast.makeText(getContext(), String.format("An error occurred. Unable to load more tweets."), Toast.LENGTH_SHORT).show(); 162 | } 163 | }); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /app/src/main/java/com/codepath/apps/restclienttemplate/models/Media.java: -------------------------------------------------------------------------------- 1 | package com.codepath.apps.restclienttemplate.models; 2 | 3 | import org.json.JSONArray; 4 | import org.json.JSONException; 5 | import org.json.JSONObject; 6 | import org.parceler.Parcel; 7 | 8 | /** 9 | * Created by robertvunabandi on 7/1/17. 10 | */ 11 | 12 | @Parcel 13 | public class Media { 14 | public String url, urlHTTPS; 15 | public long uid; 16 | 17 | public Media() {} 18 | 19 | public static Media fromJSON(JSONArray jsonObject) throws JSONException { 20 | Media media = new Media(); 21 | JSONObject MED = jsonObject.getJSONObject(0); 22 | // extract the values 23 | media.url = MED.getString("media_url"); 24 | media.urlHTTPS = MED.getString("media_url_https"); 25 | media.uid = MED.getLong("id"); 26 | 27 | return media; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/codepath/apps/restclienttemplate/models/SampleModel.java: -------------------------------------------------------------------------------- 1 | package com.codepath.apps.restclienttemplate.models; 2 | 3 | import com.codepath.apps.restclienttemplate.MyDatabase; 4 | import com.raizlabs.android.dbflow.annotation.Column; 5 | import com.raizlabs.android.dbflow.annotation.PrimaryKey; 6 | import com.raizlabs.android.dbflow.annotation.Table; 7 | import com.raizlabs.android.dbflow.sql.language.Select; 8 | import com.raizlabs.android.dbflow.structure.BaseModel; 9 | 10 | import org.json.JSONException; 11 | import org.json.JSONObject; 12 | 13 | import java.util.List; 14 | 15 | /* 16 | * This is a temporary, sample model that demonstrates the basic structure 17 | * of a SQLite persisted Model object. Check out the DBFlow wiki for more details: 18 | * https://github.com/codepath/android_guides/wiki/DBFlow-Guide 19 | * 20 | * Note: All models **must extend from** `BaseModel` as shown below. 21 | * 22 | */ 23 | @Table(database = MyDatabase.class) 24 | public class SampleModel extends BaseModel { 25 | 26 | @PrimaryKey 27 | @Column 28 | Long id; 29 | 30 | // Define table fields 31 | @Column 32 | private String name; 33 | 34 | public SampleModel() { 35 | super(); 36 | } 37 | 38 | // Parse model from JSON 39 | public SampleModel(JSONObject object){ 40 | super(); 41 | 42 | try { 43 | this.name = object.getString("title"); 44 | } catch (JSONException e) { 45 | e.printStackTrace(); 46 | } 47 | } 48 | 49 | // Getters 50 | public String getName() { 51 | return name; 52 | } 53 | 54 | // Setters 55 | public void setName(String name) { 56 | this.name = name; 57 | } 58 | 59 | /* The where class in this code below will be marked red until you first compile the project, since the code 60 | * for the SampleModel_Table class is generated at compile-time. 61 | */ 62 | 63 | // Record Finders 64 | public static SampleModel byId(long id) { 65 | return new Select().from(SampleModel.class).where(SampleModel_Table.id.eq(id)).querySingle(); 66 | } 67 | 68 | public static List recentItems() { 69 | return new Select().from(SampleModel.class).orderBy(SampleModel_Table.id, false).limit(300).queryList(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app/src/main/java/com/codepath/apps/restclienttemplate/models/Tweet.java: -------------------------------------------------------------------------------- 1 | package com.codepath.apps.restclienttemplate.models; 2 | 3 | import android.text.format.DateUtils; 4 | 5 | import org.json.JSONException; 6 | import org.json.JSONObject; 7 | import org.parceler.Parcel; 8 | 9 | import java.text.ParseException; 10 | import java.text.SimpleDateFormat; 11 | import java.util.Locale; 12 | 13 | /** 14 | * Created by robertvunabandi on 6/26/17. 15 | */ 16 | 17 | @Parcel 18 | public class Tweet { 19 | public String body, createdAt; 20 | public long uid; // DB id for the tweet 21 | public User user; 22 | public Media media; 23 | public int retweetCount, likeCount; 24 | public boolean tweetLiked, tweetRetweeted, mediaFound; 25 | 26 | // deserialize the JSON 27 | public Tweet() {} 28 | 29 | public static Tweet fromJSON(JSONObject jsonObject) throws JSONException{ 30 | Tweet tweet = new Tweet(); 31 | 32 | // extract the values from JSON 33 | tweet.body = jsonObject.getString("text"); 34 | tweet.uid = jsonObject.getLong("id"); 35 | tweet.createdAt = getRelativeTimeAgo(jsonObject.getString("created_at")); 36 | // user and media 37 | tweet.user = User.fromJSON(jsonObject.getJSONObject("user")); 38 | try { 39 | tweet.media = Media.fromJSON(jsonObject.getJSONObject("entities").getJSONArray("media")); 40 | tweet.mediaFound = true; 41 | } catch (JSONException e) { 42 | tweet.media = null; 43 | tweet.mediaFound = false; 44 | } 45 | 46 | tweet.retweetCount = jsonObject.getInt("retweet_count"); 47 | tweet.likeCount = jsonObject.getInt("favorite_count"); 48 | tweet.tweetRetweeted = jsonObject.getBoolean("retweeted"); 49 | tweet.tweetLiked = jsonObject.getBoolean("favorited"); 50 | 51 | return tweet; 52 | } 53 | 54 | public static String getRelativeTimeAgo(String rawJsonDate) { 55 | String twitterFormat = "EEE MMM dd HH:mm:ss ZZZZZ yyyy"; 56 | SimpleDateFormat sf = new SimpleDateFormat(twitterFormat, Locale.ENGLISH); 57 | sf.setLenient(true); 58 | 59 | String relativeDate = ""; 60 | try { 61 | long dateMillis = sf.parse(rawJsonDate).getTime(); 62 | relativeDate = DateUtils.getRelativeTimeSpanString(dateMillis, 63 | System.currentTimeMillis(), DateUtils.SECOND_IN_MILLIS).toString(); 64 | } catch (ParseException e) { 65 | e.printStackTrace(); 66 | } 67 | 68 | return relativeDate; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/src/main/java/com/codepath/apps/restclienttemplate/models/TweetText.java: -------------------------------------------------------------------------------- 1 | package com.codepath.apps.restclienttemplate.models; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | /** 7 | * Created by robertvunabandi on 6/29/17. 8 | */ 9 | 10 | public class TweetText { 11 | public String HTMLFONTOPEN = ""; // color highlight 12 | public String HTMLFONTOPENHASHTAG = ""; // color highlight 13 | public String HTMLFONTCLOSE = ""; 14 | public String finalText; 15 | 16 | public TweetText(String text) { 17 | setTweet(text); 18 | } 19 | 20 | public void setTweet(String tweet) { 21 | String[] words = tweet.split("\\s+"); 22 | int l = words.length; 23 | for (int i = 0; i < l; i++) { 24 | String replacement = userIntoHTMLBlue(words[i]); 25 | words[i] = replacement; 26 | } 27 | List list = Arrays.asList(words); 28 | finalText = join(list, " "); 29 | } 30 | 31 | public String userIntoHTMLBlue(String userText) { 32 | if (userText.charAt(0) == '@') { 33 | return HTMLFONTOPEN + userText + HTMLFONTCLOSE; 34 | } else if (userText.charAt(0) == '#') { 35 | return HTMLFONTOPENHASHTAG + userText + HTMLFONTCLOSE; 36 | } else { 37 | return userText; 38 | } 39 | } 40 | 41 | public static String join(List list, String delimeter) { 42 | StringBuilder sb = new StringBuilder(); 43 | boolean first = true; 44 | for (String item : list) { 45 | if (first) { 46 | first = false; 47 | } else { 48 | sb.append(delimeter); 49 | } 50 | sb.append(item); 51 | } 52 | return sb.toString(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/com/codepath/apps/restclienttemplate/models/User.java: -------------------------------------------------------------------------------- 1 | package com.codepath.apps.restclienttemplate.models; 2 | 3 | import org.json.JSONException; 4 | import org.json.JSONObject; 5 | import org.parceler.Parcel; 6 | 7 | import static com.codepath.apps.restclienttemplate.models.Tweet.getRelativeTimeAgo; 8 | 9 | /** 10 | * Created by robertvunabandi on 6/26/17. 11 | */ 12 | 13 | @Parcel 14 | public class User { 15 | // list attributes 16 | public String name, screenName, profileImageUrl; 17 | public long uid; 18 | // variables for user details 19 | public String profileImageUrlHTTPS, profileBackgroundImageUrl, profileBackgroundImageUrlHTTPS, profileBannerUrl, profileBackgroundColor, createdAt, description; 20 | public long followersCount, followingsCount; 21 | public boolean verifiedUser, followsYou, following; 22 | 23 | public User() {} 24 | 25 | // deserialize the JSON 26 | public static User fromJSON(JSONObject jsonObject) throws JSONException { 27 | User user = new User(); 28 | 29 | // extract the values 30 | user.name = jsonObject.getString("name"); 31 | user.screenName = jsonObject.getString("screen_name"); 32 | user.profileImageUrl = jsonObject.getString("profile_image_url").replace("_normal",""); 33 | user.profileImageUrlHTTPS = jsonObject.getString("profile_image_url_https").replace("_normal",""); 34 | user.uid = jsonObject.getLong("id"); 35 | // for user detail 36 | 37 | user.description = jsonObject.getString("description"); 38 | user.profileBackgroundImageUrl = jsonObject.getString("profile_background_image_url").replace("_normal",""); 39 | user.profileBackgroundColor = jsonObject.getString("profile_background_color"); 40 | user.profileBannerUrl = jsonObject.getString("profile_banner_url"); 41 | user.createdAt = getRelativeTimeAgo(jsonObject.getString("created_at")); // when the user was first on twitter 42 | user.followersCount = jsonObject.getLong("followers_count"); 43 | user.followingsCount = jsonObject.getLong("friends_count"); // it could be listed_count as well 44 | user.verifiedUser = jsonObject.getBoolean("verified"); 45 | user.followsYou = jsonObject.getBoolean("following"); 46 | user.following = false; // boolean to modify in case it's true 47 | 48 | return user; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/res/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/.DS_Store -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-hdpi/.DS_Store -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_face_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-hdpi/ic_face_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_face_placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-hdpi/ic_face_placeholder.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_heart_clear_light_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-hdpi/ic_heart_clear_light_blue.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_heart_clear_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-hdpi/ic_heart_clear_red.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_heart_solid_light_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-hdpi/ic_heart_solid_light_blue.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_mode_edit_black_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-hdpi/ic_mode_edit_black_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_person_v1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-hdpi/ic_person_v1.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_person_v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-hdpi/ic_person_v2.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_person_v3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-hdpi/ic_person_v3.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_picture_placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-hdpi/ic_picture_placeholder.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_reply.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-hdpi/ic_reply.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_reply_accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-hdpi/ic_reply_accent.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_reply_black_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-hdpi/ic_reply_black_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_retweet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-hdpi/ic_retweet.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_retweet_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-hdpi/ic_retweet_green.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_tweet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-hdpi/ic_tweet.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_tweet_accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-hdpi/ic_tweet_accent.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_tweet_quil.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-hdpi/ic_tweet_quil.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_tweet_quil_accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-hdpi/ic_tweet_quil_accent.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_face_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-mdpi/ic_face_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_face_placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-mdpi/ic_face_placeholder.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_heart_clear_light_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-mdpi/ic_heart_clear_light_blue.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_heart_clear_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-mdpi/ic_heart_clear_red.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_heart_solid_light_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-mdpi/ic_heart_solid_light_blue.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_mode_edit_black_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-mdpi/ic_mode_edit_black_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_person_v1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-mdpi/ic_person_v1.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_person_v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-mdpi/ic_person_v2.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_person_v3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-mdpi/ic_person_v3.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_picture_placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-mdpi/ic_picture_placeholder.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_reply.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-mdpi/ic_reply.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_reply_accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-mdpi/ic_reply_accent.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_reply_black_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-mdpi/ic_reply_black_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_retweet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-mdpi/ic_retweet.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_retweet_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-mdpi/ic_retweet_green.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_tweet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-mdpi/ic_tweet.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_tweet_accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-mdpi/ic_tweet_accent.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_tweet_quil.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-mdpi/ic_tweet_quil.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_tweet_quil_accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-mdpi/ic_tweet_quil_accent.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_face_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xhdpi/ic_face_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_face_placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xhdpi/ic_face_placeholder.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_heart_clear_light_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xhdpi/ic_heart_clear_light_blue.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_heart_clear_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xhdpi/ic_heart_clear_red.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_heart_solid_light_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xhdpi/ic_heart_solid_light_blue.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_mode_edit_black_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xhdpi/ic_mode_edit_black_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_person_v1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xhdpi/ic_person_v1.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_person_v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xhdpi/ic_person_v2.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_person_v3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xhdpi/ic_person_v3.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_picture_placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xhdpi/ic_picture_placeholder.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_reply.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xhdpi/ic_reply.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_reply_accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xhdpi/ic_reply_accent.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_reply_black_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xhdpi/ic_reply_black_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_retweet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xhdpi/ic_retweet.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_retweet_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xhdpi/ic_retweet_green.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_tweet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xhdpi/ic_tweet.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_tweet_accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xhdpi/ic_tweet_accent.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_tweet_quil.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xhdpi/ic_tweet_quil.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_tweet_quil_accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xhdpi/ic_tweet_quil_accent.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_face_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxhdpi/ic_face_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_face_placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxhdpi/ic_face_placeholder.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_heart_clear_light_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxhdpi/ic_heart_clear_light_blue.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_heart_clear_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxhdpi/ic_heart_clear_red.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_heart_solid_light_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxhdpi/ic_heart_solid_light_blue.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_mode_edit_black_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxhdpi/ic_mode_edit_black_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_person_v1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxhdpi/ic_person_v1.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_person_v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxhdpi/ic_person_v2.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_person_v3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxhdpi/ic_person_v3.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_picture_placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxhdpi/ic_picture_placeholder.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_reply.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxhdpi/ic_reply.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_reply_accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxhdpi/ic_reply_accent.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_reply_black_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxhdpi/ic_reply_black_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_retweet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxhdpi/ic_retweet.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_retweet_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxhdpi/ic_retweet_green.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_tweet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxhdpi/ic_tweet.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_tweet_accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxhdpi/ic_tweet_accent.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_tweet_quil.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxhdpi/ic_tweet_quil.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_tweet_quil_accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxhdpi/ic_tweet_quil_accent.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_face_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxxhdpi/ic_face_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_face_placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxxhdpi/ic_face_placeholder.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_heart_clear_light_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxxhdpi/ic_heart_clear_light_blue.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_heart_clear_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxxhdpi/ic_heart_clear_red.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_heart_solid_light_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxxhdpi/ic_heart_solid_light_blue.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_mode_edit_black_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxxhdpi/ic_mode_edit_black_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_person_v1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxxhdpi/ic_person_v1.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_person_v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxxhdpi/ic_person_v2.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_person_v3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxxhdpi/ic_person_v3.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_picture_placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxxhdpi/ic_picture_placeholder.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_reply.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxxhdpi/ic_reply.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_reply_accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxxhdpi/ic_reply_accent.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_reply_black_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxxhdpi/ic_reply_black_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_retweet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxxhdpi/ic_retweet.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_retweet_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxxhdpi/ic_retweet_green.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_tweet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxxhdpi/ic_tweet.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_tweet_accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxxhdpi/ic_tweet_accent.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_tweet_quil.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxxhdpi/ic_tweet_quil.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_tweet_quil_accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable-xxxhdpi/ic_tweet_quil_accent.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertvunabandi/twitter/e19052ff5d6e86aaa273bcd98e76fa7d291d021a/app/src/main/res/drawable/.DS_Store -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_follow.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_unfollow.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_back_button_accent.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_back_button_light_blue.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_close_accent.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_heart_clear_light_blue_svg.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 10 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_heart_clear_red_svg.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 10 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_heart_solid_light_blue_svg.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 10 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_heart_solid_red_svg.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 10 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_person_v1_svg.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 15 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_person_v2_svg.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 15 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_person_v3_svg.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 15 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_picture_placeholder_svg.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 17 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_reply_accent_svg.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_reply_svg.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_retweet_green_svg.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 10 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_retweet_svg.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 10 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_tweet_accent_svg.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_tweet_quil_accent_svg.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_tweet_quil_svg.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_tweet_svg.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/rounded_corners.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_compose.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 18 | 21 | 22 | 32 | 33 | 44 | 45 | 56 | 57 | 58 | 59 | 66 | 67 | 84 | 85 |