January 24, 2011

Bash shell script: Scraping and downloading image files from a ffffound RSS feed

I wrote this little script for a friend, as an exercise in bash shell scripting. This script is for OS X.

Step 1:
Install Homebrew - this is a great tool for installing common Unix tools. You should only have to open Terminal, and paste the 1-line installation script found in the link above. Something like this:
ruby -e "$(curl -fsSLk https://gist.github.com/raw/323731/install_homebrew.rb)"

Step 2:
Install wget with Homebrew: type this into Terminal and press Enter:
brew install wget

Step 3:
Save the following code into a text file called "ffffound_sssswiped.sh" and save it into your User/Pictures/ directory:
curl http://feeds.feedburner.com/ffffound/everyone | egrep -o source\ url=\"http://[^[:space:]]*.\(jpg\|png\|gif\) | egrep -o http://[^[:space:]]*.\(jpg\|png\|gif\) | xargs wget -nc -P ~/Pictures/ffffound

Step 4:
Customize! You can replace http://feeds.feedburner.com/ffffound/everyone with your own ffffound RSS feed, or anyone else's.

Step 5:
Run the script: type the following into Terminal, and hit Enter:
bash ~/Pictures/ffffound_sssswiped.sh

You should see the download progress as it scrapes the RSS feed for just the large-format image files. You can run this as often as you want, and it will skip any files you've already downloaded.

Magic!

January 19, 2011

Android + Phonegap: Scale the WebView to fit the device

I was porting an iPad app to Android for the new Samsung tablet, and I had some trouble getting my web view to scale to the size of the device screen so that I wouldn't have to resize any of my assets. Obviously this is a questionable tactic, but I was experimenting and wanted to see how it would look :)

Here's the meat of my main App.java class:
public class App extends DroidGap {
 
 // declare the original size of the iPad app
 protected float ORIG_APP_W = 768;
 protected float ORIG_APP_H = 1004;
 
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        super.loadUrl("file:///android_asset/www/index.html");
        
     // set some defaults
     this.appView.setBackgroundColor(0x000000);
     this.appView.setHorizontalScrollBarEnabled(false);
     this.appView.setHorizontalScrollbarOverlay(false);
     this.appView.setVerticalScrollBarEnabled(false);
     this.appView.setVerticalScrollbarOverlay(false);
     
     // get actual screen size
     Display display = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
     int width = display.getWidth(); 
     int height = display.getHeight(); 
     
     // calculate target scale (only dealing with portrait orientation)
     double globalScale = Math.ceil( ( width / ORIG_APP_W ) * 100 );
     
     // make sure we're all good
     Log.v( "ORIG_APP_W", " = " + ORIG_APP_W );
     Log.v( "ORIG_APP_H", " = " + ORIG_APP_H );
     Log.v( "width", " = " + width );
     Log.v( "this.appView.getMeasuredHeight()", " = " + height );
     Log.v( "globalScale", " = " + globalScale );
     Log.v( "this.appView.getScale()", "index=" + this.appView.getScale() );
    
     // set some defaults on the web view
     this.appView.getSettings().setBuiltInZoomControls( false );
     this.appView.getSettings().setSupportZoom( false );
     this.appView.getSettings().setGeolocationEnabled( true );
     this.appView.getSettings().setLightTouchEnabled( true );
     this.appView.getSettings().setRenderPriority( RenderPriority.HIGH );
     
     // set the scale
     this.appView.setInitialScale( (int)globalScale );
   }
}
I also updated the AndroidManifest.xml file to lock the app into portrait orientation, work on tablet-sized devices, have a nice app name, and give the device access to the Internet and geolocation:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.phonegap.testapp"
      android:versionCode="1"
      android:versionName="1.0">     
     
    <application android:icon="@drawable/icon" 
        android:label="@string/app_name"
        android:debuggable="true">
        <activity android:name=".App" 
                  android:label="Test App" 
                  android:configChanges="orientation|keyboardHidden"
                  android:noHistory="true" 
                  android:stateNotNeeded="true" 
                  android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

    <!-- allows access to phonegap hardware features -->
 <uses-permission android:name="android.permission.INTERNET" />
 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
 <!--<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />-->
 <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> 
 <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
 <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />


   <supports-screens
     android:largeScreens="true"
     android:normalScreens="false"
     android:smallScreens="false"
     android:resizeable="true"
     android:anyDensity="true"
     />

