📜 ⬆️ ⬇️

We write the client for Habr for Android

Looking ahead, here's what happened:
image


12:56 I will do it in parallel with the writing of the topic (so interesting). In the course of writing a client explaining all the steps. So, we smoked, poured tea, prepared a playlist and, while the tea is cooling down, we check if the name habrahabr is occupied in the market. Great, go to the creation of the application.

13:02 Create a new project.
Screenshot
')
API level is 4, for the reason that at a lower value - on Samsung Galaxy Tab tablets, the screen resolution will be incorrect and the owners of these miracle devices will not fail to shove a bunch of minuses into the market (although in principle, this is hardly a developer’s cant).

13:08 Fix the manifest.
It is necessary to add two lines:
- android: configChanges = "orientation", this line is necessary so that when we change the orientation of the screen does not destroy our activites.
- <uses-permission android: name = "android.permission.INTERNET" />, we request permission to access the Internet
AndroidManifest
* plays front242 - headhunter v3.0

13:13 Fix layout.
Erase everything and add a single element - webview - full screen

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

13:27 With the icon all the more difficult it turned out. The spot of the logo after the reduction turned into muddy crap, I had to google it . I hope the author will not be offended.

Phew, the hardest thing is done, finally you can get it.

13:39 Load 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 just set our habr view on us, having previously turned on javascript and form / password memorization. It looks ugly for now, 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 the feature: this.getWindow (). RequestFeature (Window.FEATURE_PROGRESS);
2. On progresschandzh - fill the window of the thermometer
activity.setTitle ("" + LASTURL);
activity.setProgress (progress * 100);
if (progress == 100) activity.setTitle ("" + LASTURL);
3. At the beginning of the download, we store the url in the variable LASTURL = url;

We process the fallen off wifi on onresrated 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 Fix the layout.
A bit of theory. On the downloaded page, you can run javascript. If you type something like a javascript in the address bar of the browser: alert (document.body.innerHTML) - then we will see the page body (for the copy-pasters - in the example above - the letter “c” is Russian, so that the parser will skip).
Well, then - the tree house, etc., create what you want, even though completely rework the page. However, we will not go so far (I hope) and just hide the sidebar to improve readability. And the browser itself will stretch the useful content on the page. So, try to add a handler at the end of the page loading:

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 * /
Tax, sidebar is hidden, but with some leap. Fix image loading:

14:34 Accelerate the download
To do this, turn off the pictures at the start of the page:
view.getSettings (). setLoadsImagesAutomatically (false);
and turn on at the finish, after hacking with javascript:
view.getSettings (). setLoadsImagesAutomatically (true);

Tax, loading content has become significantly faster (on my half-dead Yota, in the emulator, at least)

14:37 Lunch

15:23 We continue the conversation
While I was going to lunch, I looked into the free wifi zone, and I also tested it. With regret I saw the search bar, ridiculously hanging in the empty right corner. Let's try to do something with it.
For starters, we 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<e.length;i++){e[i].style['display'] = 'none';}}" +


* This source code was highlighted with Source Code Highlighter .


Hmm, breaking does not build. Tax, let's try to attach 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 Ops, one quote is not there and the whole script collapses like a house of cards.
It should be like this: div.style ['margin-left'] = '30px';
So I will try to cut down the ad units in the menu, but not so much, that I have been busy with a sinful layout for a long time (I hate).

16:04 Since these elements are not named, I tried this:
"var urls=document.getElementsByTagName('a');for(var i=0;i<urls.length;i++){if (urls[i].target='_top'){urls[i].appendChild(document.createTextNode(''));}}" +

* This source code was highlighted with Source Code Highlighter .

Did not grow together

16:16 Let's try to hit the squares:
"var imgs=document.getElementsByTagName('IMG');for(var i=0;i<imgs.length;i++){if (imgs[i].height=60) {imgs[i].src='';imgs[i].width=0;} }" +

* 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 their homework. Let them live for the joy of the advertisers.
Smoke break

16:45 Search, perhaps we will return back, having a little pressed down on width:
"var panel = document.getElementById('search');" +
"panel.style['width'] = '55px';" +


* This source code was highlighted with Source Code Highlighter .


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


Here to explain especially even nothing like ...
The web store stores data in a local isolated cache and the clearCache function removes cached maps and the like.

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

17:01 Taks, perhaps, it is worth making a mode with / without pictures
Fix the menu:
menu.add (0, 6, 0, „IMG ON“);
menu.add (0, 7, 0, „IMG OFF“);

17:05 Mutima save 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 .


In it, we will pass the settings (load or not pictures), and let them push the transferred value into the variable.
(the above declared constant PREFS_NAME is like the name of the config)

Now 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 an 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 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 stamp into the market

17:38 We publish.
In fact, not the easiest task. Somewhere you need to get a bunch of promotions of a certain size, so it's not hard for designers here.

According to the parameters.
- To install Russian as the main language of the application, you must first add Russian, and only after that it will be possible to remove English.
- In the descriptive and promotional text fields, it is advisable to mention the keywords by which the application can be searched.
- If you set the price free - then make it paid - it is impossible
- Do not put a copy protection checkbox, the application will not be placed on the part of the device
- It is better to specify all countries, even if the application is only for Russian speakers, for example.

17:50 Published.

Download from the market
Download source code

Hmm, with the icon it didn't turn out very nicely (For some reason this thought gnaws me. So if anyone can portray something 48 * 48 - I will be very grateful.

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


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

Source: https://habr.com/ru/post/117885/


All Articles