├── .idea
├── .name
├── dictionaries
│ └── mchang.xml
├── ant.xml
├── encodings.xml
├── vcs.xml
├── copyright
│ ├── profiles_settings.xml
│ └── BSD.xml
├── modules.xml
├── compiler.xml
├── misc.xml
└── uiDesigner.xml
├── .gitignore
├── res
├── drawable
│ └── stub.png
├── raw
│ └── keystore.bks
├── drawable-hdpi
│ ├── icon.png
│ ├── ic_menu_home.png
│ ├── ic_menu_star.png
│ ├── ic_menu_camera.png
│ ├── ic_menu_compose.png
│ ├── ic_menu_delete.png
│ ├── ic_menu_gallery.png
│ ├── ic_menu_login.png
│ ├── ic_menu_refresh.png
│ ├── ic_title_camera.png
│ ├── ic_title_home.png
│ ├── ic_menu_slideshow.png
│ ├── ic_title_refresh.png
│ ├── ic_menu_allfriends.png
│ └── ic_menu_preferences.png
├── drawable-ldpi
│ ├── icon.png
│ ├── ic_menu_home.png
│ ├── ic_menu_star.png
│ ├── ic_menu_camera.png
│ ├── ic_menu_compose.png
│ ├── ic_menu_delete.png
│ ├── ic_menu_gallery.png
│ ├── ic_menu_login.png
│ ├── ic_menu_refresh.png
│ ├── ic_title_camera.png
│ ├── ic_title_home.png
│ ├── ic_menu_slideshow.png
│ ├── ic_title_refresh.png
│ ├── ic_menu_allfriends.png
│ └── ic_menu_preferences.png
├── drawable-mdpi
│ ├── icon.png
│ ├── ic_menu_home.png
│ ├── ic_menu_star.png
│ ├── ic_menu_camera.png
│ ├── ic_menu_compose.png
│ ├── ic_menu_delete.png
│ ├── ic_menu_gallery.png
│ ├── ic_menu_login.png
│ ├── ic_menu_refresh.png
│ ├── ic_title_camera.png
│ ├── ic_title_home.png
│ ├── ic_menu_slideshow.png
│ ├── ic_title_refresh.png
│ ├── ic_menu_allfriends.png
│ └── ic_menu_preferences.png
├── layout
│ ├── login_layout.xml
│ ├── feed_layout.xml
│ ├── popular_layout.xml
│ ├── home_layout.xml
│ ├── feed_list_item.xml
│ └── detail_layout.xml
└── values
│ ├── dimens.xml
│ ├── styles.xml
│ ├── colors.xml
│ └── strings.xml
├── default.properties
├── README.md
├── local.properties
├── proguard.cfg
├── AndroidManifest.xml
├── android-instagram2.iml
├── android-instagram2.iml.backup
└── src
└── org
└── acmelab
└── andgram2
├── Comment.java
├── InstagramImage.java
├── Constants.java
├── Credentials.java
├── LazyGridAdapter.java
├── MyHttpClient.java
├── HomeActivity.java
├── LazyListAdapter.java
├── Utils.java
├── LoginActivity.java
├── ImageLoader.java
├── PopularActivity.java
├── FeedActivity.java
└── ImageDetailActivity.java
/.idea/.name:
--------------------------------------------------------------------------------
1 | android-instagram2
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | out/
2 | .idea/workspace.xml
3 | tmp/
4 | gen/
5 | libs/.DS_Store
6 |
--------------------------------------------------------------------------------
/res/drawable/stub.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable/stub.png
--------------------------------------------------------------------------------
/res/raw/keystore.bks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/raw/keystore.bks
--------------------------------------------------------------------------------
/res/drawable-hdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-hdpi/icon.png
--------------------------------------------------------------------------------
/res/drawable-ldpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-ldpi/icon.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-mdpi/icon.png
--------------------------------------------------------------------------------
/.idea/dictionaries/mchang.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_menu_home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-hdpi/ic_menu_home.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_menu_star.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-hdpi/ic_menu_star.png
--------------------------------------------------------------------------------
/res/drawable-ldpi/ic_menu_home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-ldpi/ic_menu_home.png
--------------------------------------------------------------------------------
/res/drawable-ldpi/ic_menu_star.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-ldpi/ic_menu_star.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/ic_menu_home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-mdpi/ic_menu_home.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/ic_menu_star.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-mdpi/ic_menu_star.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_menu_camera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-hdpi/ic_menu_camera.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_menu_compose.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-hdpi/ic_menu_compose.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_menu_delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-hdpi/ic_menu_delete.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_menu_gallery.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-hdpi/ic_menu_gallery.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_menu_login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-hdpi/ic_menu_login.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_menu_refresh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-hdpi/ic_menu_refresh.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_title_camera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-hdpi/ic_title_camera.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_title_home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-hdpi/ic_title_home.png
--------------------------------------------------------------------------------
/res/drawable-ldpi/ic_menu_camera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-ldpi/ic_menu_camera.png
--------------------------------------------------------------------------------
/res/drawable-ldpi/ic_menu_compose.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-ldpi/ic_menu_compose.png
--------------------------------------------------------------------------------
/res/drawable-ldpi/ic_menu_delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-ldpi/ic_menu_delete.png
--------------------------------------------------------------------------------
/res/drawable-ldpi/ic_menu_gallery.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-ldpi/ic_menu_gallery.png
--------------------------------------------------------------------------------
/res/drawable-ldpi/ic_menu_login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-ldpi/ic_menu_login.png
--------------------------------------------------------------------------------
/res/drawable-ldpi/ic_menu_refresh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-ldpi/ic_menu_refresh.png
--------------------------------------------------------------------------------
/res/drawable-ldpi/ic_title_camera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-ldpi/ic_title_camera.png
--------------------------------------------------------------------------------
/res/drawable-ldpi/ic_title_home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-ldpi/ic_title_home.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/ic_menu_camera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-mdpi/ic_menu_camera.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/ic_menu_compose.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-mdpi/ic_menu_compose.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/ic_menu_delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-mdpi/ic_menu_delete.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/ic_menu_gallery.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-mdpi/ic_menu_gallery.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/ic_menu_login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-mdpi/ic_menu_login.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/ic_menu_refresh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-mdpi/ic_menu_refresh.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/ic_title_camera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-mdpi/ic_title_camera.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/ic_title_home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-mdpi/ic_title_home.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_menu_slideshow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-hdpi/ic_menu_slideshow.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_title_refresh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-hdpi/ic_title_refresh.png
--------------------------------------------------------------------------------
/res/drawable-ldpi/ic_menu_slideshow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-ldpi/ic_menu_slideshow.png
--------------------------------------------------------------------------------
/res/drawable-ldpi/ic_title_refresh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-ldpi/ic_title_refresh.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/ic_menu_slideshow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-mdpi/ic_menu_slideshow.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/ic_title_refresh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-mdpi/ic_title_refresh.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_menu_allfriends.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-hdpi/ic_menu_allfriends.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_menu_preferences.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-hdpi/ic_menu_preferences.png
--------------------------------------------------------------------------------
/res/drawable-ldpi/ic_menu_allfriends.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-ldpi/ic_menu_allfriends.png
--------------------------------------------------------------------------------
/res/drawable-ldpi/ic_menu_preferences.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-ldpi/ic_menu_preferences.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/ic_menu_allfriends.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-mdpi/ic_menu_allfriends.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/ic_menu_preferences.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markchang/android-instagram2/HEAD/res/drawable-mdpi/ic_menu_preferences.png
--------------------------------------------------------------------------------
/.idea/ant.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/default.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system use,
7 | # "build.properties", and override values to adapt the script to your
8 | # project structure.
9 |
10 | # Project target.
11 | target=Google Inc.:Google APIs:7
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Android Instagram (Andgram)
2 | ===========================
3 |
4 | This is an Instagram client for Android. It is based on my
5 | [previous version](https://github.com/markchang/android-instagram/) which
6 | did not use the official API. This one does.
7 |
8 | No, it won't let you create an Instagram account.
9 |
10 | If you want changes, fork the repository and send a pull request.
11 |
12 | Resources:
13 |
14 | * http://instagram.com/developer/
15 |
16 |
--------------------------------------------------------------------------------
/local.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must *NOT* be checked in Version Control Systems,
5 | # as it contains information specific to your local configuration.
6 |
7 | # location of the SDK. This is only used by Ant
8 | # For customization when using a Version Control System, please read the
9 | # header note.
10 | sdk.dir=/Users/mchang/Code/android-sdk
11 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/res/layout/login_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
13 |
14 |
20 |
21 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/proguard.cfg:
--------------------------------------------------------------------------------
1 | -optimizationpasses 5
2 | -dontusemixedcaseclassnames
3 | -dontskipnonpubliclibraryclasses
4 | -dontpreverify
5 | -verbose
6 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
7 |
8 | -keep public class * extends android.app.Activity
9 | -keep public class * extends android.app.Application
10 | -keep public class * extends android.app.Service
11 | -keep public class * extends android.content.BroadcastReceiver
12 | -keep public class * extends android.content.ContentProvider
13 | -keep public class com.android.vending.licensing.ILicensingService
14 |
15 | -keepclasseswithmembernames class * {
16 | native ;
17 | }
18 |
19 | -keepclasseswithmembernames class * {
20 | public (android.content.Context, android.util.AttributeSet);
21 | }
22 |
23 | -keepclasseswithmembernames class * {
24 | public (android.content.Context, android.util.AttributeSet, int);
25 | }
26 |
27 | -keepclassmembers enum * {
28 | public static **[] values();
29 | public static ** valueOf(java.lang.String);
30 | }
31 |
32 | -keep class * implements android.os.Parcelable {
33 | public static final android.os.Parcelable$Creator *;
34 | }
35 |
--------------------------------------------------------------------------------
/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
11 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/.idea/copyright/BSD.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android-instagram2.iml:
--------------------------------------------------------------------------------
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 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
28 |
29 |
30 | 6dip
31 | 45dip
32 | 90dip
33 | 14sp
34 | 18sp
35 | 22sp
36 |
37 |
--------------------------------------------------------------------------------
/.idea/misc.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 |
26 |
27 |
28 |
29 |
30 |
31 | Inspections
32 |
33 |
34 |
35 |
36 | Abstraction issues
37 |
38 |
39 |
40 |
41 |
42 |
43 | http://www.w3.org/1999/xhtml
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/android-instagram2.iml.backup:
--------------------------------------------------------------------------------
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 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/src/org/acmelab/andgram2/Comment.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011, Mark L. Chang . All rights reserved.
3 | *
4 | * Redistribution and use in source and binary forms, with or without modification, are
5 | * permitted provided that the following conditions are met:
6 | *
7 | * 1. Redistributions of source code must retain the above copyright notice, this list of
8 | * conditions and the following disclaimer.
9 | *
10 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list
11 | * of conditions and the following disclaimer in the documentation and/or other
12 | * materials provided with the distribution.
13 | *
14 | * THIS SOFTWARE IS PROVIDED BY Mark L. Chang ``AS IS'' AND ANY EXPRESS OR IMPLIED
15 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
16 | * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MARK L. CHANG OR
17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
21 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
22 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 | *
24 | * The views and conclusions contained in the software and documentation are those of the
25 | * authors and should not be interpreted as representing official policies, either expressed
26 | * or implied, of Mark L. Chang.
27 | */
28 |
29 | package org.acmelab.andgram2;
30 |
31 | /**
32 | * Created by IntelliJ IDEA.
33 | * User: mchang
34 | * Date: 3/29/11
35 | * Time: 1:30 AM
36 | * To change this template use File | Settings | File Templates.
37 | */
38 | public class Comment {
39 | public final String username;
40 | public final String comment;
41 |
42 | public Comment(String _username, String _comment) {
43 | this.username = _username;
44 | this.comment = _comment;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/res/layout/feed_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
29 |
30 |
34 |
35 |
39 |
40 |
45 |
46 |
--------------------------------------------------------------------------------
/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
28 |
29 |
30 |
41 |
--------------------------------------------------------------------------------
/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
28 |
29 |
30 | #ff355689
31 | #ffffffff
32 | #ff000000
33 | #ff355689
34 | #ff7081a3
35 | #ffffffff
36 | #ffd5ddeb
37 | #ffe3e8f1
38 | #40ffffff
39 |
40 | #ff999999
41 |
42 | #ff202020
43 |
44 | #3A5FCD
45 | #3A5FCD
46 | #27408B
47 |
48 |
49 |
--------------------------------------------------------------------------------
/src/org/acmelab/andgram2/InstagramImage.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011, Mark L. Chang . All rights reserved.
3 | *
4 | * Redistribution and use in source and binary forms, with or without modification, are
5 | * permitted provided that the following conditions are met:
6 | *
7 | * 1. Redistributions of source code must retain the above copyright notice, this list of
8 | * conditions and the following disclaimer.
9 | *
10 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list
11 | * of conditions and the following disclaimer in the documentation and/or other
12 | * materials provided with the distribution.
13 | *
14 | * THIS SOFTWARE IS PROVIDED BY Mark L. Chang ``AS IS'' AND ANY EXPRESS OR IMPLIED
15 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
16 | * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MARK L. CHANG OR
17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
21 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
22 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 | *
24 | * The views and conclusions contained in the software and documentation are those of the
25 | * authors and should not be interpreted as representing official policies, either expressed
26 | * or implied, of Mark L. Chang.
27 | */
28 |
29 | package org.acmelab.andgram2;
30 |
31 | import java.util.ArrayList;
32 |
33 | /**
34 | * Created by IntelliJ IDEA.
35 | * User: mchang
36 | * Date: 3/25/11
37 | * Time: 11:09 PM
38 | * To change this template use File | Settings | File Templates.
39 | */
40 | public class InstagramImage {
41 | public String thumbnail;
42 | public String low_resolution;
43 | public String standard_resolution;
44 | public String permalink;
45 | public String username;
46 | public String user_id;
47 | public String full_name = "";
48 | public String caption = "";
49 | public String taken_at;
50 | public Long taken_time;
51 | public String id;
52 |
53 | public boolean user_has_liked;
54 |
55 | public int liker_count;
56 | public int comment_count;
57 |
58 | public ArrayList liker_list;
59 | public ArrayList comment_list;
60 | }
61 |
--------------------------------------------------------------------------------
/res/layout/popular_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
29 |
30 |
34 |
35 |
39 |
40 |
51 |
--------------------------------------------------------------------------------
/res/layout/home_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
14 |
15 |
21 |
26 |
31 |
36 |
37 |
38 |
43 |
48 |
53 |
54 |
55 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/src/org/acmelab/andgram2/Constants.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011, Mark L. Chang . All rights reserved.
3 | *
4 | * Redistribution and use in source and binary forms, with or without modification, are
5 | * permitted provided that the following conditions are met:
6 | *
7 | * 1. Redistributions of source code must retain the above copyright notice, this list of
8 | * conditions and the following disclaimer.
9 | *
10 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list
11 | * of conditions and the following disclaimer in the documentation and/or other
12 | * materials provided with the distribution.
13 | *
14 | * THIS SOFTWARE IS PROVIDED BY Mark L. Chang ``AS IS'' AND ANY EXPRESS OR IMPLIED
15 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
16 | * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MARK L. CHANG OR
17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
21 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
22 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 | *
24 | * The views and conclusions contained in the software and documentation are those of the
25 | * authors and should not be interpreted as representing official policies, either expressed
26 | * or implied, of Mark L. Chang.
27 | */
28 |
29 | package org.acmelab.andgram2;
30 |
31 | /**
32 | * Created by IntelliJ IDEA.
33 | * User: mchang
34 | * Date: 4/16/11
35 | * Time: 12:32 AM
36 | * All them constat stuffs.
37 | */
38 | public class Constants {
39 | public static final String TAG = "ANDGRAM2";
40 | public static final String PREFS_NAME = "andgram2_prefs";
41 |
42 | public static final String AUTHORIZATION_URL = "https://api.instagram.com/oauth/authorize/";
43 | public static final String ACCESS_TOKEN_ENDPOINT = "https://api.instagram.com/oauth/access_token";
44 | public static final String REDIRECT_URI = "andgram://";
45 |
46 | public static final String POPULAR_ENDPOINT = "https://api.instagram.com/v1/media/popular/";
47 | public static final String USER_FEED_ENDPOINT = "https://api.instagram.com/v1/users/self/feed";
48 | public static final String USER_RECENT_ENDPOINT = "https://api.instagram.com/v1/users/self/media/recent";
49 |
50 | public static final String MEDIA_ENDPOINT = "https://api.instagram.com/v1/media/";
51 | public static final String LIKE_MEDIA_ENDPOINT = "/likes/";
52 | public static final String COMMENT_MEDIA_ENDPOINT = "/comments";
53 |
54 | public static final String OUTPUT_DIR = "andgram";
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/src/org/acmelab/andgram2/Credentials.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011, Mark L. Chang . All rights reserved.
3 | *
4 | * Redistribution and use in source and binary forms, with or without modification, are
5 | * permitted provided that the following conditions are met:
6 | *
7 | * 1. Redistributions of source code must retain the above copyright notice, this list of
8 | * conditions and the following disclaimer.
9 | *
10 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list
11 | * of conditions and the following disclaimer in the documentation and/or other
12 | * materials provided with the distribution.
13 | *
14 | * THIS SOFTWARE IS PROVIDED BY Mark L. Chang ``AS IS'' AND ANY EXPRESS OR IMPLIED
15 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
16 | * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MARK L. CHANG OR
17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
21 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
22 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 | *
24 | * The views and conclusions contained in the software and documentation are those of the
25 | * authors and should not be interpreted as representing official policies, either expressed
26 | * or implied, of Mark L. Chang.
27 | */
28 |
29 | package org.acmelab.andgram2;
30 |
31 | /**
32 | * Created by IntelliJ IDEA.
33 | * User: mchang
34 | * Date: 4/15/11
35 | * Time: 11:44 AM
36 | * SEKRET.
37 | */
38 | public class Credentials {
39 |
40 | /*
41 | Instagram credentials come from the developer's account. You can register a new
42 | client (aka. Andgram2) here: http://instagram.com/developer/manage/
43 |
44 | Copy your Client ID and Client Secret into the below Strings
45 | */
46 | public static final String CLIENT_ID = "";
47 | public static final String CLIENT_SECRET = "";
48 |
49 | /*
50 | Android doesn't include all SSL CA certs, obviously. The signing certificate
51 | Instagram uses is not in my build of Android. So, you need to bundle all
52 | the certificates in the signing path, all the way to the root CA. To do so,
53 | you need to make keystore with all the certificates. Mine is stored here
54 | in res/raw. I believe it has to have a password associated with it. The
55 | variable below is that password.
56 |
57 | Instructions on how to make a cert bundle can be found here:
58 | http://bit.ly/dNKsFt
59 |
60 | I am not including my keystore password here.
61 | */
62 | public static final String KEYSTORE_PASSWORD = "";
63 | }
64 |
--------------------------------------------------------------------------------
/res/layout/feed_list_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
29 |
30 |
35 |
42 |
51 |
58 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
29 |
30 |
31 | Andgram2
32 | Andgram2
33 |
34 | Credentials
35 | Refresh
36 | Preferences
37 | Clear
38 |
39 | Comment
40 | Feed
41 | Popular
42 | Your feed
43 | Image Details
44 | empty
45 |
46 | Dashboard
47 | Login
48 |
49 | Login to Instagram
50 |
51 |
52 | This application does not support the creation of new Instagram accounts.
53 | That portion of the undocumented API has not yet been reverse-engineered.
54 | You will need to use an iOS device (such as an iPhone) in order to create
55 | an account.
56 |
57 |
58 |
59 | When you click on the button below, you will be taken to the Instagram
60 | web site to log in. You will be asked to authorize Andgram2 to access
61 | Instagram on your behalf. This way, we do not store your username or
62 | password.
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/res/layout/detail_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
29 |
30 |
31 |
36 |
40 |
41 |
45 |
46 |
53 |
64 |
71 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/src/org/acmelab/andgram2/LazyGridAdapter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011, Mark L. Chang . All rights reserved.
3 | *
4 | * Redistribution and use in source and binary forms, with or without modification, are
5 | * permitted provided that the following conditions are met:
6 | *
7 | * 1. Redistributions of source code must retain the above copyright notice, this list of
8 | * conditions and the following disclaimer.
9 | *
10 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list
11 | * of conditions and the following disclaimer in the documentation and/or other
12 | * materials provided with the distribution.
13 | *
14 | * THIS SOFTWARE IS PROVIDED BY Mark L. Chang ``AS IS'' AND ANY EXPRESS OR IMPLIED
15 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
16 | * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MARK L. CHANG OR
17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
21 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
22 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 | *
24 | * The views and conclusions contained in the software and documentation are those of the
25 | * authors and should not be interpreted as representing official policies, either expressed
26 | * or implied, of Mark L. Chang.
27 | */
28 |
29 | package org.acmelab.andgram2;
30 |
31 | import android.app.Activity;
32 | import android.content.Context;
33 | import android.util.Log;
34 | import android.view.LayoutInflater;
35 | import android.view.View;
36 | import android.view.ViewGroup;
37 | import android.widget.BaseAdapter;
38 | import android.widget.GridView;
39 | import android.widget.ImageView;
40 |
41 | import java.util.ArrayList;
42 |
43 | public class LazyGridAdapter extends BaseAdapter {
44 | private static final boolean debug = false;
45 | private Activity activity;
46 | private ArrayList instagramImageArrayList;
47 | private static LayoutInflater inflater = null;
48 | public ImageLoader imageLoader;
49 |
50 | public LazyGridAdapter(Activity a, ArrayList i) {
51 | activity = a;
52 | instagramImageArrayList = i;
53 | inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
54 | imageLoader = new ImageLoader(activity.getApplicationContext());
55 | }
56 |
57 | public int getCount() {
58 | return instagramImageArrayList.size();
59 | }
60 |
61 | public Object getItem(int position) {
62 | return instagramImageArrayList.get(position);
63 | }
64 |
65 | public long getItemId(int position) {
66 | return position;
67 | }
68 |
69 | public View getView(int position, View convertView, ViewGroup parent) {
70 | if(debug) Log.i(Constants.TAG, "Getting grid view " + Integer.toString(position));
71 | ImageView imageView;
72 | if (convertView == null) { // if it's not recycled, initialize some attributes
73 | if(debug) Log.i(Constants.TAG, "Creating view for the first time");
74 | final int imageDim = (int) (75 * activity.getResources().getDisplayMetrics().density + 0.5f );
75 | imageView = new ImageView(activity);
76 | imageView.setLayoutParams(new GridView.LayoutParams(imageDim, imageDim));
77 | imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
78 | imageView.setPadding(0, 0, 0, 0);
79 | } else {
80 | if(debug) Log.i(Constants.TAG, "Got a view");
81 | imageView = (ImageView) convertView;
82 | }
83 |
84 | InstagramImage image = instagramImageArrayList.get(position);
85 | imageView.setTag(image.thumbnail);
86 | imageLoader.DisplayImage(image.thumbnail, activity, imageView);
87 | return imageView;
88 | }
89 | }
--------------------------------------------------------------------------------
/src/org/acmelab/andgram2/MyHttpClient.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011, Mark L. Chang . All rights reserved.
3 | *
4 | * Redistribution and use in source and binary forms, with or without modification, are
5 | * permitted provided that the following conditions are met:
6 | *
7 | * 1. Redistributions of source code must retain the above copyright notice, this list of
8 | * conditions and the following disclaimer.
9 | *
10 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list
11 | * of conditions and the following disclaimer in the documentation and/or other
12 | * materials provided with the distribution.
13 | *
14 | * THIS SOFTWARE IS PROVIDED BY Mark L. Chang ``AS IS'' AND ANY EXPRESS OR IMPLIED
15 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
16 | * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MARK L. CHANG OR
17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
21 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
22 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 | *
24 | * The views and conclusions contained in the software and documentation are those of the
25 | * authors and should not be interpreted as representing official policies, either expressed
26 | * or implied, of Mark L. Chang.
27 | */
28 |
29 | package org.acmelab.andgram2;
30 |
31 | import android.content.Context;
32 | import org.apache.http.conn.ClientConnectionManager;
33 | import org.apache.http.conn.scheme.PlainSocketFactory;
34 | import org.apache.http.conn.scheme.Scheme;
35 | import org.apache.http.conn.scheme.SchemeRegistry;
36 | import org.apache.http.conn.ssl.SSLSocketFactory;
37 | import org.apache.http.impl.client.DefaultHttpClient;
38 | import org.apache.http.impl.conn.SingleClientConnManager;
39 |
40 | import java.io.InputStream;
41 | import java.security.KeyStore;
42 |
43 | /**
44 | * Created by IntelliJ IDEA.
45 | * User: mchang
46 | * Date: 4/15/11
47 | * Time: 10:34 PM
48 | * Had to make a new client to support the certificate store for instagram's site.
49 | */
50 | public class MyHttpClient extends DefaultHttpClient {
51 | final Context context;
52 |
53 | public MyHttpClient(Context context) {
54 | this.context = context;
55 | }
56 |
57 | @Override
58 | protected ClientConnectionManager createClientConnectionManager() {
59 | SchemeRegistry registry = new SchemeRegistry();
60 | registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
61 | // Register for port 443 our SSLSocketFactory with our keystore
62 | // to the ConnectionManager
63 | registry.register(new Scheme("https", newSslSocketFactory(), 443));
64 | return new SingleClientConnManager(getParams(), registry);
65 |
66 | }
67 |
68 | private SSLSocketFactory newSslSocketFactory() {
69 | try {
70 | // Get an instance of the Bouncy Castle KeyStore format
71 | KeyStore trusted = KeyStore.getInstance("BKS");
72 | // Get the raw resource, which contains the keystore with
73 | // your trusted certificates (root and any intermediate certs)
74 | InputStream in = context.getResources().openRawResource(R.raw.keystore);
75 | try {
76 | // Initialize the keystore with the provided trusted certificates
77 | // Also provide the password of the keystore
78 | trusted.load(in, Credentials.KEYSTORE_PASSWORD.toCharArray());
79 | } finally {
80 | in.close();
81 | }
82 | // Pass the keystore to the SSLSocketFactory. The factory is responsible
83 | // for the verification of the server certificate.
84 | SSLSocketFactory sf = new SSLSocketFactory(trusted);
85 | // Hostname verification from certificate
86 | // http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html#d4e506
87 | sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
88 | return sf;
89 | } catch (Exception e) {
90 | throw new AssertionError(e);
91 | }
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/src/org/acmelab/andgram2/HomeActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011, Mark L. Chang . All rights reserved.
3 | *
4 | * Redistribution and use in source and binary forms, with or without modification, are
5 | * permitted provided that the following conditions are met:
6 | *
7 | * 1. Redistributions of source code must retain the above copyright notice, this list of
8 | * conditions and the following disclaimer.
9 | *
10 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list
11 | * of conditions and the following disclaimer in the documentation and/or other
12 | * materials provided with the distribution.
13 | *
14 | * THIS SOFTWARE IS PROVIDED BY Mark L. Chang ``AS IS'' AND ANY EXPRESS OR IMPLIED
15 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
16 | * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MARK L. CHANG OR
17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
21 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
22 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 | *
24 | * The views and conclusions contained in the software and documentation are those of the
25 | * authors and should not be interpreted as representing official policies, either expressed
26 | * or implied, of Mark L. Chang.
27 | */
28 |
29 | package org.acmelab.andgram2;
30 |
31 | import android.app.Activity;
32 | import android.content.Intent;
33 | import android.content.SharedPreferences;
34 | import android.os.Bundle;
35 | import android.util.Log;
36 | import android.view.View;
37 |
38 | public class HomeActivity extends Activity
39 | {
40 | String access_token = null;
41 |
42 | /** Called when the activity is first created. */
43 | @Override
44 | public void onCreate(Bundle savedInstanceState)
45 | {
46 | super.onCreate(savedInstanceState);
47 | Log.i(Constants.TAG, "onCreate");
48 | setContentView(R.layout.home_layout);
49 |
50 | // if no login data, prompt for login
51 | access_token = Utils.getAccessToken(this);
52 |
53 | if( access_token == null ) {
54 | openLoginIntent(null);
55 | }
56 | }
57 |
58 | public void setAccessToken() {
59 | SharedPreferences sharedPreferences = getSharedPreferences(Constants.PREFS_NAME, MODE_PRIVATE);
60 | access_token = sharedPreferences.getString("access_token", null);
61 | }
62 |
63 |
64 | public void openPopularGridIntent(View view) {
65 | // if no login data, prompt for login
66 | access_token = Utils.getAccessToken(this);
67 |
68 | if( access_token == null )
69 | openLoginIntent(null);
70 | else {
71 | String popular_endpoint = Utils.decorateEndpoint(Constants.POPULAR_ENDPOINT, access_token);
72 | Intent feedIntent = new Intent(HomeActivity.this, PopularActivity.class);
73 | feedIntent.putExtra("endpoint", popular_endpoint);
74 | feedIntent.putExtra("title", R.string.popular);
75 | startActivity(feedIntent);
76 | }
77 | }
78 |
79 | public void openFeedIntent(View view) {
80 | access_token = Utils.getAccessToken(this);
81 |
82 | if( access_token == null )
83 | openLoginIntent(null);
84 | else {
85 | String feed_endpoint = Utils.decorateEndpoint(Constants.USER_FEED_ENDPOINT, access_token);
86 | Intent feedIntent = new Intent(HomeActivity.this, FeedActivity.class);
87 | feedIntent.putExtra("endpoint", feed_endpoint);
88 | feedIntent.putExtra("title", R.string.feed);
89 | startActivity(feedIntent);
90 | }
91 | }
92 |
93 | public void openUserFeedIntent(View view) {
94 | access_token = Utils.getAccessToken(this);
95 |
96 | if( access_token == null )
97 | openLoginIntent(null);
98 | else {
99 | String feed_endpoint = Utils.decorateEndpoint(Constants.USER_RECENT_ENDPOINT, access_token);
100 | Intent feedIntent = new Intent(HomeActivity.this, FeedActivity.class);
101 | feedIntent.putExtra("endpoint", feed_endpoint);
102 | feedIntent.putExtra("title", R.string.userfeed);
103 | startActivity(feedIntent);
104 | }
105 | }
106 |
107 | public void openLoginIntent(View view) {
108 | // clear all login data
109 | SharedPreferences sharedPreferences = getSharedPreferences(Constants.PREFS_NAME, MODE_PRIVATE);
110 | SharedPreferences.Editor editor = sharedPreferences.edit();
111 | editor.clear();
112 | editor.commit();
113 |
114 | Intent loginIntent = new Intent(HomeActivity.this, LoginActivity.class);
115 | loginIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_SINGLE_TOP);
116 | startActivity(loginIntent);
117 | }
118 |
119 | }
120 |
--------------------------------------------------------------------------------
/src/org/acmelab/andgram2/LazyListAdapter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011, Mark L. Chang . All rights reserved.
3 | *
4 | * Redistribution and use in source and binary forms, with or without modification, are
5 | * permitted provided that the following conditions are met:
6 | *
7 | * 1. Redistributions of source code must retain the above copyright notice, this list of
8 | * conditions and the following disclaimer.
9 | *
10 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list
11 | * of conditions and the following disclaimer in the documentation and/or other
12 | * materials provided with the distribution.
13 | *
14 | * THIS SOFTWARE IS PROVIDED BY Mark L. Chang ``AS IS'' AND ANY EXPRESS OR IMPLIED
15 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
16 | * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MARK L. CHANG OR
17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
21 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
22 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 | *
24 | * The views and conclusions contained in the software and documentation are those of the
25 | * authors and should not be interpreted as representing official policies, either expressed
26 | * or implied, of Mark L. Chang.
27 | */
28 |
29 | package org.acmelab.andgram2;
30 |
31 | import android.app.Activity;
32 | import android.content.Context;
33 | import android.text.Html;
34 | import android.view.LayoutInflater;
35 | import android.view.View;
36 | import android.view.ViewGroup;
37 | import android.widget.BaseAdapter;
38 | import android.widget.ImageView;
39 | import android.widget.TextView;
40 |
41 | import java.util.ArrayList;
42 |
43 | public class LazyListAdapter extends BaseAdapter {
44 |
45 | private Activity activity;
46 | private ArrayList instagramImageArrayList;
47 | private static LayoutInflater inflater=null;
48 | public ImageLoader imageLoader;
49 |
50 | public LazyListAdapter(Activity a, ArrayList i) {
51 | activity = a;
52 | instagramImageArrayList = i;
53 | inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
54 | imageLoader = new ImageLoader(activity.getApplicationContext());
55 | }
56 |
57 | public int getCount() {
58 | return instagramImageArrayList.size();
59 | }
60 |
61 | public Object getItem(int position) {
62 | return instagramImageArrayList.get(position);
63 | }
64 |
65 | public long getItemId(int position) {
66 | return position;
67 | }
68 |
69 | public static class ViewHolder{
70 | public TextView username;
71 | public TextView comments;
72 | public TextView caption;
73 | public ImageView image;
74 | }
75 |
76 | public View getView(int position, View convertView, ViewGroup parent) {
77 | View vi = convertView;
78 | ViewHolder holder;
79 | if(convertView == null){
80 | vi = inflater.inflate(R.layout.feed_list_item, null);
81 | holder = new ViewHolder();
82 | holder.image = (ImageView)vi.findViewById(R.id.feed_image);
83 | holder.username = (TextView)vi.findViewById(R.id.feed_username);
84 | holder.comments = (TextView)vi.findViewById(R.id.feed_comments);
85 | holder.caption = (TextView)vi.findViewById(R.id.feed_caption);
86 | vi.setTag(holder);
87 | }
88 | else
89 | holder = (ViewHolder)vi.getTag();
90 |
91 | InstagramImage image = instagramImageArrayList.get(position);
92 |
93 | holder.image.setTag(image.standard_resolution);
94 | holder.username.setText(Html.fromHtml("" + image.username + " ") +
95 | image.taken_at);
96 | holder.caption.setText(Html.fromHtml("" + image.username + " " + image.caption));
97 |
98 | // comments hold likes and comments
99 | StringBuilder likerString = new StringBuilder();
100 |
101 | if( image.liker_list != null ) {
102 | if( image.liker_list.size() > 0 ) {
103 | likerString.append("Liked by ");
104 | for( String liker : image.liker_list ) {
105 | likerString.append(" " + liker);
106 | }
107 | likerString.append("");
108 | if( image.liker_list.size() < image.liker_count ) {
109 | int others_count = image.liker_count - image.liker_list.size();
110 | likerString.append(" and " + Integer.toString(others_count) + " others");
111 | }
112 | likerString.append("
");
113 | }
114 | }
115 |
116 | // iterate over comments
117 | if( image.comment_list != null ) {
118 | if( image.comment_list.size() > 0 ) {
119 | for( Comment comment : image.comment_list ) {
120 | likerString.append("" + comment.username + " ");
121 | likerString.append(comment.comment + "
");
122 | }
123 | }
124 | }
125 |
126 | holder.comments.setText(Html.fromHtml(likerString.toString()));
127 |
128 | imageLoader.DisplayImage(image.standard_resolution, activity, holder.image);
129 |
130 | return vi;
131 | }
132 | }
--------------------------------------------------------------------------------
/src/org/acmelab/andgram2/Utils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011, Mark L. Chang . All rights reserved.
3 | *
4 | * Redistribution and use in source and binary forms, with or without modification, are
5 | * permitted provided that the following conditions are met:
6 | *
7 | * 1. Redistributions of source code must retain the above copyright notice, this list of
8 | * conditions and the following disclaimer.
9 | *
10 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list
11 | * of conditions and the following disclaimer in the documentation and/or other
12 | * materials provided with the distribution.
13 | *
14 | * THIS SOFTWARE IS PROVIDED BY Mark L. Chang ``AS IS'' AND ANY EXPRESS OR IMPLIED
15 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
16 | * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MARK L. CHANG OR
17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
21 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
22 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 | *
24 | * The views and conclusions contained in the software and documentation are those of the
25 | * authors and should not be interpreted as representing official policies, either expressed
26 | * or implied, of Mark L. Chang.
27 | */
28 |
29 | package org.acmelab.andgram2;
30 |
31 | import android.app.Activity;
32 | import android.content.Context;
33 | import android.content.Intent;
34 | import android.content.SharedPreferences;
35 | import android.net.ConnectivityManager;
36 | import android.util.Log;
37 | import android.widget.Toast;
38 | import org.apache.http.HttpEntity;
39 | import org.apache.http.HttpResponse;
40 | import org.apache.http.HttpStatus;
41 | import org.apache.http.NameValuePair;
42 | import org.apache.http.client.entity.UrlEncodedFormEntity;
43 | import org.apache.http.client.methods.HttpDelete;
44 | import org.apache.http.client.methods.HttpGet;
45 | import org.apache.http.client.methods.HttpPost;
46 | import org.apache.http.impl.client.DefaultHttpClient;
47 | import org.apache.http.message.BasicNameValuePair;
48 | import org.apache.http.protocol.HTTP;
49 | import org.json.JSONException;
50 | import org.json.JSONObject;
51 | import org.json.JSONTokener;
52 |
53 | import java.io.*;
54 | import java.util.ArrayList;
55 | import java.util.List;
56 |
57 | public class Utils {
58 |
59 | public static String getAccessToken(Context ctx) {
60 | SharedPreferences sharedPreferences = ctx.getSharedPreferences(Constants.PREFS_NAME, Activity.MODE_PRIVATE);
61 | return(sharedPreferences.getString("access_token", null));
62 | }
63 |
64 | public static boolean isOnline(Context ctx) {
65 | ConnectivityManager cm = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE);
66 | if( cm.getActiveNetworkInfo() == null ) return false;
67 | return cm.getActiveNetworkInfo().isConnectedOrConnecting();
68 | }
69 |
70 | public static String decorateEndpoint(String endpoint, String access_token) {
71 | return endpoint + "?access_token=" + access_token;
72 | }
73 |
74 | public static void CopyStream(InputStream is, OutputStream os)
75 | {
76 | final int buffer_size=1024;
77 | try
78 | {
79 | byte[] bytes=new byte[buffer_size];
80 | for(;;)
81 | {
82 | int count=is.read(bytes, 0, buffer_size);
83 | if(count==-1)
84 | break;
85 | os.write(bytes, 0, count);
86 | }
87 | }
88 | catch(Exception ex){}
89 | }
90 |
91 | public static String getUsername(Context ctx) {
92 | SharedPreferences sharedPreferences = ctx.getSharedPreferences(Constants.PREFS_NAME, Activity.MODE_PRIVATE);
93 | return sharedPreferences.getString("username",null);
94 | }
95 |
96 |
97 | public static JSONObject doRestfulPut(MyHttpClient httpClient, String url,
98 | List postParams, Context ctx) {
99 | HttpPost httpPost = new HttpPost(url);
100 |
101 | // TODO: SSL retries
102 | try {
103 | httpPost.setEntity(new UrlEncodedFormEntity(postParams, HTTP.UTF_8));
104 | HttpResponse httpResponse = httpClient.execute(httpPost);
105 |
106 | // test result code
107 | if( httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK ) {
108 | Log.e(Constants.TAG, "Login HTTP status fail");
109 | return null;
110 | }
111 |
112 | // test json response
113 | HttpEntity httpEntity = httpResponse.getEntity();
114 | if( httpEntity != null ) {
115 | BufferedReader reader = new BufferedReader(new InputStreamReader(httpEntity.getContent(), "UTF-8"));
116 | String json = reader.readLine();
117 | JSONTokener jsonTokener = new JSONTokener(json);
118 | JSONObject jsonObject = new JSONObject(jsonTokener);
119 |
120 | return jsonObject;
121 | } else {
122 | return null;
123 | }
124 | } catch( IOException e ) {
125 | return null;
126 | } catch( JSONException e ) {
127 | return null;
128 | }
129 | }
130 |
131 | public static JSONObject doRestfulGet(MyHttpClient httpClient, String url, Context ctx) {
132 | if( Utils.isOnline(ctx) == false ) {
133 | Toast.makeText(ctx,"No connection to Internet.\nTry again later",Toast.LENGTH_SHORT).show();
134 | Log.i(Constants.TAG, "No internet!");
135 | return null;
136 | }
137 |
138 | // TODO: SSL retries
139 | try {
140 | HttpGet httpGet = new HttpGet(url);
141 | HttpResponse httpResponse = httpClient.execute(httpGet);
142 |
143 | // test result code
144 | if( httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK ) {
145 | return null;
146 | }
147 |
148 | HttpEntity httpEntity = httpResponse.getEntity();
149 |
150 | if( httpEntity != null ) {
151 | BufferedReader reader = new BufferedReader(new InputStreamReader(httpEntity.getContent(), "UTF-8"));
152 | String json = reader.readLine();
153 | JSONTokener jsonTokener = new JSONTokener(json);
154 | JSONObject jsonObject = new JSONObject(jsonTokener);
155 | return jsonObject;
156 | } else {
157 | return null;
158 | }
159 | } catch (Exception e) {
160 | e.printStackTrace();
161 | return null;
162 | }
163 | }
164 |
165 | public static JSONObject doRestfulDelete(MyHttpClient httpClient, String url, Context ctx) {
166 | if( Utils.isOnline(ctx) == false ) {
167 | Toast.makeText(ctx,"No connection to Internet.\nTry again later",Toast.LENGTH_SHORT).show();
168 | Log.i(Constants.TAG, "No internet!");
169 | return null;
170 | }
171 |
172 | // TODO: SSL retries
173 | try {
174 | HttpDelete httpDelete = new HttpDelete(url);
175 | HttpResponse httpResponse = httpClient.execute(httpDelete);
176 |
177 | // test result code
178 | if( httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK ) {
179 | return null;
180 | }
181 |
182 | HttpEntity httpEntity = httpResponse.getEntity();
183 |
184 | if( httpEntity != null ) {
185 | BufferedReader reader = new BufferedReader(new InputStreamReader(httpEntity.getContent(), "UTF-8"));
186 | String json = reader.readLine();
187 | JSONTokener jsonTokener = new JSONTokener(json);
188 | JSONObject jsonObject = new JSONObject(jsonTokener);
189 | return jsonObject;
190 | } else {
191 | return null;
192 | }
193 | } catch (Exception e) {
194 | e.printStackTrace();
195 | return null;
196 | }
197 | }
198 | }
--------------------------------------------------------------------------------
/.idea/uiDesigner.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 |
26 |
27 |
28 |
29 | -
30 |
31 |
32 |
33 |
34 |
35 | -
36 |
37 |
38 |
39 |
40 |
41 | -
42 |
43 |
44 |
45 |
46 | -
47 |
48 |
49 |
50 |
51 | -
52 |
53 |
54 |
55 |
56 | -
57 |
58 |
59 |
60 |
61 | -
62 |
63 |
64 |
65 |
66 | -
67 |
68 |
69 |
70 |
71 | -
72 |
73 |
74 | -
75 |
76 |
77 |
78 |
79 | -
80 |
81 |
82 |
83 |
84 | -
85 |
86 |
87 |
88 |
89 | -
90 |
91 |
92 |
93 |
94 | -
95 |
96 |
97 |
98 |
99 | -
100 |
101 |
102 | -
103 |
104 |
105 | -
106 |
107 |
108 | -
109 |
110 |
111 | -
112 |
113 |
114 |
115 |
116 | -
117 |
118 |
119 | -
120 |
121 |
122 |
123 |
124 |
125 |
126 |
--------------------------------------------------------------------------------
/src/org/acmelab/andgram2/LoginActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011, Mark L. Chang . All rights reserved.
3 | *
4 | * Redistribution and use in source and binary forms, with or without modification, are
5 | * permitted provided that the following conditions are met:
6 | *
7 | * 1. Redistributions of source code must retain the above copyright notice, this list of
8 | * conditions and the following disclaimer.
9 | *
10 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list
11 | * of conditions and the following disclaimer in the documentation and/or other
12 | * materials provided with the distribution.
13 | *
14 | * THIS SOFTWARE IS PROVIDED BY Mark L. Chang ``AS IS'' AND ANY EXPRESS OR IMPLIED
15 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
16 | * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MARK L. CHANG OR
17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
21 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
22 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 | *
24 | * The views and conclusions contained in the software and documentation are those of the
25 | * authors and should not be interpreted as representing official policies, either expressed
26 | * or implied, of Mark L. Chang.
27 | */
28 |
29 | package org.acmelab.andgram2;
30 |
31 | import android.app.Activity;
32 | import android.content.Intent;
33 | import android.content.SharedPreferences;
34 | import android.net.Uri;
35 | import android.os.Bundle;
36 | import android.util.Log;
37 | import android.view.View;
38 | import android.widget.Toast;
39 | import org.apache.http.HttpEntity;
40 | import org.apache.http.HttpResponse;
41 | import org.apache.http.HttpStatus;
42 | import org.apache.http.NameValuePair;
43 | import org.apache.http.client.HttpClient;
44 | import org.apache.http.client.entity.UrlEncodedFormEntity;
45 | import org.apache.http.client.methods.HttpPost;
46 | import org.apache.http.message.BasicNameValuePair;
47 | import org.json.JSONException;
48 | import org.json.JSONObject;
49 | import org.json.JSONTokener;
50 |
51 | import javax.net.ssl.SSLException;
52 | import java.io.BufferedReader;
53 | import java.io.IOException;
54 | import java.io.InputStreamReader;
55 | import java.util.*;
56 |
57 | /**
58 | * Created by IntelliJ IDEA.
59 | * User: mchang
60 | * Date: 4/16/11
61 | * Time: 12:54 AM
62 | * Prompts the user to login. Discusses OAuth.
63 | */
64 | public class LoginActivity extends Activity {
65 | public void onCreate(Bundle savedInstanceState) {
66 | super.onCreate(savedInstanceState);
67 | setContentView(R.layout.login_layout);
68 |
69 | Log.i(Constants.TAG, "LoginActivity onCreate");
70 | }
71 |
72 | public void doAuth(View view) {
73 | Log.i(Constants.TAG, "LoginActivity doAuth");
74 | // == STEP ONE: DIRECT YOUR USER TO OUR AUTHORIZATION URL
75 | // TODO: use a webview rather than the browser
76 | Intent webAuthIntent = new Intent(Intent.ACTION_VIEW);
77 | webAuthIntent.setData(Uri.parse(getAuthorizationUrl()));
78 | startActivity(webAuthIntent);
79 | }
80 |
81 | private String getAuthorizationUrl() {
82 | StringBuilder authorizationUrl = new StringBuilder();
83 | authorizationUrl.append(Constants.AUTHORIZATION_URL);
84 | authorizationUrl.append("?client_id=" + Credentials.CLIENT_ID);
85 | authorizationUrl.append("&redirect_uri="+ Constants.REDIRECT_URI);
86 | authorizationUrl.append("&response_type=code");
87 | authorizationUrl.append("&display=touch");
88 | authorizationUrl.append("&scope=likes+comments+relationships");
89 |
90 | return authorizationUrl.toString();
91 | }
92 |
93 | @Override
94 | public void onResume() {
95 | super.onResume();
96 |
97 | Log.i(Constants.TAG, "LoginActivity onResume");
98 |
99 | Uri uri = this.getIntent().getData();
100 | if( uri != null ) {
101 | // oauth callback, so we update or create our credentials
102 | String access_code = retrieveAccessCode(uri);
103 | Map oauthMap = requestAccessToken(access_code);
104 |
105 | if( oauthMap != null ) {
106 | SharedPreferences sharedPreferences = getSharedPreferences(Constants.PREFS_NAME, MODE_PRIVATE);
107 | SharedPreferences.Editor editor = sharedPreferences.edit();
108 | editor.clear();
109 |
110 | // stash in preferences
111 | Iterator it = oauthMap.entrySet().iterator();
112 | while (it.hasNext()) {
113 | Map.Entry pairs = (Map.Entry)it.next();
114 | editor.putString((String)pairs.getKey(), (String)pairs.getValue());
115 | System.out.println(pairs.getKey() + " = " + pairs.getValue());
116 | }
117 | editor.commit();
118 |
119 | // TODO: fix this
120 | Toast.makeText(this, "Login successful!", Toast.LENGTH_LONG).show();
121 |
122 | // start main activity
123 | Intent homeIntent = new Intent(LoginActivity.this, HomeActivity.class);
124 | homeIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_SINGLE_TOP);
125 | startActivity(homeIntent);
126 |
127 | } else {
128 | // TODO: better error message
129 | }
130 | }
131 | }
132 |
133 | private String retrieveAccessCode(Uri uri) {
134 | if(uri != null) {
135 | String access_code = uri.getQueryParameter("code");
136 | String error = uri.getQueryParameter("error");
137 | String error_reason = uri.getQueryParameter("error_reason");
138 | String error_description = uri.getQueryParameter("error_description");
139 |
140 | if( error != null ) {
141 | // didn't actually work
142 | Log.e(Constants.TAG, "OAuth access code error.");
143 | Log.e(Constants.TAG, error);
144 | Log.e(Constants.TAG, error_reason);
145 | Log.e(Constants.TAG, error_description);
146 | return null;
147 | } else {
148 | return access_code;
149 | }
150 | } else {
151 | return null;
152 | }
153 | }
154 |
155 | private Map requestAccessToken(String access_code) {
156 | HttpResponse httpResponse = null;
157 | HttpEntity httpEntity = null;
158 | HashMap oauthMap = new HashMap();
159 |
160 | HttpPost httpPost = new HttpPost(Constants.ACCESS_TOKEN_ENDPOINT);
161 |
162 | List postParams = new ArrayList();
163 | postParams.add(new BasicNameValuePair("client_id", Credentials.CLIENT_ID));
164 | postParams.add(new BasicNameValuePair("client_secret", Credentials.CLIENT_SECRET));
165 | postParams.add(new BasicNameValuePair("grant_type", "authorization_code"));
166 | postParams.add(new BasicNameValuePair("redirect_uri", Constants.REDIRECT_URI));
167 | postParams.add(new BasicNameValuePair("code", access_code));
168 |
169 | // post it
170 | boolean success = false;
171 | int fail_count = 0;
172 | while( success == false ) {
173 | try {
174 | HttpClient httpClient = new MyHttpClient(getApplicationContext());
175 | httpPost.setEntity(new UrlEncodedFormEntity(postParams));
176 | httpResponse = httpClient.execute(httpPost);
177 |
178 | // test result code
179 | if( httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK ) {
180 | Toast.makeText(this, "Authorization error", Toast.LENGTH_SHORT).show();
181 | Log.e(Constants.TAG, "Error requesting oauth access token.");
182 | return null;
183 | }
184 |
185 | httpEntity = httpResponse.getEntity();
186 | success = true;
187 | } catch( SSLException sslException ) {
188 | success = false;
189 | fail_count++;
190 | if( fail_count > 10 ) {
191 | Toast.makeText(this, "SSL exception.\nMost times, you can simply try again.", Toast.LENGTH_SHORT).show();
192 | Log.e(Constants.TAG, "SSL exception.");
193 | return null;
194 | }
195 | } catch (IOException ioException) {
196 | Toast.makeText(this, "Authorization error", Toast.LENGTH_SHORT).show();
197 | Log.e(Constants.TAG, "Error requesting oauth access token.");
198 | return null;
199 | }
200 | }
201 |
202 | Log.i(Constants.TAG, "Got access token response!");
203 |
204 | // parse JSON response
205 | try {
206 | if( httpEntity != null ) {
207 | BufferedReader reader = new BufferedReader(new InputStreamReader(httpEntity.getContent(), "UTF-8"));
208 | String json = reader.readLine();
209 | JSONTokener jsonTokener = new JSONTokener(json);
210 | JSONObject jsonObject = new JSONObject(jsonTokener);
211 | JSONObject userObject = jsonObject.getJSONObject("user");
212 |
213 | // stash it in the return hashmap
214 | oauthMap.put("access_token", jsonObject.getString("access_token"));
215 | oauthMap.put("user_id", userObject.getString("id"));
216 | oauthMap.put("username", userObject.getString("username"));
217 | oauthMap.put("full_name", userObject.getString("full_name"));
218 | oauthMap.put("profile_picture_url", userObject.getString("profile_picture"));
219 |
220 | return oauthMap;
221 |
222 | }
223 | } catch( JSONException jsonException ) {
224 | Toast.makeText(this, "Error parsing authorization response.", Toast.LENGTH_SHORT).show();
225 | Log.e(Constants.TAG, "Error parsing oauth access token JSON response.");
226 | return null;
227 | } catch( IOException ioException ) {
228 | Toast.makeText(this, "I/O error parsing authorization response.", Toast.LENGTH_SHORT).show();
229 | Log.e(Constants.TAG, "I/O error parsing oauth access token JSON response.");
230 | return null;
231 | }
232 |
233 | return null;
234 | }
235 |
236 |
237 | }
--------------------------------------------------------------------------------
/src/org/acmelab/andgram2/ImageLoader.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011, Mark L. Chang . All rights reserved.
3 | *
4 | * Redistribution and use in source and binary forms, with or without modification, are
5 | * permitted provided that the following conditions are met:
6 | *
7 | * 1. Redistributions of source code must retain the above copyright notice, this list of
8 | * conditions and the following disclaimer.
9 | *
10 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list
11 | * of conditions and the following disclaimer in the documentation and/or other
12 | * materials provided with the distribution.
13 | *
14 | * THIS SOFTWARE IS PROVIDED BY Mark L. Chang ``AS IS'' AND ANY EXPRESS OR IMPLIED
15 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
16 | * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MARK L. CHANG OR
17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
21 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
22 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 | *
24 | * The views and conclusions contained in the software and documentation are those of the
25 | * authors and should not be interpreted as representing official policies, either expressed
26 | * or implied, of Mark L. Chang.
27 | */
28 |
29 | package org.acmelab.andgram2;
30 |
31 | import android.app.Activity;
32 | import android.content.Context;
33 | import android.graphics.Bitmap;
34 | import android.graphics.BitmapFactory;
35 | import android.util.Log;
36 | import android.widget.ImageView;
37 |
38 | import java.io.*;
39 | import java.lang.ref.SoftReference;
40 | import java.net.URL;
41 | import java.util.*;
42 |
43 | public class ImageLoader {
44 | private static final boolean debug = false;
45 |
46 | // the simplest in-memory cache implementation.
47 | // This should be replaced with something like SoftReference or
48 | // BitmapOptions.inPurgeable(since 1.6)
49 | private HashMap> cache=new HashMap>();
50 |
51 | final int stub_id = R.drawable.stub;
52 | private File cacheDir;
53 |
54 | public ImageLoader(Context context){
55 | // Make the background thread low priority. This way it will not affect the UI performance
56 | photoLoaderThread.setPriority(Thread.NORM_PRIORITY-1);
57 |
58 | //Find the dir to save cached images
59 | if (android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED))
60 | cacheDir=new File(android.os.Environment.getExternalStorageDirectory(),Constants.OUTPUT_DIR);
61 | else
62 | cacheDir=context.getCacheDir();
63 | if(!cacheDir.exists())
64 | cacheDir.mkdirs();
65 | }
66 |
67 | public void DisplayImage(String url, Activity activity, ImageView imageView)
68 | {
69 | if(debug) Log.i(Constants.TAG, "Displaying image: " + url);
70 | if(cache.containsKey(url)) {
71 | if(debug) Log.i(Constants.TAG, "Found cached image " + url);
72 | SoftReference softRef = cache.get(url);
73 | Bitmap bitmap = softRef.get();
74 | if( bitmap == null ) {
75 | if(debug) Log.i(Constants.TAG, "But re-queuing GC'ed image: " + url);
76 | // maybe? : cache.remove(softRef);
77 | queuePhoto(url, activity, imageView);
78 | imageView.setImageResource(stub_id);
79 | } else {
80 | if(debug) Log.i(Constants.TAG, "Setting image bitmap");
81 | imageView.setImageBitmap(softRef.get());
82 | if( softRef.get() == null ) {
83 | if(debug) Log.e(Constants.TAG, "Null bitmap: " + url);
84 | }
85 | }
86 | }
87 | else
88 | {
89 | if(debug) Log.i(Constants.TAG, "Not in cache, queueing " + url);
90 | queuePhoto(url, activity, imageView);
91 | imageView.setImageResource(stub_id);
92 | }
93 | }
94 |
95 | private void queuePhoto(String url, Activity activity, ImageView imageView)
96 | {
97 | if(debug) Log.i(Constants.TAG, "Queueing: " + url);
98 |
99 | // This ImageView may be used for other images before.
100 | // So there may be some old tasks in the queue. We need to discard them.
101 | photosQueue.Clean(imageView);
102 | PhotoToLoad p=new PhotoToLoad(url, imageView);
103 | synchronized( photosQueue.photosToLoad ){
104 | photosQueue.photosToLoad.add(p);
105 | photosQueue.photosToLoad.notifyAll();
106 | }
107 |
108 | //start thread if it's not started yet
109 | if(photoLoaderThread.getState()==Thread.State.NEW)
110 | photoLoaderThread.start();
111 | }
112 |
113 | private Bitmap getBitmap(String url)
114 | {
115 | // I identify images by hashcode. Not a perfect solution, good for the demo.
116 | if(debug) Log.i(Constants.TAG, "Getting image in thread: " + url);
117 | String filename=String.valueOf(url.hashCode());
118 | File f=new File(cacheDir, filename);
119 |
120 | //from SD cache
121 | Bitmap b = decodeFile(f);
122 | if(b != null) {
123 | if(debug) Log.i(Constants.TAG, "Found in cache." );
124 | return b;
125 | }
126 |
127 |
128 | //from web
129 | try {
130 | if(debug) Log.i(Constants.TAG, "Downloading from web");
131 | Bitmap bitmap = null;
132 | InputStream is = new URL(url).openStream();
133 | OutputStream os = new FileOutputStream(f);
134 | Utils.CopyStream(is, os);
135 | os.close();
136 | is.close();
137 | bitmap = decodeFile(f);
138 | return bitmap;
139 | } catch (Exception ex){
140 | ex.printStackTrace();
141 | return null;
142 | }
143 | }
144 |
145 | //decodes image and scales it to reduce memory consumption
146 | private Bitmap decodeFile(File f){
147 | try {
148 | if(debug) Log.i(Constants.TAG, "Decoding image: " + f.toString());
149 | Bitmap b = BitmapFactory.decodeStream(new FileInputStream(f));
150 | if(debug) Log.i(Constants.TAG, "decoded a real bitmap!");
151 | return(b);
152 | } catch (FileNotFoundException e) {
153 | if(debug) Log.i(Constants.TAG, "File not found");
154 | return null;
155 | } catch (Exception ex) {
156 | if(debug) Log.e(Constants.TAG, "Some other shit failed in decodeFile");
157 | ex.printStackTrace();
158 | return null;
159 | }
160 | }
161 |
162 | // Task for the queue
163 | private class PhotoToLoad
164 | {
165 | public String url;
166 | public ImageView imageView;
167 | public PhotoToLoad(String u, ImageView i){
168 | url = u;
169 | imageView = i;
170 | }
171 | }
172 |
173 | PhotosQueue photosQueue = new PhotosQueue();
174 |
175 | public void stopThread()
176 | {
177 | photoLoaderThread.interrupt();
178 | }
179 |
180 | // stores feedList of photos to download
181 | class PhotosQueue
182 | {
183 | // private Stack photosToLoad = new Stack();
184 | private LinkedList photosToLoad = new LinkedList();
185 |
186 | //removes all instances of this ImageView
187 | public void Clean(ImageView image)
188 | {
189 | for(int j=0 ;j(bmp));
220 | Object tag = photoToLoad.imageView.getTag();
221 | if(tag != null && ((String)tag).equals(photoToLoad.url)){
222 | BitmapDisplayer bd = new BitmapDisplayer(bmp, photoToLoad.imageView);
223 | Activity a = (Activity)photoToLoad.imageView.getContext();
224 | a.runOnUiThread(bd);
225 | } else {
226 | if(debug) Log.i(Constants.TAG, "Got image w/o tag!");
227 | }
228 | }
229 | }
230 | if(Thread.interrupted())
231 | break;
232 | }
233 | } catch (InterruptedException e) {
234 | //allow thread to exit
235 | }
236 | }
237 | }
238 |
239 | PhotosLoader photoLoaderThread = new PhotosLoader();
240 |
241 | // Used to display bitmap in the UI thread
242 | class BitmapDisplayer implements Runnable
243 | {
244 | Bitmap bitmap;
245 | ImageView imageView;
246 |
247 | public BitmapDisplayer(Bitmap b, ImageView i) {
248 | bitmap = b;
249 | imageView = i;
250 | }
251 |
252 | public void run()
253 | {
254 | if(bitmap != null)
255 | imageView.setImageBitmap(bitmap);
256 | else
257 | imageView.setImageResource(stub_id);
258 | }
259 | }
260 |
261 | public void clearCache() {
262 | // clear memory cache
263 | cache.clear();
264 |
265 | //clear SD cache
266 | File[] files=cacheDir.listFiles();
267 | for(File f:files)
268 | f.delete();
269 | }
270 |
271 | }
272 |
--------------------------------------------------------------------------------
/src/org/acmelab/andgram2/PopularActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011, Mark L. Chang . All rights reserved.
3 | *
4 | * Redistribution and use in source and binary forms, with or without modification, are
5 | * permitted provided that the following conditions are met:
6 | *
7 | * 1. Redistributions of source code must retain the above copyright notice, this list of
8 | * conditions and the following disclaimer.
9 | *
10 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list
11 | * of conditions and the following disclaimer in the documentation and/or other
12 | * materials provided with the distribution.
13 | *
14 | * THIS SOFTWARE IS PROVIDED BY Mark L. Chang ``AS IS'' AND ANY EXPRESS OR IMPLIED
15 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
16 | * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MARK L. CHANG OR
17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
21 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
22 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 | *
24 | * The views and conclusions contained in the software and documentation are those of the
25 | * authors and should not be interpreted as representing official policies, either expressed
26 | * or implied, of Mark L. Chang.
27 | */
28 |
29 | package org.acmelab.andgram2;
30 |
31 | import android.app.Activity;
32 | import android.content.Intent;
33 | import android.os.AsyncTask;
34 | import android.os.Bundle;
35 | import android.util.Log;
36 | import android.view.View;
37 | import android.widget.AdapterView;
38 | import android.widget.GridView;
39 | import android.widget.Toast;
40 | import com.markupartist.android.widget.ActionBar;
41 | import org.apache.http.HttpEntity;
42 | import org.apache.http.HttpResponse;
43 | import org.apache.http.HttpStatus;
44 | import org.apache.http.client.methods.HttpGet;
45 | import org.json.JSONArray;
46 | import org.json.JSONException;
47 | import org.json.JSONObject;
48 | import org.json.JSONTokener;
49 |
50 | import javax.net.ssl.SSLException;
51 | import java.io.BufferedReader;
52 | import java.io.IOException;
53 | import java.io.InputStreamReader;
54 | import java.text.SimpleDateFormat;
55 | import java.util.ArrayList;
56 | import java.util.Date;
57 |
58 | public class PopularActivity extends Activity {
59 |
60 | private static final boolean debug = true;
61 |
62 | private String sourceUrl;
63 |
64 | GridView grid;
65 | LazyGridAdapter adapter;
66 | ArrayList instagramImageList;
67 | ActionBar actionBar;
68 |
69 | @Override
70 | public void onCreate(Bundle savedInstanceState) {
71 | super.onCreate(savedInstanceState);
72 |
73 | setContentView(R.layout.popular_layout);
74 |
75 | Bundle extras = getIntent().getExtras();
76 | sourceUrl = extras.getString("endpoint");
77 | grid = (GridView)findViewById(R.id.gridview);
78 | int titleId = extras.getInt("title");
79 | String title = getResources().getString(titleId);
80 |
81 | actionBar = (ActionBar) findViewById(R.id.popular_actionbar);
82 | actionBar.setTitle(title);
83 |
84 | Intent homeIntent = new Intent(getApplicationContext(), HomeActivity.class);
85 | homeIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
86 |
87 | actionBar.addAction(new RefreshAction());
88 | final ActionBar.Action goHomeAction = new ActionBar.IntentAction(this,
89 | homeIntent, R.drawable.ic_title_home);
90 | actionBar.addAction(goHomeAction);
91 |
92 | // set that feedList to background downloader
93 | instagramImageList = new ArrayList();
94 | adapter = new LazyGridAdapter(this, instagramImageList);
95 | grid.setAdapter(adapter);
96 |
97 | // make a click listener
98 | grid.setOnItemClickListener(new AdapterView.OnItemClickListener() {
99 | public void onItemClick(AdapterView> parent, View v, int position, long id) {
100 | InstagramImage imageToShow = instagramImageList.get(position);
101 | Intent imageDetailIntent = new Intent(PopularActivity.this, ImageDetailActivity.class);
102 | imageDetailIntent.putExtra("id", imageToShow.id);
103 | startActivity(imageDetailIntent);
104 | }
105 | });
106 |
107 | new FetchActivity().execute();
108 | }
109 |
110 | private void refresh() {
111 | instagramImageList.clear();
112 | adapter.notifyDataSetChanged();
113 | new FetchActivity().execute();
114 | }
115 |
116 |
117 | @Override
118 | public void onDestroy()
119 | {
120 | adapter.imageLoader.stopThread();
121 | grid.setAdapter(null);
122 | super.onDestroy();
123 | }
124 |
125 | public void clearCache(View view) {
126 | adapter.imageLoader.clearCache();
127 | adapter.notifyDataSetChanged();
128 | }
129 |
130 | private class FetchActivity extends AsyncTask {
131 | protected void onPreExecute() {
132 | actionBar.setProgressBarVisibility(View.VISIBLE);
133 | }
134 |
135 | protected void onPostExecute(Boolean result) {
136 | actionBar.setProgressBarVisibility(View.GONE);
137 |
138 | if(result) {
139 | adapter.notifyDataSetChanged();
140 | }
141 | }
142 |
143 | protected void onProgressUpdate(String... toastText) {
144 | Toast.makeText(PopularActivity.this, toastText[0], Toast.LENGTH_SHORT).show();
145 | if(debug) Log.e(Constants.TAG, toastText[0]);
146 | }
147 |
148 | protected Boolean doInBackground(Void... voids) {
149 |
150 | if(debug) Log.i(Constants.TAG, "PopularActivity FETCH");
151 |
152 | HttpEntity httpEntity = null;
153 |
154 | if( Utils.isOnline(getApplicationContext()) == false ) {
155 | publishProgress("No connection to Internet.\nTry again later");
156 | return false;
157 | }
158 |
159 | boolean success = false;
160 | int fail_count = 0;
161 | while( success == false ) {
162 | try {
163 | MyHttpClient httpClient = new MyHttpClient(getApplicationContext());
164 | HttpGet httpGet = new HttpGet(sourceUrl);
165 | HttpResponse httpResponse = httpClient.execute(httpGet);
166 |
167 | // test result code
168 | if( httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK ) {
169 | publishProgress("Login failed.");
170 | return false;
171 | }
172 |
173 | httpEntity = httpResponse.getEntity();
174 | success = true;
175 | } catch( SSLException sslException ) {
176 | if(debug) Log.e(Constants.TAG, "SSL Exception: " + fail_count);
177 | success = false;
178 | fail_count++;
179 | if( fail_count > 10 ) {
180 | publishProgress("SSL exception.\nMost times, you can simply try again.");
181 | return false;
182 | }
183 | } catch (IOException ioException) {
184 | publishProgress("Authorization error");
185 | return false;
186 | }
187 | }
188 |
189 | try {
190 | if( httpEntity != null ) {
191 | BufferedReader reader = new BufferedReader(new InputStreamReader(httpEntity.getContent(), "UTF-8"));
192 | String json = reader.readLine();
193 | JSONTokener jsonTokener = new JSONTokener(json);
194 | JSONObject jsonObject = new JSONObject(jsonTokener);
195 |
196 | // parse the activity feed
197 | JSONArray data = jsonObject.getJSONArray("data");
198 |
199 | // get image URLs and commentary
200 | for( int i=0; i< data.length(); i++ ) {
201 | // create a new instance
202 | InstagramImage instagramImage = new InstagramImage();
203 |
204 | // image
205 | JSONObject image = (JSONObject)data.get(i);
206 | JSONObject images = image.getJSONObject("images");
207 | JSONObject thumbnailImage = images.getJSONObject("thumbnail");
208 | JSONObject lowResolutionImage = images.getJSONObject("low_resolution");
209 | JSONObject standardResolutionImage = images.getJSONObject("standard_resolution");
210 | instagramImage.id = image.getString("id");
211 | instagramImage.user_has_liked = image.getBoolean("user_has_liked");
212 | instagramImage.permalink = image.getString("link");
213 |
214 | // permalinks
215 | instagramImage.thumbnail = thumbnailImage.getString("url");
216 | instagramImage.low_resolution = lowResolutionImage.getString("url");
217 | instagramImage.standard_resolution = standardResolutionImage.getString("url");
218 |
219 | // user
220 | JSONObject user = image.getJSONObject("user");
221 | instagramImage.username = user.getString("username");
222 | instagramImage.user_id = user.getString("id");
223 | instagramImage.full_name = user.getString("full_name");
224 |
225 | // date taken_at
226 | Long dateLong = image.getLong("created_time");
227 | SimpleDateFormat formatter = new SimpleDateFormat("MMMM d, yyyy HH:mm");
228 | instagramImage.taken_at = formatter.format(new Date(dateLong * 1000L));
229 | instagramImage.taken_time = dateLong * 1000L;
230 |
231 | // comments
232 | instagramImage.comment_count = image.getJSONObject("comments").getInt("count");
233 | JSONArray comments = image.getJSONObject("comments").getJSONArray("data");
234 | if( comments != null ) {
235 | ArrayList commentList = new ArrayList();
236 | for( int c=0; c < comments.length(); c++ ) {
237 | JSONObject comment = comments.getJSONObject(c);
238 | JSONObject from = comment.getJSONObject("from");
239 | commentList.add(new Comment(from.getString("username"),
240 | comment.getString("text")));
241 | }
242 | instagramImage.comment_list = commentList;
243 | }
244 |
245 | // caption
246 |
247 | try {
248 | JSONObject caption = image.getJSONObject("caption");
249 | if( caption != null ) {
250 | instagramImage.caption = caption.getString("text");
251 | }
252 | } catch (JSONException e) {}
253 |
254 | // likers
255 | try {
256 | instagramImage.liker_count = image.getJSONObject("likes").getInt("count");
257 | JSONArray likes = image.getJSONObject("likes").getJSONArray("data");
258 | if( likes != null ) {
259 | ArrayList likerList = new ArrayList();
260 | if( likes.length() > 0 ) {
261 | for( int l=0; l < likes.length(); l++ ) {
262 | JSONObject like = likes.getJSONObject(l);
263 | likerList.add(like.getString("username"));
264 | }
265 | instagramImage.liker_list = likerList;
266 | }
267 | }
268 | } catch( JSONException j ) {}
269 |
270 | instagramImageList.add(instagramImage);
271 | }
272 |
273 | return true;
274 | } else {
275 | publishProgress("Improper data returned from Instagram");
276 | if(debug) Log.e(Constants.TAG, "instagram returned bad data");
277 | return false;
278 | }
279 | } catch (Exception e) {
280 | e.printStackTrace();
281 | return false;
282 | }
283 | }
284 | }
285 |
286 | private class RefreshAction implements ActionBar.Action {
287 |
288 | public int getDrawable() {
289 | return R.drawable.ic_title_refresh;
290 | }
291 |
292 | public void performAction(View view) {
293 | refresh();
294 | }
295 | }
296 | }
--------------------------------------------------------------------------------
/src/org/acmelab/andgram2/FeedActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011, Mark L. Chang . All rights reserved.
3 | *
4 | * Redistribution and use in source and binary forms, with or without modification, are
5 | * permitted provided that the following conditions are met:
6 | *
7 | * 1. Redistributions of source code must retain the above copyright notice, this list of
8 | * conditions and the following disclaimer.
9 | *
10 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list
11 | * of conditions and the following disclaimer in the documentation and/or other
12 | * materials provided with the distribution.
13 | *
14 | * THIS SOFTWARE IS PROVIDED BY Mark L. Chang ``AS IS'' AND ANY EXPRESS OR IMPLIED
15 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
16 | * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MARK L. CHANG OR
17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
21 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
22 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 | *
24 | * The views and conclusions contained in the software and documentation are those of the
25 | * authors and should not be interpreted as representing official policies, either expressed
26 | * or implied, of Mark L. Chang.
27 | */
28 |
29 | package org.acmelab.andgram2;
30 |
31 | import android.app.Activity;
32 | import android.app.AlertDialog;
33 | import android.content.DialogInterface;
34 | import android.content.Intent;
35 | import android.os.AsyncTask;
36 | import android.os.Bundle;
37 | import android.util.Log;
38 | import android.view.View;
39 | import android.widget.AdapterView;
40 | import android.widget.EditText;
41 | import android.widget.ListView;
42 | import android.widget.Toast;
43 | import com.markupartist.android.widget.ActionBar;
44 | import org.apache.http.HttpEntity;
45 | import org.apache.http.HttpResponse;
46 | import org.apache.http.HttpStatus;
47 | import org.apache.http.NameValuePair;
48 | import org.apache.http.client.methods.HttpGet;
49 | import org.apache.http.message.BasicNameValuePair;
50 | import org.json.JSONArray;
51 | import org.json.JSONException;
52 | import org.json.JSONObject;
53 | import org.json.JSONTokener;
54 |
55 | import javax.net.ssl.SSLException;
56 | import java.io.BufferedReader;
57 | import java.io.IOException;
58 | import java.io.InputStreamReader;
59 | import java.text.SimpleDateFormat;
60 | import java.util.ArrayList;
61 | import java.util.Date;
62 | import java.util.List;
63 |
64 | public class FeedActivity extends Activity {
65 | private static final boolean debug = true;
66 | private String sourceUrl;
67 |
68 | ListView feedList;
69 | LazyListAdapter adapter;
70 | ArrayList instagramImageList;
71 | ActionBar actionBar;
72 | MyHttpClient httpClient;
73 |
74 | @Override
75 | public void onCreate(Bundle savedInstanceState) {
76 | super.onCreate(savedInstanceState);
77 |
78 | if(debug) Log.i(Constants.TAG, "FeedActivity onCreate");
79 | setContentView(R.layout.feed_layout);
80 |
81 | Bundle extras = getIntent().getExtras();
82 | sourceUrl = extras.getString("endpoint");
83 | int titleId = extras.getInt("title");
84 | String title = getResources().getString(titleId);
85 |
86 | actionBar = (ActionBar) findViewById(R.id.feedActionbar);
87 | actionBar.setTitle(title);
88 |
89 | Intent homeIntent = new Intent(getApplicationContext(), HomeActivity.class);
90 | homeIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
91 |
92 | actionBar.addAction(new RefreshAction());
93 | final ActionBar.Action goHomeAction = new ActionBar.IntentAction(this,
94 | homeIntent, R.drawable.ic_title_home);
95 | actionBar.addAction(goHomeAction);
96 |
97 | feedList = (ListView)findViewById(R.id.feedList);
98 | feedList.setOnItemClickListener(itemClickListener);
99 |
100 | // set that list to background downloader
101 | instagramImageList = new ArrayList();
102 | adapter = new LazyListAdapter(this, instagramImageList);
103 | feedList.setAdapter(adapter);
104 | new FetchActivity().execute();
105 | }
106 |
107 | private void refresh() {
108 | instagramImageList.clear();
109 | adapter.notifyDataSetChanged();
110 | new FetchActivity().execute();
111 | }
112 |
113 |
114 | @Override
115 | public void onDestroy()
116 | {
117 | adapter.imageLoader.stopThread();
118 | feedList.setAdapter(null);
119 | super.onDestroy();
120 | }
121 |
122 | public void clearCache(View view) {
123 | adapter.imageLoader.clearCache();
124 | adapter.notifyDataSetChanged();
125 | }
126 |
127 | private class FetchActivity extends AsyncTask {
128 | protected void onPreExecute() {
129 | actionBar.setProgressBarVisibility(View.VISIBLE);
130 | }
131 |
132 | protected void onPostExecute(Boolean result) {
133 | actionBar.setProgressBarVisibility(View.GONE);
134 |
135 | if(result) {
136 | adapter.notifyDataSetChanged();
137 | }
138 | }
139 |
140 | protected void onProgressUpdate(String... toastText) {
141 | Toast.makeText(FeedActivity.this, toastText[0], Toast.LENGTH_SHORT).show();
142 | if(debug) Log.e(Constants.TAG, toastText[0]);
143 | }
144 |
145 | protected Boolean doInBackground(Void... voids) {
146 |
147 | if(debug) Log.i(Constants.TAG, "PopularActivity FETCH");
148 |
149 | HttpEntity httpEntity = null;
150 |
151 | if( Utils.isOnline(getApplicationContext()) == false ) {
152 | publishProgress("No connection to Internet.\nTry again later");
153 | return false;
154 | }
155 |
156 | boolean success = false;
157 | int fail_count = 0;
158 | while( success == false ) {
159 | try {
160 | httpClient = new MyHttpClient(getApplicationContext());
161 | HttpGet httpGet = new HttpGet(sourceUrl);
162 | HttpResponse httpResponse = httpClient.execute(httpGet);
163 |
164 | // test result code
165 | if( httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK ) {
166 | publishProgress("Login failed.");
167 | return false;
168 | }
169 |
170 | httpEntity = httpResponse.getEntity();
171 | success = true;
172 | } catch( SSLException sslException ) {
173 | if(debug) Log.e(Constants.TAG, "SSL Exception: " + fail_count);
174 | success = false;
175 | fail_count++;
176 | if( fail_count > 10 ) {
177 | publishProgress("SSL exception.\nMost times, you can simply try again.");
178 | return false;
179 | }
180 | } catch (IOException ioException) {
181 | publishProgress("Authorization error");
182 | return false;
183 | }
184 | }
185 |
186 | try {
187 | if( httpEntity != null ) {
188 | BufferedReader reader = new BufferedReader(new InputStreamReader(httpEntity.getContent(), "UTF-8"));
189 | String json = reader.readLine();
190 | JSONTokener jsonTokener = new JSONTokener(json);
191 | JSONObject jsonObject = new JSONObject(jsonTokener);
192 |
193 | // parse the activity feed
194 | JSONArray data = jsonObject.getJSONArray("data");
195 |
196 | // get image URLs and commentary
197 | for( int i=0; i< data.length(); i++ ) {
198 | // create a new instance
199 | InstagramImage instagramImage = new InstagramImage();
200 |
201 | // image
202 | JSONObject image = (JSONObject)data.get(i);
203 | JSONObject images = image.getJSONObject("images");
204 | JSONObject thumbnailImage = images.getJSONObject("thumbnail");
205 | JSONObject lowResolutionImage = images.getJSONObject("low_resolution");
206 | JSONObject standardResolutionImage = images.getJSONObject("standard_resolution");
207 | instagramImage.id = image.getString("id");
208 | instagramImage.permalink = image.getString("link");
209 |
210 | instagramImage.user_has_liked = image.getBoolean("user_has_liked");
211 |
212 | // permalinks
213 | instagramImage.thumbnail = thumbnailImage.getString("url");
214 | instagramImage.low_resolution = lowResolutionImage.getString("url");
215 | instagramImage.standard_resolution = standardResolutionImage.getString("url");
216 |
217 | // user
218 | JSONObject user = image.getJSONObject("user");
219 | instagramImage.username = user.getString("username");
220 | instagramImage.user_id = user.getString("id");
221 | instagramImage.full_name = user.getString("full_name");
222 |
223 | // date taken_at
224 | Long dateLong = image.getLong("created_time");
225 | SimpleDateFormat formatter = new SimpleDateFormat("MMMM d, yyyy HH:mm");
226 | instagramImage.taken_at = formatter.format(new Date(dateLong * 1000L));
227 | instagramImage.taken_time = dateLong * 1000L;
228 |
229 | // comments
230 | instagramImage.comment_count = image.getJSONObject("comments").getInt("count");
231 | JSONArray comments = image.getJSONObject("comments").getJSONArray("data");
232 | if( comments != null ) {
233 | ArrayList commentList = new ArrayList();
234 | for( int c=0; c < comments.length(); c++ ) {
235 | JSONObject comment = comments.getJSONObject(c);
236 | JSONObject from = comment.getJSONObject("from");
237 | commentList.add(new Comment(from.getString("username"),
238 | comment.getString("text")));
239 | }
240 | instagramImage.comment_list = commentList;
241 | }
242 |
243 | // caption
244 |
245 | try {
246 | JSONObject caption = image.getJSONObject("caption");
247 | if( caption != null ) {
248 | instagramImage.caption = caption.getString("text");
249 | }
250 | } catch (JSONException e) {}
251 |
252 | // likers
253 | try {
254 | instagramImage.liker_count = image.getJSONObject("likes").getInt("count");
255 | JSONArray likes = image.getJSONObject("likes").getJSONArray("data");
256 | if( likes != null ) {
257 | ArrayList likerList = new ArrayList();
258 | if( likes.length() > 0 ) {
259 | for( int l=0; l < likes.length(); l++ ) {
260 | JSONObject like = likes.getJSONObject(l);
261 | likerList.add(like.getString("username"));
262 | }
263 | instagramImage.liker_list = likerList;
264 | }
265 | }
266 | } catch( JSONException j ) {}
267 |
268 | instagramImageList.add(instagramImage);
269 | }
270 |
271 | return true;
272 | } else {
273 | publishProgress("Improper data returned from Instagram");
274 | if(debug) Log.e(Constants.TAG, "instagram returned bad data");
275 | return false;
276 | }
277 | } catch (Exception e) {
278 | e.printStackTrace();
279 | return false;
280 | }
281 | }
282 | }
283 |
284 | public AdapterView.OnItemClickListener itemClickListener = new AdapterView.OnItemClickListener() {
285 | public void onItemClick(AdapterView> adapterView, View view, int i, long l) {
286 | final InstagramImage instagramImage = (InstagramImage)adapter.getItem(i);
287 |
288 | // build dialog
289 | List dialogItems = new ArrayList();
290 |
291 | // 0: like/unlike
292 | if( instagramImage.user_has_liked == true ) {
293 | dialogItems.add("Unlike");
294 | } else {
295 | dialogItems.add("Like");
296 | }
297 |
298 | // 1: comment
299 | dialogItems.add("Comment");
300 |
301 | // 2: share
302 | dialogItems.add("Share");
303 |
304 | final CharSequence[] items = dialogItems.toArray(new String[dialogItems.size()]);
305 |
306 | AlertDialog.Builder builder = new AlertDialog.Builder(FeedActivity.this);
307 | builder.setTitle("Choose your action");
308 | builder.setItems(items, new DialogInterface.OnClickListener() {
309 | public void onClick(DialogInterface dialog, int item) {
310 | switch(item) {
311 | case 0:
312 | if (instagramImage.user_has_liked == true) {
313 | unlike(instagramImage);
314 | } else {
315 | like(instagramImage);
316 | }
317 | break;
318 | case 1:
319 | showCommentDialog(instagramImage);
320 | break;
321 | case 2:
322 | showShareDialog(instagramImage);
323 | break;
324 | default:
325 | break;
326 | }
327 | }
328 | });
329 | AlertDialog alert = builder.create();
330 | alert.show();
331 |
332 | }
333 | };
334 |
335 | public void showShareDialog(InstagramImage image) {
336 | // shoot the intent
337 | // will default to "messaging / sms" if nothing else is installed
338 | Intent sharingIntent = new Intent(Intent.ACTION_SEND);
339 | //Text seems to be necessary for Facebook and Twitter
340 | sharingIntent.setType("text/plain");
341 | sharingIntent.putExtra(Intent.EXTRA_TEXT, image.caption + " " + image.permalink);
342 | startActivity(Intent.createChooser(sharingIntent,"Share using"));
343 | }
344 |
345 | public void showCommentDialog(InstagramImage image) {
346 | final InstagramImage finalImage = image;
347 |
348 | AlertDialog.Builder alert = new AlertDialog.Builder(this);
349 |
350 | alert.setTitle("Comment");
351 |
352 | // Set an EditText view to get user input
353 | final EditText input = new EditText(this);
354 | alert.setView(input);
355 |
356 | alert.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
357 | public void onClick(DialogInterface dialog, int whichButton) {
358 | String comment = input.getText().toString();
359 | postComment(comment, finalImage);
360 | }
361 | });
362 |
363 | alert.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
364 | public void onClick(DialogInterface dialog, int whichButton) {
365 | // Canceled.
366 | }
367 | });
368 |
369 | alert.show();
370 | }
371 |
372 | public void postComment(String comment, InstagramImage image) {
373 | List postParams = new ArrayList();
374 | postParams.add(new BasicNameValuePair("text", comment));
375 | postParams.add(new BasicNameValuePair("access_token", Utils.getAccessToken(getApplicationContext())));
376 | String url = Constants.MEDIA_ENDPOINT + image.id + Constants.COMMENT_MEDIA_ENDPOINT;
377 |
378 |
379 | JSONObject jsonResponse = Utils.doRestfulPut(httpClient,
380 | url,
381 | postParams,
382 | this);
383 | if( jsonResponse != null ) {
384 | image.comment_list.add(new Comment(Utils.getUsername(getApplicationContext()),comment));
385 | adapter.notifyDataSetChanged();
386 | } else {
387 | Toast.makeText(this,
388 | "Comment failed", Toast.LENGTH_SHORT).show();
389 | }
390 | }
391 |
392 |
393 | public void like(InstagramImage image) {
394 | List postParams = new ArrayList();
395 | postParams.add(new BasicNameValuePair("access_token", Utils.getAccessToken(getApplicationContext())));
396 |
397 | String url = Constants.MEDIA_ENDPOINT + image.id + Constants.LIKE_MEDIA_ENDPOINT;
398 |
399 | JSONObject jsonResponse = Utils.doRestfulPut(httpClient,
400 | url,
401 | postParams,
402 | this);
403 |
404 | if( jsonResponse != null ) {
405 | if( image.liker_list == null ) image.liker_list = new ArrayList();
406 | image.liker_list.add(Utils.getUsername(getApplicationContext()));
407 | image.liker_count++;
408 | image.user_has_liked = true;
409 | adapter.notifyDataSetChanged();
410 | }
411 | }
412 |
413 | public void unlike(InstagramImage image) {
414 | List postParams = new ArrayList();
415 | postParams.add(new BasicNameValuePair("access_token", Utils.getAccessToken(getApplicationContext())));
416 |
417 | String url = Constants.MEDIA_ENDPOINT + image.id + Constants.LIKE_MEDIA_ENDPOINT;
418 | String access_url = Utils.decorateEndpoint(url, Utils.getAccessToken(getApplicationContext()));
419 |
420 | JSONObject jsonResponse = Utils.doRestfulDelete(httpClient, access_url, this);
421 |
422 | if( jsonResponse != null ) {
423 | image.liker_list.remove(Utils.getUsername(getApplicationContext()));
424 | image.liker_count--;
425 | image.user_has_liked = false;
426 | adapter.notifyDataSetChanged();
427 | }
428 | }
429 |
430 |
431 | private class RefreshAction implements ActionBar.Action {
432 |
433 | public int getDrawable() {
434 | return R.drawable.ic_title_refresh;
435 | }
436 |
437 | public void performAction(View view) {
438 | refresh();
439 | }
440 | }
441 | }
--------------------------------------------------------------------------------
/src/org/acmelab/andgram2/ImageDetailActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011, Mark L. Chang . All rights reserved.
3 | *
4 | * Redistribution and use in source and binary forms, with or without modification, are
5 | * permitted provided that the following conditions are met:
6 | *
7 | * 1. Redistributions of source code must retain the above copyright notice, this list of
8 | * conditions and the following disclaimer.
9 | *
10 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list
11 | * of conditions and the following disclaimer in the documentation and/or other
12 | * materials provided with the distribution.
13 | *
14 | * THIS SOFTWARE IS PROVIDED BY Mark L. Chang ``AS IS'' AND ANY EXPRESS OR IMPLIED
15 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
16 | * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MARK L. CHANG OR
17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
21 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
22 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 | *
24 | * The views and conclusions contained in the software and documentation are those of the
25 | * authors and should not be interpreted as representing official policies, either expressed
26 | * or implied, of Mark L. Chang.
27 | */
28 |
29 | package org.acmelab.andgram2;
30 |
31 | import android.app.Activity;
32 | import android.app.AlertDialog;
33 | import android.content.DialogInterface;
34 | import android.content.Intent;
35 | import android.graphics.Bitmap;
36 | import android.graphics.BitmapFactory;
37 | import android.os.AsyncTask;
38 | import android.os.Bundle;
39 | import android.text.Html;
40 | import android.util.Log;
41 | import android.view.View;
42 | import android.widget.EditText;
43 | import android.widget.ImageView;
44 | import android.widget.TextView;
45 | import android.widget.Toast;
46 | import com.markupartist.android.widget.ActionBar;
47 | import org.apache.http.HttpEntity;
48 | import org.apache.http.HttpResponse;
49 | import org.apache.http.HttpStatus;
50 | import org.apache.http.NameValuePair;
51 | import org.apache.http.client.methods.HttpGet;
52 | import org.apache.http.message.BasicNameValuePair;
53 | import org.json.JSONArray;
54 | import org.json.JSONException;
55 | import org.json.JSONObject;
56 | import org.json.JSONTokener;
57 |
58 | import javax.net.ssl.SSLException;
59 | import java.io.*;
60 | import java.net.URL;
61 | import java.text.SimpleDateFormat;
62 | import java.util.ArrayList;
63 | import java.util.Date;
64 | import java.util.List;
65 |
66 | /**
67 | * Created by IntelliJ IDEA.
68 | * User: mchang
69 | * Date: 4/19/11
70 | * Time: 1:06 AM
71 | * Shows a single image
72 | */
73 |
74 | public class ImageDetailActivity extends Activity {
75 |
76 | private static final boolean debug = true;
77 | ActionBar actionBar;
78 | String id;
79 | MyHttpClient httpClient;
80 | InstagramImage image;
81 |
82 |
83 | public void onCreate(Bundle savedInstanceState) {
84 | super.onCreate(savedInstanceState);
85 | setContentView(R.layout.detail_layout);
86 |
87 | Bundle extras = getIntent().getExtras();
88 | id = extras.getString("id");
89 |
90 | actionBar = (ActionBar) findViewById(R.id.detail_actionbar);
91 | actionBar.setTitle(R.string.image_detail);
92 |
93 | Intent homeIntent = new Intent(getApplicationContext(), HomeActivity.class);
94 | homeIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
95 |
96 | final ActionBar.Action goHomeAction = new ActionBar.IntentAction(this,
97 | homeIntent, R.drawable.ic_title_home);
98 | actionBar.addAction(goHomeAction);
99 |
100 | httpClient = new MyHttpClient(this);
101 |
102 | new FetchImage().execute(id);
103 | }
104 |
105 | private void drawImageDetails(InstagramImage _image) {
106 | // stash image for later use (so we can click on it)
107 | this.image = _image;
108 |
109 | // get handle to UI elements
110 | TextView username = (TextView) findViewById(R.id.detail_username);
111 | ImageView imageView = (ImageView) findViewById(R.id.detail_image);
112 | TextView caption = (TextView) findViewById(R.id.detail_caption);
113 | TextView comments = (TextView) findViewById(R.id.detail_comments);
114 |
115 | // render it
116 | imageView.setTag(image.standard_resolution);
117 | username.setText(Html.fromHtml("" + image.username + " ") +
118 | image.taken_at);
119 | caption.setText(Html.fromHtml("" + image.username + " " + image.caption));
120 |
121 | // comments hold likes and comments
122 | StringBuilder likerString = new StringBuilder();
123 |
124 | if (image.liker_list != null) {
125 | if (image.liker_list.size() > 0) {
126 | likerString.append("Liked by ");
127 | for (String liker : image.liker_list) {
128 | likerString.append(" " + liker);
129 | }
130 | likerString.append("");
131 | if (image.liker_list.size() < image.liker_count) {
132 | int others_count = image.liker_count - image.liker_list.size();
133 | likerString.append(" and " + Integer.toString(others_count) + " others");
134 | }
135 | likerString.append("
");
136 | }
137 | }
138 |
139 | // iterate over comments
140 | if (image.comment_list != null) {
141 | if (image.comment_list.size() > 0) {
142 | for (Comment comment : image.comment_list) {
143 | likerString.append("" + comment.username + " ");
144 | likerString.append(comment.comment + "
");
145 | }
146 | }
147 | }
148 |
149 | comments.setText(Html.fromHtml(likerString.toString()));
150 | }
151 |
152 | private void drawImageBitmap(Bitmap bitmap) {
153 | ImageView imageView = (ImageView) findViewById(R.id.detail_image);
154 | if( bitmap != null ) {
155 | imageView.setImageBitmap(bitmap);
156 |
157 | // set click listener
158 |
159 | } else {
160 | Toast.makeText(this,"Error fetching photo!", Toast.LENGTH_SHORT).show();
161 | return;
162 | }
163 | }
164 |
165 | public void imageClick(View view) {
166 | // build dialog
167 | List dialogItems = new ArrayList();
168 |
169 | // 0: like/unlike
170 | if (image.user_has_liked == true) {
171 | dialogItems.add("Unlike");
172 | } else {
173 | dialogItems.add("Like");
174 | }
175 |
176 | // 1: comment
177 | dialogItems.add("Comment");
178 |
179 | // 2: share
180 | dialogItems.add("Share");
181 |
182 | final CharSequence[] items = dialogItems.toArray(new String[dialogItems.size()]);
183 |
184 | AlertDialog.Builder builder = new AlertDialog.Builder(ImageDetailActivity.this);
185 | builder.setTitle("Choose your action");
186 | builder.setItems(items, new DialogInterface.OnClickListener() {
187 | public void onClick(DialogInterface dialog, int item) {
188 | switch (item) {
189 | case 0:
190 | if (image.user_has_liked == true) {
191 | unlike(image);
192 | } else {
193 | like(image);
194 | }
195 | break;
196 | case 1:
197 | showCommentDialog(image);
198 | break;
199 | case 2:
200 | showShareDialog(image);
201 | break;
202 | default:
203 | break;
204 | }
205 | }
206 | });
207 | AlertDialog alert = builder.create();
208 | alert.show();
209 | }
210 |
211 | public void showShareDialog(InstagramImage image) {
212 | // shoot the intent
213 | // will default to "messaging / sms" if nothing else is installed
214 | Intent sharingIntent = new Intent(Intent.ACTION_SEND);
215 | //Text seems to be necessary for Facebook and Twitter
216 | sharingIntent.setType("text/plain");
217 | sharingIntent.putExtra(Intent.EXTRA_TEXT, image.caption + " " + image.permalink);
218 | startActivity(Intent.createChooser(sharingIntent,"Share using"));
219 | }
220 |
221 | public void showCommentDialog(InstagramImage image) {
222 | final InstagramImage finalImage = image;
223 |
224 | AlertDialog.Builder alert = new AlertDialog.Builder(this);
225 |
226 | alert.setTitle("Comment");
227 |
228 | // Set an EditText view to get user input
229 | final EditText input = new EditText(this);
230 | alert.setView(input);
231 |
232 | alert.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
233 | public void onClick(DialogInterface dialog, int whichButton) {
234 | String comment = input.getText().toString();
235 | postComment(comment, finalImage);
236 | }
237 | });
238 |
239 | alert.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
240 | public void onClick(DialogInterface dialog, int whichButton) {
241 | // Cancelled.
242 | }
243 | });
244 |
245 | alert.show();
246 | }
247 |
248 | public void postComment(String comment, InstagramImage image) {
249 | List postParams = new ArrayList();
250 | postParams.add(new BasicNameValuePair("text", comment));
251 | postParams.add(new BasicNameValuePair("access_token", Utils.getAccessToken(getApplicationContext())));
252 | String url = Constants.MEDIA_ENDPOINT + image.id + Constants.COMMENT_MEDIA_ENDPOINT;
253 |
254 |
255 | JSONObject jsonResponse = Utils.doRestfulPut(httpClient,
256 | url,
257 | postParams,
258 | this);
259 | if( jsonResponse != null ) {
260 | image.comment_list.add(new Comment(Utils.getUsername(getApplicationContext()),comment));
261 | drawImageDetails(image);
262 | } else {
263 | Toast.makeText(this,
264 | "Comment failed", Toast.LENGTH_SHORT).show();
265 | }
266 | }
267 |
268 |
269 | public void like(InstagramImage image) {
270 | List postParams = new ArrayList();
271 | postParams.add(new BasicNameValuePair("access_token", Utils.getAccessToken(getApplicationContext())));
272 |
273 | String url = Constants.MEDIA_ENDPOINT + image.id + Constants.LIKE_MEDIA_ENDPOINT;
274 |
275 | JSONObject jsonResponse = Utils.doRestfulPut(httpClient,
276 | url,
277 | postParams,
278 | this);
279 |
280 | if( jsonResponse != null ) {
281 | if( image.liker_list == null ) image.liker_list = new ArrayList();
282 | image.liker_list.add(Utils.getUsername(getApplicationContext()));
283 | image.liker_count++;
284 | image.user_has_liked = true;
285 | drawImageDetails(image);
286 | }
287 | }
288 |
289 | public void unlike(InstagramImage image) {
290 | List postParams = new ArrayList();
291 | postParams.add(new BasicNameValuePair("access_token", Utils.getAccessToken(getApplicationContext())));
292 |
293 | String url = Constants.MEDIA_ENDPOINT + image.id + Constants.LIKE_MEDIA_ENDPOINT;
294 | String access_url = Utils.decorateEndpoint(url, Utils.getAccessToken(getApplicationContext()));
295 |
296 | JSONObject jsonResponse = Utils.doRestfulDelete(httpClient, access_url, this);
297 |
298 | if( jsonResponse != null ) {
299 | image.liker_list.remove(Utils.getUsername(getApplicationContext()));
300 | image.liker_count--;
301 | image.user_has_liked = false;
302 | drawImageDetails(image);
303 | }
304 | }
305 |
306 |
307 | private Bitmap decodeFile(File f){
308 | try {
309 | if(debug) Log.i(Constants.TAG, "Decoding image: " + f.toString());
310 | Bitmap b = BitmapFactory.decodeStream(new FileInputStream(f));
311 | if(debug) Log.i(Constants.TAG, "decoded a real bitmap!");
312 | return(b);
313 | } catch (FileNotFoundException e) {
314 | if(debug) Log.i(Constants.TAG, "File not found");
315 | return null;
316 | } catch (Exception ex) {
317 | if(debug) Log.e(Constants.TAG, "Some other shit failed in decodeFile");
318 | ex.printStackTrace();
319 | return null;
320 | }
321 | }
322 |
323 |
324 |
325 | private class FetchImage extends AsyncTask {
326 | protected void onPreExecute() {
327 | actionBar.setProgressBarVisibility(View.VISIBLE);
328 | TextView caption = (TextView) findViewById(R.id.detail_username);
329 | caption.setText("Loading...");
330 | }
331 |
332 | protected void onPostExecute(InstagramImage image) {
333 | actionBar.setProgressBarVisibility(View.GONE);
334 | drawImageDetails(image);
335 | new FetchBitmap().execute(image.standard_resolution);
336 | }
337 |
338 | protected void onProgressUpdate(String... toastText) {
339 | Toast.makeText(ImageDetailActivity.this, toastText[0], Toast.LENGTH_SHORT).show();
340 | if(debug) Log.e(Constants.TAG, toastText[0]);
341 | }
342 |
343 | protected InstagramImage doInBackground(String... id) {
344 | if (debug) Log.i(Constants.TAG, "Fetching single image");
345 |
346 | HttpEntity httpEntity = null;
347 |
348 | // construct url
349 | String url = Utils.decorateEndpoint(Constants.MEDIA_ENDPOINT + id[0], Utils.getAccessToken(getApplicationContext()));
350 |
351 |
352 | if (Utils.isOnline(getApplicationContext()) == false) {
353 | publishProgress("No connection to Internet.\nTry again later");
354 | return null;
355 | }
356 |
357 | boolean success = false;
358 | int fail_count = 0;
359 | while (success == false) {
360 | try {
361 | httpClient = new MyHttpClient(getApplicationContext());
362 | HttpGet httpGet = new HttpGet(url);
363 | HttpResponse httpResponse = httpClient.execute(httpGet);
364 |
365 | // test result code
366 | if (httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
367 | publishProgress("Login failed.");
368 | return null;
369 | }
370 |
371 | httpEntity = httpResponse.getEntity();
372 | success = true;
373 | } catch (SSLException sslException) {
374 | if (debug) Log.e(Constants.TAG, "SSL Exception: " + fail_count);
375 | success = false;
376 | fail_count++;
377 | if (fail_count > 10) {
378 | publishProgress("SSL exception.\nMost times, you can simply try again.");
379 | return null;
380 | }
381 | } catch (IOException ioException) {
382 | publishProgress("Authorization error");
383 | return null;
384 | }
385 | }
386 |
387 | try {
388 | if (httpEntity != null) {
389 | BufferedReader reader = new BufferedReader(new InputStreamReader(httpEntity.getContent(), "UTF-8"));
390 | String json = reader.readLine();
391 | JSONTokener jsonTokener = new JSONTokener(json);
392 | JSONObject jsonObject = new JSONObject(jsonTokener);
393 |
394 | // create a new instance
395 | InstagramImage instagramImage = new InstagramImage();
396 |
397 | // parse the activity feed
398 | JSONObject image = jsonObject.getJSONObject("data");
399 |
400 | // image
401 | JSONObject images = image.getJSONObject("images");
402 | JSONObject thumbnailImage = images.getJSONObject("thumbnail");
403 | JSONObject lowResolutionImage = images.getJSONObject("low_resolution");
404 | JSONObject standardResolutionImage = images.getJSONObject("standard_resolution");
405 | instagramImage.id = image.getString("id");
406 | instagramImage.permalink = image.getString("link");
407 |
408 | instagramImage.user_has_liked = image.getBoolean("user_has_liked");
409 |
410 | // permalinks
411 | instagramImage.thumbnail = thumbnailImage.getString("url");
412 | instagramImage.low_resolution = lowResolutionImage.getString("url");
413 | instagramImage.standard_resolution = standardResolutionImage.getString("url");
414 |
415 | // user
416 | JSONObject user = image.getJSONObject("user");
417 | instagramImage.username = user.getString("username");
418 | instagramImage.user_id = user.getString("id");
419 | instagramImage.full_name = user.getString("full_name");
420 |
421 | // date taken_at
422 | Long dateLong = image.getLong("created_time");
423 | SimpleDateFormat formatter = new SimpleDateFormat("MMMM d, yyyy HH:mm");
424 | instagramImage.taken_at = formatter.format(new Date(dateLong * 1000L));
425 | instagramImage.taken_time = dateLong * 1000L;
426 |
427 | // comments
428 | instagramImage.comment_count = image.getJSONObject("comments").getInt("count");
429 | JSONArray comments = image.getJSONObject("comments").getJSONArray("data");
430 | if (comments != null) {
431 | ArrayList commentList = new ArrayList();
432 | for (int c = 0; c < comments.length(); c++) {
433 | JSONObject comment = comments.getJSONObject(c);
434 | JSONObject from = comment.getJSONObject("from");
435 | commentList.add(new Comment(from.getString("username"),
436 | comment.getString("text")));
437 | }
438 | instagramImage.comment_list = commentList;
439 | }
440 |
441 | // caption
442 |
443 | try {
444 | JSONObject caption = image.getJSONObject("caption");
445 | if (caption != null) {
446 | instagramImage.caption = caption.getString("text");
447 | }
448 | } catch (JSONException e) {
449 | }
450 |
451 | // likers
452 | try {
453 | instagramImage.liker_count = image.getJSONObject("likes").getInt("count");
454 | JSONArray likes = image.getJSONObject("likes").getJSONArray("data");
455 | if (likes != null) {
456 | ArrayList likerList = new ArrayList();
457 | if (likes.length() > 0) {
458 | for (int l = 0; l < likes.length(); l++) {
459 | JSONObject like = likes.getJSONObject(l);
460 | likerList.add(like.getString("username"));
461 | }
462 | instagramImage.liker_list = likerList;
463 | }
464 | }
465 | } catch (JSONException j) {
466 | }
467 |
468 | // got it!
469 | return instagramImage;
470 |
471 | } else {
472 | publishProgress("Improper data returned from Instagram");
473 | if (debug) Log.e(Constants.TAG, "instagram returned bad data");
474 | return null;
475 | }
476 | } catch (Exception e) {
477 | e.printStackTrace();
478 | return null;
479 | }
480 | }
481 | }
482 |
483 | private class FetchBitmap extends AsyncTask {
484 | protected void onPreExecute() {
485 | actionBar.setProgressBarVisibility(View.VISIBLE);
486 | }
487 |
488 | protected void onPostExecute(Bitmap bitmap) {
489 | actionBar.setProgressBarVisibility(View.GONE);
490 | drawImageBitmap(bitmap);
491 | }
492 |
493 | protected void onProgressUpdate(String... toastText) {
494 | Toast.makeText(ImageDetailActivity.this, toastText[0], Toast.LENGTH_SHORT).show();
495 | if(debug) Log.e(Constants.TAG, toastText[0]);
496 | }
497 |
498 | protected Bitmap doInBackground(String... url) {
499 |
500 | if(debug) Log.i(Constants.TAG, "Fetching bitmap");
501 |
502 | File downloadDir;
503 |
504 | //Find the dir to save cached images
505 | if (android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED))
506 | downloadDir = new File(android.os.Environment.getExternalStorageDirectory(), Constants.OUTPUT_DIR);
507 | else {
508 | publishProgress("You need a SD card!");
509 | return null;
510 | }
511 |
512 | if (!downloadDir.exists())
513 | downloadDir.mkdirs();
514 |
515 | String filename = String.valueOf(url.hashCode());
516 |
517 | try {
518 | File f = new File(downloadDir, filename);
519 | if (debug) Log.i(Constants.TAG, "Downloading from web");
520 | Bitmap bitmap = null;
521 | InputStream is = new URL(url[0]).openStream();
522 | OutputStream os = new FileOutputStream(f);
523 | Utils.CopyStream(is, os);
524 | os.close();
525 | is.close();
526 | bitmap = decodeFile(f);
527 | return bitmap;
528 | } catch (Exception ex) {
529 | ex.printStackTrace();
530 | return null;
531 | }
532 | }
533 | }
534 | }
535 |
536 |
--------------------------------------------------------------------------------