</manifest> 
And finally my res/layout/main.xml file, though I'm not sure if this is different from the Phonegap default:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >    
            <WebView android:id="@+id/appView"
            android:layout_height="fill_parent"
            android:layout_width="fill_parent"
            /> 
</LinearLayout>
I hope this helps someone port their hybrid html5 iPad app to Android tablets.

Javascript: Clear a webkitTransition animation

I was animating an html element with Webkit transitions via javascript, and after the animation was done, I applied non-animated webkitTransform positioning to the same element. It animated instead of immediately displaying the new style. I came up with the following function to clear any previous Webkit animation values:
function clearAnimation( element ) {
  if( typeof element.style.webkitTransform !== 'undefined' && element.style.webkitTransform ) {   // 2nd conditional fixes bug in Chrome on windows
    element.style.webkitTransition = '';
    element.style.webkitTransform = '';
  }
}
This will prevent any overlap when switching between animatiions and instant repositioning.

January 18, 2011

Android: touchmove event bug

touchmove events in Android web browsers have a really serious bug. If you don't include the following code, the touchmove event will fire once, but not again until you're done moving your touch, which utterly kills the usefulness of the touchmove event. It's a weird one, and may very well break more advanced touch logic that works on iOS. But if you preventDefault() on the touchstart event, your touchmove will function as expected.
element.addEventListener( "touchstart", function(e){ onStart(e); }, false );
function onStart ( touchEvent ) {
  if( navigator.userAgent.match(/Android/i) ) {
    touchEvent.preventDefault();
  }
}
This bug is documented here:
http://code.google.com/p/android/issues/detail?id=5491

and is probably not entirely unrelated to my previous post about weird Android Touch event behavior:
http://uihacker.blogspot.com/2010/10/android-bug-miss-drag-as-we-are-waiting.html

CSS Grab hand cursor

I wanted a grabby hand cursor in html since we're building sites that have draggable interfaces for both desktop browsers and touchscreen devices. I found some CSS that takes care of most modern browsers, and came up with a little extra for Internet Explorer and Chrome.

Here's the CSS:
#trackbar {
  width:100%;
  height:50px;
  cursor:hand;
  cursor:grab;
  cursor:-moz-grab;
  cursor:-webkit-grab;
}

#trackbar.custom_cursor {
  cursor: url(https://mail.google.com/mail/images/2/openhand.cur), default !important;
}

#trackbar.grabbing {
  cursor:grabbing;
  cursor:-moz-grabbing;
  cursor:-webkit-grabbing;
}

#trackbar.custom_cursor.grabbing {
  cursor: url(https://mail.google.com/mail/images/2/closedhand.cur), default !important;
}
The 3 cursor attributes looked great in most modern browsers, but didn't work in IE (obviously) or Chrome. I looked at some code in Google Maps and gleaned the custom cursors, which you can pull down locally, but you have to reference the .cur files absolutely, or they won't work in IE. Awesome. So, because we'd rather use CSS, we only apply the custom cursor files if you're in IE or Chrome. Something like this:
if( navigator.userAgent.match(/MSIE/i) || navigator.userAgent.match(/Chrome/i) ) document.getElementById('trackbar').className = 'custom_cursor';
I'm not sure if there's any way around it, but it doesn't seem like IE will change cursors after you mouse down. So if you add the "grabbing" class on a mousedown event, IE will block your grabby hands :(

January 11, 2011

Actionscript: &nbsp; / HTML text issue from an old Flash 8 project

I built a project back in 2006 that's amazingly still alive and making lots of money for a client. I had a bit of code that would insert an &nbsp; into a textfield for dummy spacing in between other letters. Out of nowhere, this stopped working for the client, and all the text was space-less. This is what I had:
StringUtil.searchReplace( myStr, _dummyChar, '<span class="textDummy">&nbsp;</span>' );
This now fails to insert a space. I tried just using an actual space, but since this is an html textfield, multiple consecutive spaces don't keep making more room. So, I added a space after the &nbsp;, and magically, it works like it had in previous years:
StringUtil.searchReplace( myStr, _dummyChar, '<span class="textDummy">&nbsp;</span> ' );
Whew fun!