├── .gitignore ├── KyleWBanks ├── build.gradle ├── libs │ ├── android-support-v4.jar │ ├── gson-2.2.2.jar │ └── httpmime-4.2.3.jar └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ ├── fonts │ │ └── script.ttf │ └── post_content.html │ ├── ic_launcher-web.png │ ├── java │ └── com │ │ └── kylewbanks │ │ ├── KWBApplication.java │ │ ├── activity │ │ ├── MainActivity.java │ │ └── PostActivity.java │ │ ├── database │ │ ├── DatabaseWrapper.java │ │ └── orm │ │ │ ├── PostORM.java │ │ │ └── TagORM.java │ │ ├── event │ │ └── PostListUpdateListener.java │ │ ├── model │ │ ├── Post.java │ │ └── Tag.java │ │ └── network │ │ ├── RESTController.java │ │ ├── RESTResponse.java │ │ └── response │ │ └── PostListResponse.java │ └── res │ ├── anim │ ├── down_from_top.xml │ ├── left_to_right.xml │ ├── right_to_left.xml │ ├── start_left_to_right.xml │ ├── start_right_to_left.xml │ └── up_from_bottom.xml │ ├── drawable-hdpi │ └── ic_launcher.png │ ├── drawable-mdpi │ └── ic_launcher.png │ ├── drawable-xhdpi │ └── ic_launcher.png │ ├── drawable-xxhdpi │ └── ic_launcher.png │ ├── drawable │ └── post_list_item_background.xml │ ├── layout │ ├── activity_main.xml │ ├── header.xml │ ├── post_list_item.xml │ └── post_view.xml │ ├── menu │ └── main.xml │ ├── values-sw600dp │ └── dimens.xml │ ├── values-sw720dp-land │ └── dimens.xml │ ├── values-v11 │ └── styles.xml │ └── values │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── LICENSE ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # generated files 12 | bin/ 13 | gen/ 14 | 15 | # Local configuration file (sdk path, etc) 16 | local.properties 17 | 18 | # Eclipse project files 19 | .classpath 20 | .project 21 | 22 | # Proguard folder generated by Eclipse 23 | proguard/ 24 | 25 | # Intellij project files 26 | *.iml 27 | *.ipr 28 | *.iws 29 | .idea/ 30 | 31 | # Android Studio 32 | .idea/ 33 | .gradle 34 | /*/local.properties 35 | /*/out 36 | /*/*/build 37 | /*/*/production 38 | *.iml 39 | *.iws 40 | *.ipr 41 | *~ 42 | *.swp 43 | 44 | KyleWBanks/build/ 45 | 46 | AnimatedListView/ 47 | -------------------------------------------------------------------------------- /KyleWBanks/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | maven { url 'http://repo1.maven.org/maven2' } 4 | } 5 | dependencies { 6 | classpath 'com.android.tools.build:gradle:0.5.+' 7 | } 8 | } 9 | apply plugin: 'android' 10 | 11 | dependencies { 12 | compile files('libs/android-support-v4.jar') 13 | compile files('libs/gson-2.2.2.jar') 14 | compile files('libs/httpmime-4.2.3.jar') 15 | compile project(':AnimatedListView') 16 | } 17 | 18 | android { 19 | compileSdkVersion 17 20 | buildToolsVersion "17.0.0" 21 | 22 | defaultConfig { 23 | minSdkVersion 14 24 | targetSdkVersion 16 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /KyleWBanks/libs/android-support-v4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KyleBanks/kylewbanks.com-AndroidApp/6feba775579336c96bf751942a48d8f23db6cf37/KyleWBanks/libs/android-support-v4.jar -------------------------------------------------------------------------------- /KyleWBanks/libs/gson-2.2.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KyleBanks/kylewbanks.com-AndroidApp/6feba775579336c96bf751942a48d8f23db6cf37/KyleWBanks/libs/gson-2.2.2.jar -------------------------------------------------------------------------------- /KyleWBanks/libs/httpmime-4.2.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KyleBanks/kylewbanks.com-AndroidApp/6feba775579336c96bf751942a48d8f23db6cf37/KyleWBanks/libs/httpmime-4.2.3.jar -------------------------------------------------------------------------------- /KyleWBanks/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /KyleWBanks/src/main/assets/fonts/script.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KyleBanks/kylewbanks.com-AndroidApp/6feba775579336c96bf751942a48d8f23db6cf37/KyleWBanks/src/main/assets/fonts/script.ttf -------------------------------------------------------------------------------- /KyleWBanks/src/main/assets/post_content.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 10 | 11 | 12 | {{CONTENT}} 13 | 18 | 19 | -------------------------------------------------------------------------------- /KyleWBanks/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KyleBanks/kylewbanks.com-AndroidApp/6feba775579336c96bf751942a48d8f23db6cf37/KyleWBanks/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /KyleWBanks/src/main/java/com/kylewbanks/KWBApplication.java: -------------------------------------------------------------------------------- 1 | package com.kylewbanks; 2 | 3 | import android.app.Application; 4 | import android.util.Log; 5 | import com.kylewbanks.event.PostListUpdateListener; 6 | import com.kylewbanks.database.orm.PostORM; 7 | import com.kylewbanks.model.Post; 8 | import com.kylewbanks.network.RESTController; 9 | import com.kylewbanks.network.response.PostListResponse; 10 | 11 | import java.util.Collections; 12 | import java.util.Date; 13 | import java.util.List; 14 | 15 | /** 16 | * Created by kylewbanks on 2013-10-09. 17 | */ 18 | public class KWBApplication extends Application { 19 | 20 | private static final String TAG = "KWBApplication"; 21 | 22 | private PostListUpdateListener _postListUpdateListener; 23 | 24 | private Date _lastLoadedPostList; 25 | private static final int LOAD_POST_LIST_INTERVAL = 1000 * 60 * 10; //10 minutes 26 | 27 | /** 28 | * Register a listener for when the Post list becomes available, or is updated 29 | * @param postListUpdateListener 30 | */ 31 | public void registerPostUpdateListener(PostListUpdateListener postListUpdateListener) { 32 | this._postListUpdateListener = postListUpdateListener; 33 | } 34 | 35 | /** 36 | * Refreshes the Posts from the database, and polls the remote server if enough time has passed 37 | */ 38 | public void checkForUpdates() { 39 | if(_lastLoadedPostList == null || new Date().getTime() - _lastLoadedPostList.getTime() > LOAD_POST_LIST_INTERVAL) { 40 | this.loadPostList(); 41 | } else { 42 | List posts = PostORM.getPosts(this); 43 | 44 | if(posts != null) { 45 | Collections.sort(posts); 46 | this._postListUpdateListener.onPostListLoaded(posts); 47 | } 48 | } 49 | } 50 | 51 | /** 52 | * Returns a Post object identified by the specified id, if available 53 | * @param postId 54 | * @return 55 | */ 56 | public Post getPostById(long postId) { 57 | return PostORM.findPostById(this, postId); 58 | } 59 | 60 | /** 61 | * Fetches the list of Posts from the remove server 62 | */ 63 | private void loadPostList() { 64 | Log.i(TAG, "Loading Post List...."); 65 | 66 | RESTController.retrievePostList(postListResponse, new long[0]); 67 | } 68 | 69 | /** 70 | * Handler for the Post list being fetched remotely 71 | */ 72 | private PostListResponse postListResponse = new PostListResponse() { 73 | @Override 74 | public void success(String json) { 75 | super.success(json); 76 | Collections.sort(postList); 77 | 78 | if(_postListUpdateListener != null) { 79 | _postListUpdateListener.onPostListLoaded(postList); 80 | } 81 | 82 | for (Post post : postList) { 83 | PostORM.insertPost(KWBApplication.this, post); 84 | } 85 | 86 | _lastLoadedPostList = new Date(); 87 | } 88 | }; 89 | } 90 | -------------------------------------------------------------------------------- /KyleWBanks/src/main/java/com/kylewbanks/activity/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.kylewbanks.activity; 2 | 3 | import android.app.ActionBar; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.graphics.Typeface; 7 | import android.os.Bundle; 8 | import android.app.Activity; 9 | import android.text.Html; 10 | import android.util.Log; 11 | import android.view.LayoutInflater; 12 | import android.view.View; 13 | import android.widget.*; 14 | import com.kylewbanks.KWBApplication; 15 | import com.kylewbanks.R; 16 | import com.kylewbanks.animlv.AnimatedListView; 17 | import com.kylewbanks.animlv.AnimatedListViewAdapter; 18 | import com.kylewbanks.animlv.AnimatedListViewObjectMapper; 19 | import com.kylewbanks.event.PostListUpdateListener; 20 | import com.kylewbanks.model.Post; 21 | 22 | import java.util.List; 23 | 24 | public class MainActivity extends Activity implements PostListUpdateListener { 25 | 26 | private static final String TAG = "MainActivity"; 27 | 28 | private List postList; 29 | 30 | private AnimatedListView postListView; 31 | private ProgressBar progressBar; 32 | 33 | @Override 34 | protected void onCreate(Bundle savedInstanceState) { 35 | super.onCreate(savedInstanceState); 36 | setContentView(R.layout.activity_main); 37 | 38 | //Customize the action bar 39 | ActionBar actionBar = getActionBar(); 40 | if(actionBar != null) { 41 | actionBar.setDisplayShowHomeEnabled(false); 42 | actionBar.setDisplayShowTitleEnabled(false); 43 | actionBar.setDisplayShowCustomEnabled(true); 44 | 45 | LayoutInflater inflater = (LayoutInflater)this.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 46 | View v = inflater.inflate(R.layout.header, null); 47 | 48 | Typeface scriptFont = Typeface.createFromAsset(getAssets(), "fonts/script.ttf"); 49 | TextView title = (TextView) v.findViewById(R.id.title); 50 | title.setTypeface(scriptFont); 51 | 52 | actionBar.setCustomView(v); 53 | } 54 | 55 | //Initialize Views 56 | postListView = (AnimatedListView) findViewById(R.id.post_list); 57 | postListView.setOnItemClickListener(postItemSelectedListener); 58 | 59 | 60 | progressBar = (ProgressBar) findViewById(R.id.loader); 61 | 62 | //Register as a Post List update listener 63 | KWBApplication application = (KWBApplication) getApplication(); 64 | application.registerPostUpdateListener(this); 65 | } 66 | 67 | @Override 68 | protected void onStart() { 69 | super.onStart(); 70 | 71 | //Check for any updated Posts 72 | KWBApplication application = (KWBApplication) getApplication(); 73 | application.checkForUpdates(); 74 | } 75 | 76 | /** 77 | * Inherited from PostListUpdateListener to be notified when the Post list changes 78 | * @param posts 79 | */ 80 | @Override 81 | public void onPostListLoaded(List posts) { 82 | this.postList = posts; 83 | 84 | runOnUiThread(reloadPostList); 85 | } 86 | 87 | /** 88 | * Triggered when the post list is updated in order to update the UI 89 | */ 90 | private Runnable reloadPostList = new Runnable() { 91 | @Override 92 | public void run() { 93 | AnimatedListViewAdapter postListAdapter = new AnimatedListViewAdapter(MainActivity.this, R.layout.post_list_item, postList, objectMapper); 94 | postListView.setAdapter(postListAdapter); 95 | 96 | progressBar.setVisibility(View.GONE); 97 | } 98 | }; 99 | 100 | /** 101 | * Called to bind a Post object to a View for the AnimatedListView 102 | */ 103 | private AnimatedListViewObjectMapper objectMapper = new AnimatedListViewObjectMapper() { 104 | @Override 105 | public void bindObjectToView(Object object, View view) { 106 | Post post = (Post) object; 107 | 108 | TextView txtTitle = (TextView) view.findViewById(R.id.post_item_title); 109 | txtTitle.setText(post.getTitle()); 110 | 111 | TextView txtTagList = (TextView) view.findViewById(R.id.post_item_tags); 112 | txtTagList.setText(post.getTagString()); 113 | 114 | TextView txtPreview = (TextView) view.findViewById(R.id.post_item_preview); 115 | txtPreview.setText(Html.fromHtml(post.getPreview())); 116 | } 117 | }; 118 | 119 | /** 120 | * Triggered when a Post is selected from the ListView 121 | */ 122 | private ListView.OnItemClickListener postItemSelectedListener = new AdapterView.OnItemClickListener() { 123 | @Override 124 | public void onItemClick(AdapterView adapterView, View view, int position, long l) { 125 | Post selectedPost = postList.get(position); 126 | 127 | Intent toPostActivity = new Intent(MainActivity.this, PostActivity.class); 128 | toPostActivity.putExtra("postId", selectedPost.getId()); 129 | startActivity(toPostActivity); 130 | overridePendingTransition(R.anim.start_right_to_left, R.anim.start_left_to_right); 131 | } 132 | }; 133 | 134 | } 135 | -------------------------------------------------------------------------------- /KyleWBanks/src/main/java/com/kylewbanks/activity/PostActivity.java: -------------------------------------------------------------------------------- 1 | package com.kylewbanks.activity; 2 | 3 | import android.app.ActionBar; 4 | import android.app.Activity; 5 | import android.content.Intent; 6 | import android.content.res.AssetManager; 7 | import android.os.Bundle; 8 | import android.util.Log; 9 | import android.view.MenuItem; 10 | import android.webkit.ConsoleMessage; 11 | import android.webkit.WebChromeClient; 12 | import android.webkit.WebSettings; 13 | import android.webkit.WebView; 14 | import com.kylewbanks.KWBApplication; 15 | import com.kylewbanks.R; 16 | import com.kylewbanks.model.Post; 17 | 18 | import java.io.IOException; 19 | import java.io.InputStream; 20 | import java.net.URLEncoder; 21 | import java.util.Scanner; 22 | 23 | /** 24 | * Created by kylewbanks on 2013-10-09. 25 | */ 26 | public class PostActivity extends Activity { 27 | 28 | private static final String TAG = "PostActivity"; 29 | 30 | private Post post; 31 | private WebView contentView; 32 | 33 | @Override 34 | protected void onCreate(Bundle savedInstanceState) { 35 | super.onCreate(savedInstanceState); 36 | setContentView(R.layout.post_view); 37 | 38 | //Determine which post we are showing 39 | KWBApplication application = (KWBApplication) getApplication(); 40 | Intent intent = getIntent(); 41 | post = application.getPostById(intent.getLongExtra("postId", -1)); 42 | 43 | //Customize the action bar 44 | ActionBar actionBar = getActionBar(); 45 | if(actionBar != null) { 46 | actionBar.setDisplayHomeAsUpEnabled(true); 47 | actionBar.setDisplayShowHomeEnabled(false); 48 | actionBar.setDisplayShowTitleEnabled(true); 49 | actionBar.setTitle(post.getTitle()); 50 | actionBar.setDisplayUseLogoEnabled(false); 51 | } 52 | 53 | //Get references to needed UI components 54 | contentView = (WebView) findViewById(R.id.post_content); 55 | } 56 | 57 | @Override 58 | protected void onStart() { 59 | super.onStart(); 60 | 61 | //Enable JavaScript and set the content of the WebView 62 | WebSettings settings = contentView.getSettings(); 63 | settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL); 64 | settings.setJavaScriptEnabled(true); 65 | contentView.loadDataWithBaseURL("", getTemplateContent().replace("{{CONTENT}}", post.getBody()), "text/html", "UTF-8", ""); 66 | } 67 | 68 | /** 69 | * Called when the physical 'Back Button' is pressed. Overridden to implement custom animation. 70 | */ 71 | @Override 72 | public void onBackPressed() { 73 | super.onBackPressed(); 74 | overridePendingTransition(R.anim.left_to_right, R.anim.right_to_left); 75 | } 76 | 77 | /** 78 | * Called when the navigation 'Back Button' is pressed (header bar). Overridden to implement custom animation. 79 | * @param item 80 | * @return 81 | */ 82 | @Override 83 | public boolean onOptionsItemSelected(MenuItem item) { 84 | switch (item.getItemId()) { 85 | case android.R.id.home: 86 | finish(); 87 | overridePendingTransition(R.anim.left_to_right, R.anim.right_to_left); 88 | return true; 89 | default: 90 | return super.onOptionsItemSelected(item); 91 | } 92 | } 93 | 94 | /** 95 | * Reads in the contents of post_content.html and returns it as a String 96 | * @return 97 | */ 98 | private String getTemplateContent() { 99 | AssetManager assetManager = getAssets(); 100 | try { 101 | InputStream contentStream = assetManager.open("post_content.html"); 102 | 103 | Scanner scanner = new Scanner(contentStream).useDelimiter("\\A"); 104 | if(scanner.hasNext()) { 105 | return scanner.next(); 106 | } 107 | } catch (IOException ex) { 108 | Log.i(TAG, "Failed to get Template content due to: " + ex); 109 | } 110 | 111 | return null; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /KyleWBanks/src/main/java/com/kylewbanks/database/DatabaseWrapper.java: -------------------------------------------------------------------------------- 1 | package com.kylewbanks.database; 2 | 3 | import android.content.Context; 4 | import android.database.sqlite.SQLiteDatabase; 5 | import android.database.sqlite.SQLiteOpenHelper; 6 | import android.util.Log; 7 | import com.kylewbanks.database.orm.PostORM; 8 | import com.kylewbanks.database.orm.TagORM; 9 | 10 | /** 11 | * Created by kylewbanks on 2013-10-10. 12 | */ 13 | public class DatabaseWrapper extends SQLiteOpenHelper { 14 | 15 | private static final String TAG = "DatabaseWrapper"; 16 | 17 | private static final int DATABASE_VERSION = 5; 18 | private static final String DATABASE_NAME = "KWB.db"; 19 | 20 | public DatabaseWrapper(Context context) { 21 | super(context, DATABASE_NAME, null, DATABASE_VERSION); 22 | } 23 | 24 | /** 25 | * Called if the database named DATABASE_NAME doesn't exist in order to create it. 26 | * 27 | * @param sqLiteDatabase 28 | */ 29 | @Override 30 | public void onCreate(SQLiteDatabase sqLiteDatabase) { 31 | Log.i(TAG, "Creating database [" + DATABASE_NAME + " v." + DATABASE_VERSION + "]..."); 32 | 33 | sqLiteDatabase.execSQL(PostORM.SQL_CREATE_TABLE); 34 | sqLiteDatabase.execSQL(TagORM.SQL_CREATE_TABLE); 35 | } 36 | 37 | /** 38 | * Called when the DATABASE_VERSION is increased. 39 | * 40 | * @param sqLiteDatabase 41 | * @param oldVersion 42 | * @param newVersion 43 | */ 44 | @Override 45 | public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) { 46 | Log.i(TAG, "Upgrading database ["+DATABASE_NAME+" v." + oldVersion+"] to ["+DATABASE_NAME+" v." + newVersion+"]..."); 47 | 48 | sqLiteDatabase.execSQL(PostORM.SQL_DROP_TABLE); 49 | sqLiteDatabase.execSQL(TagORM.SQL_DROP_TABLE); 50 | onCreate(sqLiteDatabase); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /KyleWBanks/src/main/java/com/kylewbanks/database/orm/PostORM.java: -------------------------------------------------------------------------------- 1 | package com.kylewbanks.database.orm; 2 | 3 | import android.content.ContentValues; 4 | import android.content.Context; 5 | import android.database.Cursor; 6 | import android.database.sqlite.SQLiteDatabase; 7 | import android.util.Log; 8 | import com.kylewbanks.database.DatabaseWrapper; 9 | import com.kylewbanks.model.Post; 10 | import com.kylewbanks.model.Tag; 11 | 12 | import java.text.ParseException; 13 | import java.text.SimpleDateFormat; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | import java.util.Locale; 17 | 18 | /** 19 | * Created by kylewbanks on 2013-10-10. 20 | */ 21 | public class PostORM { 22 | 23 | private static final String TAG = "PostORM"; 24 | 25 | private static final String TABLE_NAME = "post"; 26 | 27 | private static final String COMMA_SEP = ", "; 28 | 29 | private static final String COLUMN_ID_TYPE = "INTEGER PRIMARY KEY"; 30 | private static final String COLUMN_ID = "id"; 31 | 32 | private static final String COLUMN_TITLE_TYPE = "TEXT"; 33 | private static final String COLUMN_TITLE = "title"; 34 | 35 | private static final String COLUMN_PREVIEW_TYPE = "TEXT"; 36 | private static final String COLUMN_PREVIEW = "preview"; 37 | 38 | private static final String COLUMN_BODY_TYPE = "TEXT"; 39 | private static final String COLUMN_BODY = "body"; 40 | 41 | private static final String COLUMN_URL_TYPE = "TEXT"; 42 | private static final String COLUMN_URL = "url"; 43 | 44 | private static final String COLUMN_DATE_TYPE = "TEXT"; 45 | private static final String COLUMN_DATE = "pubdate"; 46 | 47 | 48 | public static final String SQL_CREATE_TABLE = 49 | "CREATE TABLE " + TABLE_NAME + " (" + 50 | COLUMN_ID + " " + COLUMN_ID_TYPE + COMMA_SEP + 51 | COLUMN_TITLE + " " + COLUMN_TITLE_TYPE + COMMA_SEP + 52 | COLUMN_PREVIEW + " " + COLUMN_PREVIEW_TYPE + COMMA_SEP + 53 | COLUMN_BODY + " " + COLUMN_BODY_TYPE + COMMA_SEP + 54 | COLUMN_URL + " " + COLUMN_URL_TYPE + COMMA_SEP + 55 | COLUMN_DATE + " " + COLUMN_DATE_TYPE + 56 | ")"; 57 | 58 | public static final String SQL_DROP_TABLE = 59 | "DROP TABLE IF EXISTS " + TABLE_NAME; 60 | 61 | private static final SimpleDateFormat _dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ", Locale.ENGLISH); 62 | 63 | /** 64 | * Fetches the full list of Posts stored in the local Database 65 | * @param context 66 | * @return 67 | */ 68 | public static List getPosts(Context context) { 69 | DatabaseWrapper databaseWrapper = new DatabaseWrapper(context); 70 | SQLiteDatabase database = databaseWrapper.getReadableDatabase(); 71 | 72 | List postList = null; 73 | 74 | if(database != null) { 75 | Cursor cursor = database.rawQuery("SELECT * FROM " + PostORM.TABLE_NAME, null); 76 | 77 | Log.i(TAG, "Loaded " + cursor.getCount() + " Posts..."); 78 | if(cursor.getCount() > 0) { 79 | postList = new ArrayList(); 80 | cursor.moveToFirst(); 81 | while (!cursor.isAfterLast()) { 82 | Post post = cursorToPost(cursor); 83 | post.setTags(TagORM.getTagsForPost(context, post)); 84 | 85 | postList.add(post); 86 | cursor.moveToNext(); 87 | } 88 | Log.i(TAG, "Posts loaded successfully."); 89 | } 90 | 91 | database.close(); 92 | } 93 | 94 | return postList; 95 | } 96 | 97 | /** 98 | * Fetches a single Post identified by the specified ID 99 | * @param context 100 | * @param postId 101 | * @return 102 | */ 103 | public static Post findPostById(Context context, long postId) { 104 | DatabaseWrapper databaseWrapper = new DatabaseWrapper(context); 105 | SQLiteDatabase database = databaseWrapper.getReadableDatabase(); 106 | 107 | Post post = null; 108 | if(database != null) { 109 | Log.i(TAG, "Loading Post["+postId+"]..."); 110 | Cursor cursor = database.rawQuery("SELECT * FROM " + PostORM.TABLE_NAME + " WHERE " + PostORM.COLUMN_ID + " = " + postId, null); 111 | 112 | if(cursor.getCount() > 0) { 113 | cursor.moveToFirst(); 114 | post = cursorToPost(cursor); 115 | post.setTags(TagORM.getTagsForPost(context, post)); 116 | Log.i(TAG, "Post loaded successfully!"); 117 | } 118 | 119 | database.close(); 120 | } 121 | 122 | return post; 123 | } 124 | 125 | /** 126 | * Inserts a Post object into the local database 127 | * @param context 128 | * @param post 129 | * @return 130 | */ 131 | public static boolean insertPost(Context context, Post post) { 132 | if(findPostById(context, post.getId()) != null) { 133 | Log.i(TAG, "Post already exists in database, not inserting!"); 134 | return updatePost(context, post); 135 | } 136 | 137 | ContentValues values = postToContentValues(post); 138 | 139 | DatabaseWrapper databaseWrapper = new DatabaseWrapper(context); 140 | SQLiteDatabase database = databaseWrapper.getWritableDatabase(); 141 | 142 | boolean success = false; 143 | try { 144 | if (database != null) { 145 | long postId = database.insert(PostORM.TABLE_NAME, "null", values); 146 | Log.i(TAG, "Inserted new Post with ID: " + postId); 147 | 148 | for (Tag tag : post.getTags()) { 149 | TagORM.insertTag(context, tag, post.getId()); 150 | } 151 | success = true; 152 | } 153 | } catch (NullPointerException ex) { 154 | Log.e(TAG, "Failed to insert Post[" + post.getId() + "] due to: " + ex); 155 | } finally { 156 | if(database != null) { 157 | database.close(); 158 | } 159 | } 160 | 161 | return success; 162 | } 163 | 164 | public static boolean updatePost(Context context, Post post) { 165 | ContentValues values = postToContentValues(post); 166 | DatabaseWrapper databaseWrapper = new DatabaseWrapper(context); 167 | SQLiteDatabase database = databaseWrapper.getWritableDatabase(); 168 | 169 | boolean success = false; 170 | try { 171 | if (database != null) { 172 | Log.i(TAG, "Updating Post[" + post.getId() + "]..."); 173 | database.update(PostORM.TABLE_NAME, values, PostORM.COLUMN_ID + " = " + post.getId(), null); 174 | success = true; 175 | } 176 | } catch (NullPointerException ex) { 177 | Log.e(TAG, "Failed to update Post[" + post.getId() + "] due to: " + ex); 178 | } finally { 179 | if(database != null) { 180 | database.close(); 181 | } 182 | } 183 | 184 | return success; 185 | } 186 | 187 | /** 188 | * Packs a Post object into a ContentValues map for use with SQL inserts. 189 | * @param post 190 | * @return 191 | */ 192 | private static ContentValues postToContentValues(Post post) { 193 | ContentValues values = new ContentValues(); 194 | values.put(PostORM.COLUMN_ID, post.getId()); 195 | values.put(PostORM.COLUMN_TITLE, post.getTitle()); 196 | values.put(PostORM.COLUMN_PREVIEW, post.getPreview()); 197 | values.put(PostORM.COLUMN_BODY, post.getBody()); 198 | values.put(PostORM.COLUMN_URL, post.getUrl()); 199 | values.put(PostORM.COLUMN_DATE, _dateFormat.format(post.getDate())); 200 | 201 | return values; 202 | } 203 | 204 | /** 205 | * Populates a Post object with data from a Cursor 206 | * @param cursor 207 | * @return 208 | */ 209 | private static Post cursorToPost(Cursor cursor) { 210 | Post post = new Post(); 211 | post.setId(cursor.getLong(cursor.getColumnIndex(COLUMN_ID))); 212 | post.setTitle(cursor.getString(cursor.getColumnIndex(COLUMN_TITLE))); 213 | post.setPreview(cursor.getString(cursor.getColumnIndex(COLUMN_PREVIEW))); 214 | post.setBody(cursor.getString(cursor.getColumnIndex(COLUMN_BODY))); 215 | post.setUrl(cursor.getString(cursor.getColumnIndex(COLUMN_URL))); 216 | 217 | String date = cursor.getString(cursor.getColumnIndex(COLUMN_DATE)); 218 | try { 219 | post.setDate(_dateFormat.parse(date)); 220 | } catch (ParseException ex) { 221 | Log.e(TAG, "Failed to parse date " + date + " for Post " + post.getId()); 222 | post.setDate(null); 223 | } 224 | 225 | return post; 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /KyleWBanks/src/main/java/com/kylewbanks/database/orm/TagORM.java: -------------------------------------------------------------------------------- 1 | package com.kylewbanks.database.orm; 2 | 3 | import android.content.ContentValues; 4 | import android.content.Context; 5 | import android.database.Cursor; 6 | import android.database.sqlite.SQLiteDatabase; 7 | import android.util.Log; 8 | import com.kylewbanks.database.DatabaseWrapper; 9 | import com.kylewbanks.model.Post; 10 | import com.kylewbanks.model.Tag; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | /** 16 | * Created by kylewbanks on 2013-10-10. 17 | */ 18 | public class TagORM { 19 | 20 | private static final String TAG = "TagORM"; 21 | 22 | private static final String TABLE_NAME = "tag"; 23 | private static final String COMMA_SEP = ", "; 24 | 25 | private static final String COLUMN_NAME_TYPE = "TEXT"; 26 | private static final String COLUMN_NAME = "name"; 27 | 28 | private static final String COLUMN_POST_ID_TYPE = "INTEGER"; 29 | private static final String COLUMN_POST_ID = "post_id"; 30 | 31 | 32 | public static final String SQL_CREATE_TABLE = 33 | "CREATE TABLE " + TABLE_NAME + " (" + 34 | COLUMN_NAME + " " + COLUMN_NAME_TYPE + COMMA_SEP + 35 | COLUMN_POST_ID + " " + COLUMN_POST_ID_TYPE + 36 | ")"; 37 | 38 | public static final String SQL_DROP_TABLE = 39 | "DROP TABLE IF EXISTS " + TABLE_NAME; 40 | 41 | 42 | /** 43 | * Inserts a Tag object into the database with an association with the specified Post 44 | * @param context 45 | * @param tag 46 | * @param postId 47 | * @return 48 | */ 49 | public static boolean insertTag(Context context, Tag tag, long postId) { 50 | ContentValues values = TagORM.tagToContentValues(tag, postId); 51 | DatabaseWrapper databaseWrapper = new DatabaseWrapper(context); 52 | SQLiteDatabase database = databaseWrapper.getWritableDatabase(); 53 | 54 | boolean success = false; 55 | try { 56 | if(database != null) { 57 | database.insert(TagORM.TABLE_NAME, "null", values); 58 | Log.i(TAG, "Inserted new Tag [" + tag.getName() + "] for Post [" + postId + "]"); 59 | success = true; 60 | } 61 | } catch (NullPointerException ex) { 62 | Log.e(TAG, "Failed to insert Tag[" + tag.getName() + "] due to: " + ex); 63 | } finally { 64 | if (database != null) { 65 | database.close(); 66 | } 67 | } 68 | 69 | return success; 70 | } 71 | 72 | /** 73 | * Gets a list of all cached Tags associated with the specified Post 74 | * @param context 75 | * @param post 76 | * @return 77 | */ 78 | public static final List getTagsForPost(Context context, Post post) { 79 | DatabaseWrapper databaseWrapper = new DatabaseWrapper(context); 80 | SQLiteDatabase database = databaseWrapper.getReadableDatabase(); 81 | 82 | List tagList = null; 83 | 84 | if (database != null) { 85 | Cursor cursor = database.rawQuery( 86 | "SELECT * FROM " + TagORM.TABLE_NAME + " WHERE " + TagORM.COLUMN_POST_ID + " = " + post.getId() + " GROUP BY " + TagORM.COLUMN_NAME, null 87 | ); 88 | 89 | Log.i(TAG, "Loaded " + cursor.getCount() + " Tags for Post["+post.getId()+"]..."); 90 | if(cursor.getCount() > 0) { 91 | tagList = new ArrayList(); 92 | 93 | cursor.moveToFirst(); 94 | while (!cursor.isAfterLast()) { 95 | tagList.add(TagORM.cursorToTag(cursor)); 96 | cursor.moveToNext(); 97 | } 98 | Log.i(TAG, "Tags loaded successfully for Post["+post.getId()+"]"); 99 | } 100 | 101 | database.close(); 102 | } 103 | return tagList; 104 | } 105 | 106 | /** 107 | * Packs a Tag object into a ContentValues map for use with SQL inserts. 108 | * @param tag 109 | * @param postId 110 | * @return 111 | */ 112 | private static ContentValues tagToContentValues(Tag tag, long postId) { 113 | ContentValues values = new ContentValues(); 114 | values.put(TagORM.COLUMN_NAME, tag.getName()); 115 | values.put(TagORM.COLUMN_POST_ID, postId); 116 | 117 | return values; 118 | } 119 | 120 | /** 121 | * Populates a Tag object with data from a Cursor 122 | * @param cursor 123 | * @return 124 | */ 125 | private static Tag cursorToTag(Cursor cursor) { 126 | Tag tag = new Tag(); 127 | tag.setName(cursor.getString(cursor.getColumnIndex(COLUMN_NAME))); 128 | 129 | return tag; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /KyleWBanks/src/main/java/com/kylewbanks/event/PostListUpdateListener.java: -------------------------------------------------------------------------------- 1 | package com.kylewbanks.event; 2 | 3 | import com.kylewbanks.model.Post; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Created by kylewbanks on 2013-10-09. 9 | */ 10 | public interface PostListUpdateListener { 11 | 12 | /** 13 | * Called when the Post list is made available, or has been updated 14 | * @param posts 15 | */ 16 | void onPostListLoaded(List posts); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /KyleWBanks/src/main/java/com/kylewbanks/model/Post.java: -------------------------------------------------------------------------------- 1 | package com.kylewbanks.model; 2 | 3 | import android.util.Log; 4 | import com.google.gson.annotations.SerializedName; 5 | 6 | import java.io.UnsupportedEncodingException; 7 | import java.net.URLEncoder; 8 | import java.util.Date; 9 | import java.util.List; 10 | 11 | /** 12 | * Created by kylewbanks on 2013-10-09. 13 | */ 14 | public class Post implements Comparable { 15 | 16 | private static final String TAG = "Post"; 17 | 18 | @SerializedName("id") 19 | private long _id; 20 | 21 | @SerializedName("title") 22 | private String _title; 23 | @SerializedName("preview") 24 | private String _preview; 25 | @SerializedName("body") 26 | private String _body; 27 | @SerializedName("url") 28 | private String _url; 29 | 30 | @SerializedName("date") 31 | private Date _date; 32 | 33 | @SerializedName("tags") 34 | private List _tags; 35 | 36 | public Post() { 37 | 38 | } 39 | 40 | public Post(long id, String title, String preview, String body, String url, Date date, List tags) { 41 | this._id = id; 42 | this._title = title; 43 | this._preview = preview; 44 | this._body = body; 45 | this._url = url; 46 | this._date = date; 47 | this._tags = tags; 48 | } 49 | 50 | /** 51 | * Returns the body of the Post in a way that is better handled by WebViews. 52 | * @return 53 | */ 54 | public String getURLEncodedBody() { 55 | try { 56 | return URLEncoder.encode(_body, "UTF-8").replaceAll("\\+", "%20"); 57 | } catch (UnsupportedEncodingException ex) { 58 | Log.i(TAG, "Failed to encode Post body due to: " + ex); 59 | return null; 60 | } 61 | } 62 | 63 | 64 | public long getId() { 65 | return _id; 66 | } 67 | 68 | public void setId(long id) { 69 | this._id = id; 70 | } 71 | 72 | public String getTitle() { 73 | return _title; 74 | } 75 | 76 | public void setTitle(String title) { 77 | this._title = title; 78 | } 79 | 80 | public String getPreview() { 81 | return _preview; 82 | } 83 | 84 | public void setPreview(String preview) { 85 | this._preview = preview; 86 | } 87 | 88 | public String getBody() { 89 | return _body; 90 | } 91 | 92 | public void setBody(String body) { 93 | this._body = body; 94 | } 95 | 96 | public String getUrl() { 97 | return _url; 98 | } 99 | 100 | public void setUrl(String url) { 101 | this._url = url; 102 | } 103 | 104 | public Date getDate() { 105 | return this._date; 106 | } 107 | 108 | public void setDate(Date date) { 109 | this._date = date; 110 | } 111 | 112 | public List getTags() { 113 | return _tags; 114 | } 115 | 116 | public void setTags(List tags) { 117 | this._tags = tags; 118 | } 119 | 120 | public String getTagString() { 121 | if (_tags == null || _tags.size() == 0) { 122 | return ""; 123 | } 124 | 125 | StringBuilder output = new StringBuilder(); 126 | output.append("Filed under "); 127 | 128 | for (int i = 0; i < _tags.size(); i++) { 129 | output.append(_tags.get(i).getName()); 130 | if(i < _tags.size()-1) { 131 | output.append(", "); 132 | } 133 | } 134 | output.append("."); 135 | 136 | return output.toString(); 137 | } 138 | 139 | @Override 140 | public int compareTo(Post post) { 141 | return post.getDate().compareTo(_date); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /KyleWBanks/src/main/java/com/kylewbanks/model/Tag.java: -------------------------------------------------------------------------------- 1 | package com.kylewbanks.model; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | /** 6 | * Created by kylewbanks on 2013-10-09. 7 | */ 8 | public class Tag { 9 | 10 | @SerializedName("name") 11 | private String _name; 12 | 13 | public Tag() { 14 | 15 | } 16 | 17 | public Tag(String name) { 18 | this._name = name; 19 | } 20 | 21 | public String getName() { 22 | return this._name; 23 | } 24 | public void setName(String name) { 25 | this._name = name; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /KyleWBanks/src/main/java/com/kylewbanks/network/RESTController.java: -------------------------------------------------------------------------------- 1 | package com.kylewbanks.network; 2 | 3 | import android.os.AsyncTask; 4 | import android.util.Log; 5 | import com.kylewbanks.network.response.PostListResponse; 6 | import org.apache.http.HttpEntity; 7 | import org.apache.http.HttpResponse; 8 | import org.apache.http.StatusLine; 9 | import org.apache.http.client.HttpClient; 10 | import org.apache.http.client.methods.HttpPost; 11 | import org.apache.http.entity.mime.MultipartEntity; 12 | import org.apache.http.entity.mime.content.StringBody; 13 | import org.apache.http.impl.client.DefaultHttpClient; 14 | 15 | import java.io.InputStream; 16 | import java.util.Arrays; 17 | import java.util.HashMap; 18 | import java.util.Map; 19 | 20 | /** 21 | * Created by kylewbanks on 2013-10-09. 22 | */ 23 | public class RESTController { 24 | 25 | private static final String TAG = "RESTController"; 26 | 27 | private static final String REST_URL = "http://kylewbanks.com/rest.json"; 28 | 29 | 30 | /** 31 | * Fetches a list of Posts from the remote server. 32 | * @param response 33 | */ 34 | public static void retrievePostList(PostListResponse response, long[] knownPostIds) { 35 | Log.i(TAG, "Loading Post List..."); 36 | 37 | Map params = new HashMap(); 38 | params.put("knownPostIds", Arrays.toString(knownPostIds)); 39 | 40 | RESTPerformer restPerformer = new RESTPerformer(); 41 | restPerformer.execute(getPackedParameters("/postList", params, response)); 42 | } 43 | 44 | /** 45 | * Bundles any additional parameters you want to pass to the server with the authentication parameters. 46 | * Also adds the required RESTResponse callback class, and server URL to the dictionary. 47 | */ 48 | private static HashMap getPackedParameters(String serverURL, Map additionalParams, RESTResponse response) { 49 | HashMap packedParams = new HashMap(); 50 | 51 | if(!serverURL.startsWith("/")) { 52 | serverURL = "/" + serverURL; 53 | } 54 | packedParams.put(RESTPerformer.SERVER_URL, REST_URL + serverURL); 55 | 56 | if(additionalParams != null) { 57 | packedParams.putAll(additionalParams); 58 | } 59 | packedParams.put(RESTPerformer.CALLBACK_CLASS, response); 60 | 61 | return packedParams; 62 | } 63 | 64 | 65 | /** 66 | * Performs the REST calls asynchronously, and executes the appropriate callback functionality. 67 | */ 68 | private static class RESTPerformer extends AsyncTask, Void, String> 69 | { 70 | private static final String TAG = "RESTPerformer"; 71 | 72 | public static final String CALLBACK_CLASS = "CallbackClass"; 73 | public static final String SERVER_URL = "ServerURL"; 74 | 75 | /** 76 | * Takes and InputStream and reads it's contents into a String. 77 | * @param is 78 | * @return 79 | */ 80 | private static String convertStreamToString(java.io.InputStream is) { 81 | java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A"); 82 | return s.hasNext() ? s.next() : ""; 83 | } 84 | 85 | /** 86 | * Performs the actual HTTP request, in a background thread. 87 | * @param params 88 | * @return 89 | */ 90 | @Override 91 | protected String doInBackground(HashMap... params) { 92 | //Retrieve the callback class and remove it from the map 93 | RESTResponse callback; 94 | if(params[0].containsKey(CALLBACK_CLASS)) { 95 | callback = (RESTResponse) params[0].get(CALLBACK_CLASS); 96 | params[0].remove(CALLBACK_CLASS); 97 | } else { 98 | callback = new RESTResponse() { 99 | @Override 100 | public void success(String json) { 101 | Log.e(TAG, "Callback not implemented!"); 102 | } 103 | @Override 104 | public void fail(Exception ex) { 105 | Log.e(TAG, "Callback not implemented!"); 106 | } 107 | }; 108 | } 109 | 110 | //Retrieve the Server URL and remove it from the map 111 | String serverURL; 112 | if(params[0].containsKey(SERVER_URL)) { 113 | serverURL = params[0].get(SERVER_URL).toString(); 114 | params[0].remove(SERVER_URL); 115 | } else { 116 | Log.e(TAG, "No server URL provided."); 117 | callback.fail(new Exception("No server URL provided")); 118 | return null; 119 | } 120 | 121 | //Generate the POST parameters 122 | StringBuilder builder = new StringBuilder(serverURL + "?"); 123 | MultipartEntity mpEntity = new MultipartEntity(); 124 | try { 125 | for(Map.Entry entry : params[0].entrySet()) { 126 | builder.append(entry.getKey() + "=" + entry.getValue() + "&"); 127 | mpEntity.addPart(entry.getKey(), new StringBody(entry.getValue().toString())); 128 | } 129 | } catch (Exception ex) { 130 | callback.fail(ex); 131 | return null; 132 | } 133 | Log.i(TAG, "Requesting: " + builder.toString()); 134 | 135 | try { 136 | //POST the parameters to the server, and retrieve the response 137 | HttpClient client = new DefaultHttpClient(); 138 | HttpPost post = new HttpPost(serverURL); 139 | post.setEntity(mpEntity); 140 | 141 | HttpResponse response = client.execute(post); 142 | StatusLine statusLine = response.getStatusLine(); 143 | if(statusLine.getStatusCode() == 200) { 144 | HttpEntity entity = response.getEntity(); 145 | InputStream content = entity.getContent(); 146 | 147 | try { 148 | callback.success(RESTPerformer.convertStreamToString(content)); 149 | } catch (Exception ex) { 150 | Log.e(TAG, "Failed to parse JSON due to: " + ex); 151 | callback.fail(ex); 152 | } 153 | content.close(); 154 | } else { 155 | Log.e(TAG, "Server responded with status code: " + statusLine.getStatusCode()); 156 | callback.fail(new Exception("Server responded with response code: " + statusLine.getStatusCode())); 157 | } 158 | } catch(Exception ex) { 159 | Log.e(TAG, "Failed to send HTTP POST request due to: " + ex); 160 | callback.fail(ex); 161 | } 162 | return null; 163 | } 164 | 165 | } 166 | 167 | } 168 | -------------------------------------------------------------------------------- /KyleWBanks/src/main/java/com/kylewbanks/network/RESTResponse.java: -------------------------------------------------------------------------------- 1 | package com.kylewbanks.network; 2 | 3 | /** 4 | * Created by kylewbanks on 2013-10-09. 5 | */ 6 | public interface RESTResponse { 7 | 8 | /** 9 | * Called upon successful completion of a HTTP request with the JSON that the server responded with. 10 | * 11 | * @param json 12 | */ 13 | void success(String json); 14 | 15 | /** 16 | * Called if the HTTP request fails for any reason. 17 | * 18 | * @param ex 19 | */ 20 | void fail(Exception ex); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /KyleWBanks/src/main/java/com/kylewbanks/network/response/PostListResponse.java: -------------------------------------------------------------------------------- 1 | package com.kylewbanks.network.response; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | import com.kylewbanks.model.Post; 6 | import com.kylewbanks.network.RESTResponse; 7 | 8 | import java.io.StringReader; 9 | import java.lang.reflect.Type; 10 | import java.util.Arrays; 11 | import java.util.List; 12 | 13 | /** 14 | * Created by kylewbanks on 2013-10-09. 15 | */ 16 | public class PostListResponse implements RESTResponse { 17 | 18 | protected List postList; 19 | 20 | /** 21 | * Called when the Post List is successfully loaded from the remote server to deserialize it from JSON to Post objects. 22 | * @param json 23 | */ 24 | @Override 25 | public void success(String json) { 26 | GsonBuilder gsonBuilder = new GsonBuilder(); 27 | gsonBuilder.setDateFormat("M/d/yy hh:mm a"); 28 | Gson gson = gsonBuilder.create(); 29 | 30 | postList = Arrays.asList(gson.fromJson(json, Post[].class)); 31 | } 32 | 33 | @Override 34 | public void fail(Exception ex) { 35 | 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /KyleWBanks/src/main/res/anim/down_from_top.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /KyleWBanks/src/main/res/anim/left_to_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | -------------------------------------------------------------------------------- /KyleWBanks/src/main/res/anim/right_to_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /KyleWBanks/src/main/res/anim/start_left_to_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | -------------------------------------------------------------------------------- /KyleWBanks/src/main/res/anim/start_right_to_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /KyleWBanks/src/main/res/anim/up_from_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /KyleWBanks/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KyleBanks/kylewbanks.com-AndroidApp/6feba775579336c96bf751942a48d8f23db6cf37/KyleWBanks/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /KyleWBanks/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KyleBanks/kylewbanks.com-AndroidApp/6feba775579336c96bf751942a48d8f23db6cf37/KyleWBanks/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /KyleWBanks/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KyleBanks/kylewbanks.com-AndroidApp/6feba775579336c96bf751942a48d8f23db6cf37/KyleWBanks/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /KyleWBanks/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KyleBanks/kylewbanks.com-AndroidApp/6feba775579336c96bf751942a48d8f23db6cf37/KyleWBanks/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /KyleWBanks/src/main/res/drawable/post_list_item_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 14 | 15 | 16 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /KyleWBanks/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 15 | 16 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /KyleWBanks/src/main/res/layout/header.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 19 | -------------------------------------------------------------------------------- /KyleWBanks/src/main/res/layout/post_list_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 17 | 25 | 31 | 32 | 38 | 39 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /KyleWBanks/src/main/res/layout/post_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /KyleWBanks/src/main/res/menu/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /KyleWBanks/src/main/res/values-sw600dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /KyleWBanks/src/main/res/values-sw720dp-land/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 128dp 5 | 6 | -------------------------------------------------------------------------------- /KyleWBanks/src/main/res/values-v11/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /KyleWBanks/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /KyleWBanks/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Kyle W. Banks 5 | Settings 6 | Hello world! 7 | 8 | 9 | -------------------------------------------------------------------------------- /KyleWBanks/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 16 | 17 | 18 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | kylewbanks.com Android App 2 | ========================= 3 | 4 | A blog reader app that displays posts from [kylewbanks.com](http://kylewbanks.com). 5 | 6 | 7 | Dependencies 8 | ============ 9 | 10 | - [KyleBanks/AnimatedListView](https://github.com/KyleBanks/AnimatedListView) 11 | 12 | 13 | Screenshots 14 | =========== 15 | 16 | ![App Screenshots](https://s3.amazonaws.com/kylewbanks/app_screenshots.png "App Screenshots") 17 | 18 | 19 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KyleBanks/kylewbanks.com-AndroidApp/6feba775579336c96bf751942a48d8f23db6cf37/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Apr 10 15:27:10 PDT 2013 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=http\://services.gradle.org/distributions/gradle-1.6-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':KyleWBanks', ':AnimatedListView' --------------------------------------------------------------------------------