We write the client for Habr for Android

    Looking ahead, this is what happened:
    image


    12:56. I will do this in parallel with writing a topic (so interesting). In the course of writing the client explaining all the steps. So, we smoked, poured tea, prepared a playlist, and while the tea cools down - we check if the name habrahabr is busy in the market. Ok, let's move on to creating the application.

    13:02 We are creating a new project.
    The

    API level screenshot is 4, for the reason that with a smaller value - on Samsung Galaxy Tab tablets, the screen resolution will be incorrect and the owners of these miracle devices will not fail to put a bunch of minuses in the market for you (although, in principle, this is a cant of the developer).

    13:08 Fixim manifesto.
    Two lines need to be added:
    - android: configChanges = "orientation", this line is needed so that when you change the screen orientation our activity is not destroyed.
    -, we ask for permission to access the Internet
    AndroidManifest
    * plays front242 - headhunter v3.0

    13:13 Fixim layout.
    We erase everything and add one single element - webview - full screen

    13:16 Icon.
    With the help of the firebog, we trim the absolute link and with the help of Photoshop we crop to 48 * 48 px and throw in res / drawable ...

    13:27 It turned out to be more complicated with the icon. The logo stain after reduction turned into muddy crap, I had to google . I hope the author will not be offended.

    Phew, we’ve finished the most difficult, finally you can hang around.

    13:39 Download Habr
    public class habr extends Activity {
      
      private WebView wv;
      
      /** Called when the activity is first created. */
      @Override
      public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        wv = (WebView) findViewById(R.id.wv);
        
        WebSettings webSettings = wv.getSettings();
        webSettings.setSavePassword(true);
        webSettings.setSaveFormData(true);
        webSettings.setJavaScriptEnabled(true);
        
        wv.loadUrl("http://habrahabr.ru");
      }
    }

    * This source code was highlighted with Source Code Highlighter.


    * Here we simply set our view on habr, after enabling javascript and remembering forms / passwords. It looks ugly so far, but it is already working. Smoke break.

    13:53 We continue the conversation.

    public class habr extends Activity {
      
      private WebView wv;
      private String LASTURL = "";
      
      /** Called when the activity is first created. */
      @Override
      public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.getWindow().requestFeature(Window.FEATURE_PROGRESS);
        setContentView(R.layout.main);
        
        wv = (WebView) findViewById(R.id.wv);
        
        WebSettings webSettings = wv.getSettings();
        webSettings.setSavePassword(true);
        webSettings.setSaveFormData(true);
        webSettings.setJavaScriptEnabled(true);
        
        final Activity activity = this;
        
        wv.setWebChromeClient(new WebChromeClient() {
          public void onProgressChanged(WebView view, int progress)
          {
            activity.setTitle(" "+LASTURL);
            activity.setProgress(progress * 100);

            if(progress == 100)
              activity.setTitle(" "+LASTURL);
          }
        });
        wv.setWebViewClient(new WebViewClient() {
          public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
            Toast.makeText(getApplicationContext(), "Error: " + description+ " " + failingUrl, Toast.LENGTH_LONG).show();
          }
          
          @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url)
          {
            if (url.indexOf("habrahabr")<=0) {
              // the link is not for a page on my site, so launch another Activity that handles URLs
              Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
              startActivity(intent);
              return true;
            }
            return false;
          }

          public void onPageStarted (WebView view, String url, Bitmap favicon) {
            LASTURL = url;
          }
          
          public void onPageFinished (WebView view, String url) {

          }
        });
        
        wv.loadUrl("http://habrahabr.ru");
      }
    }

    * This source code was highlighted with Source Code Highlighter.


    So, we need a loading thermometer.
    1. We request a feature: this.getWindow (). RequestFeature (Window.FEATURE_PROGRESS);
    2. On progresschange - fill out the window of the thermometer
    activity.setTitle ("" + LASTURL);
    activity.setProgress (progress * 100);
    if (progress == 100) activity.setTitle ("" + LASTURL);
    3. At the beginning of the load, remember the url in the variable LASTURL = url;

    We process the fallen off Wi-Fi on the reported error:
    Toast.makeText (getApplicationContext (), "Error:" + description + "" + failingUrl, Toast.LENGTH_LONG) .show ();
    (a message pops up for absolutely morons, although so everything will be written on the page)

    14:05 Fixim layout.
    A bit of theory. You can execute javascript on the loaded page. For example, if you type something like javascript: alert (document.body.innerHTML) in the address bar of your browser, then we will see the body of the page (copy-paste - in the example above - the letter “c” is Russian so that the parser skips).
    Well, then - the HOUSE tree, etc., do whatever you want, at least completely redo the page. However, we won’t go that far (I hope) and just hide the sidebar to improve readability. And the browser itself will stretch useful content across the page. So, we try to add a handler at the end of the page load:

    public void onPageFinished (WebView view, String url) {
         view.loadUrl("javascript:(function() { " +
            "hide('sidebar');"+
            "function hide(id){if (document.getElementById(id)){document.getElementById(id).style['display'] = 'none';}}"+
                "})()");
    }

    * This source code was highlighted with Source Code Highlighter.


    / * Distracted by work * /
    Dachshunds, the sidebar is hiding, but with a certain leap. Fix image loading:

    14:34 Speed ​​up the download
    To do this, disable the images at the start of the page:
    view.getSettings (). SetLoadsImagesAutomatically (false);
    and enable at the finish, after the hack with javascript:
    view.getSettings (). setLoadsImagesAutomatically (true);

    Dachshund, loading the content has become noticeably faster (on my half-dead Iota, in the emulator, at least)

    14:37 Lunch

    15:23 We continue the conversation
    While I went for lunch, I looked into the free Wi-Fi zone, and at the same time I tested it. Regretfully, I saw a search bar hanging absurdly in the empty right corner. Let's try to do something with it.
    For starters, stupidly hide.

    * music: submatakana - the krypt (this is something with something)

    "hideByClass('panel-tools');"+
    "function hideByClass(c){var e=document.getElementsByClassName(c);for(var i=0;i
    * This source code was highlighted with Source Code Highlighter.


    Hmm, do not build to break. Dachshund, try to “append it to the list of blogs.

    "var parent = document.getElementsByClassName('page-navigation')[0];"+
                "var panel = document.getElementsByClassName('panel-tools')[0];"+
                "var div = document.createElement('div');"+
                "div.innerHTML = panel.innerHTML;"+
                "parent.appendChild(div);"+

    * This source code was highlighted with Source Code Highlighter.


    ops, now we have two panels)
    "panel.innerHTML = '';"+
    "div.style['margin-left'] = '30px;'"+

    * This source code was highlighted with Source Code Highlighter.


    15:57 Oops, one quotation mark is not there and the whole script is crashing like a house of cards.
    It should be like this: div.style ['margin-left'] = '30px';
    So I’ll try to cut down the ad units in the menu, but no, I don’t, so I’ve been messing with sinful layout for a long time (I hate it).

    16:04 Since these elements are not named, I tried this:
    "var urls=document.getElementsByTagName('a');for(var i=0;i
    * This source code was highlighted with Source Code Highlighter.

    Not fused

    16:16 Let's try to hit the areas:
    "var imgs=document.getElementsByTagName('IMG');for(var i=0;i
    * This source code was highlighted with Source Code Highlighter.

    The pictures disappeared, but the empty space still sticks out (Okay, let it remain for those who wish to do it. Let them live for the joy of the advertisers.
    Smoke break.

    16:45 Search, let's go back, having narrowed a bit in width:
    "var panel = document.getElementById('search');"+
    "panel.style['width'] = '55px';"+

    * This source code was highlighted with Source Code Highlighter.


    Well, finally, let's get down to android.
    16:47 We overlap the hardware button back.

    1.     @Override
    2.   public boolean onKeyDown(int keyCode, KeyEvent event) {
    3.     if ((keyCode == KeyEvent.KEYCODE_BACK) && wv.canGoBack()) {
    4.       wv.goBack();
    5.       return true;
    6.     }
    7.     return super.onKeyDown(keyCode, event);
    8.   }
    * This source code was highlighted with Source Code Highlighter.


    16:57 Create a menu

    1.   @Override
    2.   public boolean onCreateOptionsMenu(Menu menu)
    3.   {
    4.     super.onCreateOptionsMenu(menu);
    5.     
    6.     this.myMenu = menu;
    7.     MenuItem item = menu.add(0, 1, 0, "MAIN PAGE");
    8.     item.setIcon(R.drawable.home);
    9.     MenuItem item2 = menu.add(0, 2, 0, "BACK");
    10.     item2.setIcon(R.drawable.arrowleft);
    11.     MenuItem item3 = menu.add(0, 3, 0, "F5");
    12.     item3.setIcon(R.drawable.s);
    13.     MenuItem item4 = menu.add(0, 4, 0, "CLEAR CACHE");
    14.     item4.setIcon(R.drawable.trash);
    15.     MenuItem item5 = menu.add(0, 5, 0, "VOID");
    16.     item5.setIcon(R.drawable.vote);
    17.     return true;
    18.   }
    19.   
    20.   @Override
    21.   public boolean onOptionsItemSelected(MenuItem item)  {
    22.     switch (item.getItemId())
    23.     {
    24.       case 1:
    25.         wv.loadUrl("http://habrahabr.ru");
    26.         break;
    27.       case 2:
    28.         if (wv.canGoBack()) {
    29.           wv.goBack();
    30.         }
    31.         break;
    32.       case 3:
    33.         wv.loadUrl(LASTURL);
    34.         break;
    35.       case 4:
    36.         wv.clearCache(true);
    37.         break;
    38.       case 5:
    39.         Intent marketIntent2 = new Intent(Intent.ACTION_VIEW, Uri.parse(
    40.             "http://market.android.com/details?id=" + getPackageName()));
    41.           startActivity(marketIntent2);                
    42.         break;
    43.     }
    44.  
    45.     return true;
    46.   }
    * This source code was highlighted with Source Code Highlighter.


    Here, it’s especially to explain even nothing ...
    Webview stores data in a local isolated cache and the clearCache function removes cached pictures, etc.

    We send to the user’s market with the help of Intent + start activation, this is the standard mechanism for interacting with external applications.

    17:01 Taxes, perhaps it’s worth making a mode with pictures / without pictures
    Fixim menu:
    menu.add (0, 6, 0, “IMG ON”);
    menu.add (0, 7, 0, “IMG OFF”);

    17:05 Mutim save function settings
    1. private void saveSettings(Boolean val)
    2.   {
    3.     SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
    4.     SharedPreferences.Editor editor = settings.edit();
    5.     editor.putBoolean("IMGMODE", val);
    6.     editor.commit();
    7.   }
    * This source code was highlighted with Source Code Highlighter.


    We will transfer the settings to it (load or not the picture), and let it cram the passed value into the variable.
    (the constant PREFS_NAME was declared above - this is, as it were, the name of the config)

    Now, we just call it in the menu handler:
    1. case 6:
    2.         saveSettings(true);        
    3.         break;
    4.       case 7:
    5.         saveSettings(false);                
    6.         break;
    * This source code was highlighted with Source Code Highlighter.


    17:18 And we read the constant when creating the application from our config
    1. SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
    2.     imgOn = settings.getBoolean("IMGMODE", false);
    3.     webSettings.setLoadsImagesAutomatically(imgOn);
    * This source code was highlighted with Source Code Highlighter.


    17:19 Testing again

    17:25 It seems that there are no jambs. We export the project.
    In eclipse, this is the right button on the export project and a wizard is launched that allows you to create / select a certificate and a packaging project. Next, we stomp into the market

    17:38 Publish.
    Actually not the easiest task. Somewhere you need to get a bunch of promographics of a certain size, so it's not hard for designers here.

    According to the parameters.
    - To install the main language of the application Russian - you must first add Russian, and only after that it will be possible to remove English.
    - In the descriptive and promotext fields, it is desirable to mention the keywords by which the application can search.
    - If you set the price free - then make it paid - it is impossible
    - Do not check the copy protection box, the application will not be installed on part of the devices
    - Better indicate all countries, even if the application is only for Russian speakers, for example.

    17:50 Published.

    Download from the market
    Download the source code

    Hmm, the icon didn’t work out very nicely (For some reason this thought gnaws at me. So if someone can portray 48 * 48 - I will be very grateful.

    Completely, the final source:
    1. package ru.habrahabr.android;
    2.  
    3. import android.app.Activity;
    4. import android.content.Intent;
    5. import android.content.SharedPreferences;
    6. import android.graphics.Bitmap;
    7. import android.net.Uri;
    8. import android.os.Bundle;
    9. import android.view.KeyEvent;
    10. import android.view.Menu;
    11. import android.view.MenuItem;
    12. import android.view.Window;
    13. import android.webkit.WebChromeClient;
    14. import android.webkit.WebSettings;
    15. import android.webkit.WebView;
    16. import android.webkit.WebViewClient;
    17. import android.widget.Toast;
    18.  
    19. public class habr extends Activity {
    20.   
    21.   private WebView wv;
    22.   private String LASTURL = "";
    23.   Menu myMenu = null;
    24.   private static final String PREFS_NAME = "MyPrefs";
    25.   private Boolean imgOn;
    26.   
    27.   /** Called when the activity is first created. */
    28.   @Override
    29.   public void onCreate(Bundle savedInstanceState) {
    30.     super.onCreate(savedInstanceState);
    31.     this.getWindow().requestFeature(Window.FEATURE_PROGRESS);
    32.     setContentView(R.layout.main);
    33.     
    34.     wv = (WebView) findViewById(R.id.wv);
    35.     
    36.     WebSettings webSettings = wv.getSettings();
    37.     webSettings.setSavePassword(true);
    38.     webSettings.setSaveFormData(true);
    39.     webSettings.setJavaScriptEnabled(true);
    40.     
    41.     SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
    42.     imgOn = settings.getBoolean("IMGMODE", false);
    43.     webSettings.setLoadsImagesAutomatically(imgOn);
    44.     
    45.     final Activity activity = this;
    46.     
    47.     wv.setWebChromeClient(new WebChromeClient() {
    48.       public void onProgressChanged(WebView view, int progress)
    49.       {
    50.         activity.setTitle(" "+LASTURL);
    51.         activity.setProgress(progress * 100);
    52.         if(progress == 100)
    53.           activity.setTitle(" "+LASTURL);
    54.       }
    55.     });
    56.     wv.setWebViewClient(new WebViewClient() {
    57.       public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
    58.         Toast.makeText(getApplicationContext(), "Error: " + description+ " " + failingUrl, Toast.LENGTH_LONG).show();
    59.       }
    60.       
    61.       @Override
    62.         public boolean shouldOverrideUrlLoading(WebView view, String url)
    63.       {
    64.         if (url.indexOf("habrahabr")<=0) {
    65.           // the link is not for a page on my site, so launch another Activity that handles URLs
    66.           Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
    67.           startActivity(intent);
    68.           return true;
    69.         }
    70.         return false;
    71.       }
    72.  
    73.       public void onPageStarted (WebView view, String url, Bitmap favicon) {
    74.         LASTURL = url;
    75.         view.getSettings().setLoadsImagesAutomatically(false);
    76.       }
    77.       
    78.       public void onPageFinished (WebView view, String url) {
    79.         view.loadUrl("javascript:(function() { " +
    80.             "hide('sidebar');"+
    81.             //"var parent = document.getElementsByClassName('page-navigation')[0];"+
    82.             //"var panel = document.getElementsByClassName('panel-tools')[0];"+
    83.             //"var div = document.createElement('div');"+
    84.             //"div.innerHTML = panel.innerHTML;"+
    85.             //"parent.appendChild(div);"+
    86.             //"panel.innerHTML = '';"+
    87.             //"div.style['margin-left'] = '31px';"+
    88.             "var panel = document.getElementById('search');"+
    89.             "panel.style['width'] = '55px';"+
    90.  
    91.             //"var imgs=document.getElementsByTagName('IMG');for(var i=0;i
    92.             //"var urls=document.getElementsByTagName('li');for(var i=0;i
    93.             //"hideByClass('panel-tools');"+
    94.             "function hide(id){if (document.getElementById(id)){document.getElementById(id).style['display'] = 'none';}}"+
    95.             //"function hideByClass(c){var e=document.getElementsByClassName(c);for(var i=0;i
    96.             "})()");
    97.         if (imgOn) view.getSettings().setLoadsImagesAutomatically(true);
    98.       }
    99.     });
    100.     
    101.     wv.loadUrl("http://habrahabr.ru");
    102.   }
    103.   
    104.   @Override
    105.   public boolean onKeyDown(int keyCode, KeyEvent event) {
    106.     if ((keyCode == KeyEvent.KEYCODE_BACK) && wv.canGoBack()) {
    107.       wv.goBack();
    108.       return true;
    109.     }
    110.     return super.onKeyDown(keyCode, event);
    111.   }
    112.   
    113.   @Override
    114.   public boolean onCreateOptionsMenu(Menu menu)
    115.   {
    116.     super.onCreateOptionsMenu(menu);
    117.     
    118.     this.myMenu = menu;
    119.     MenuItem item = menu.add(0, 1, 0, "MAIN PAGE");
    120.     item.setIcon(R.drawable.home);
    121.     MenuItem item2 = menu.add(0, 2, 0, "BACK");
    122.     item2.setIcon(R.drawable.arrowleft);
    123.     MenuItem item3 = menu.add(0, 3, 0, "F5");
    124.     item3.setIcon(R.drawable.s);
    125.     MenuItem item4 = menu.add(0, 4, 0, "CLEAR CACHE");
    126.     item4.setIcon(R.drawable.trash);
    127.     MenuItem item5 = menu.add(0, 5, 0, "VOID");
    128.     item5.setIcon(R.drawable.vote);
    129.     menu.add(0, 6, 0, "IMG ON");
    130.     menu.add(0, 7, 0, "IMG OFF");
    131.     return true;
    132.   }
    133.   
    134.   @Override
    135.   public boolean onOptionsItemSelected(MenuItem item)  {
    136.     switch (item.getItemId())
    137.     {
    138.       case 1:
    139.         wv.loadUrl("http://habrahabr.ru");
    140.         break;
    141.       case 2:
    142.         if (wv.canGoBack()) {
    143.           wv.goBack();
    144.         }
    145.         break;
    146.       case 3:
    147.         wv.loadUrl(LASTURL);
    148.         break;
    149.       case 4:
    150.         wv.clearCache(true);
    151.         break;
    152.       case 5:
    153.         Intent marketIntent2 = new Intent(Intent.ACTION_VIEW, Uri.parse(
    154.             "http://market.android.com/details?id=" + getPackageName()));
    155.           startActivity(marketIntent2);                
    156.         break;
    157.       case 6:
    158.         saveSettings(true);        
    159.         break;
    160.       case 7:
    161.         saveSettings(false);                
    162.         break;
    163.     }
    164.  
    165.     return true;
    166.   }
    167.   
    168.   private void saveSettings(Boolean val)
    169.   {
    170.     SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
    171.     SharedPreferences.Editor editor = settings.edit();
    172.     editor.putBoolean("IMGMODE", val);
    173.     editor.commit();
    174.   }
    175. }
    * This source code was highlighted with Source Code Highlighter.


    UPD : Redesigned the layout very much.
    He took as a basis the style developed by almalexa.habrahabr.ru and substantially modified it with a file for a small resolution.
    The resulting style: userstyles.org/styles/46932/habr
    On this I think the development is complete. Customer in the market - updated.
    Total: everything took about a day.
    image

    Also popular now: