I have developed an app, sort of an text reader, and need to introduce a new functionality to it (users are asking for it- I've intended to incorporate it all along, just that I never figured out how to do it). I'm generating an html from the text and displaying it in a webview. I want the user to be able to select a section of the text and then open the context menu. What I want to identify is on which section he has longClicked on (each line of the html consists of pre-formatted line/section number, the line text and may be finished with a href link), and which part of it was selected. Once I figure the first part out, the second is just a matter of sorting it out.
I've tried using <span id='someuniquenumber'>
for the line text, but webView does not recognize it as an anchor*. I've also, unsuccessfully, tried to enable text selection on links (which IMHO is not a desirable option, but will resort to it, if there are no other options left).
Here's an example of the text selection problem- I want to be able to identify the selection as "line 5, words 4 through 10", or at least that the user has selected the text from the line marked as 5.
Any help would be greatly appreciated- just bear in mind that I can affect both the webView behaviour and the HTML code displayed. Thanks in advance. :)
extra=0
in type='null'
If I use tags, I do not know the way to allow text selection within that link, whereas without it I cannot identify the element user has clicked on (and the selected text may certainly not be unique, and therefore may not be searched within HTML).
EDIT
Here's the code I have regarding webView (onCreate in MainActivity):
mWebView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
int temp = url.lastIndexOf("/") + 1;
String link = url.substring(temp);
if (link.charAt(0) == 'L') {
link = link.substring(1);
LinksFragment mLinksFragment = LinksFragment.newInstance(Integer.valueOf(link),textZoom,zoomAll,sans,fragNo);
mLinksFragment.show(fm,"fragment_links");
} else if (link.charAt(0) == 'C') {
// show or add comment
} else {
// follow link
// append link to clipboard
ClipData tmp = myCB.getPrimaryClip();
if (!myCB.getPrimaryClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {
tmp = ClipData.newPlainText("text","");
myCB.setPrimaryClip(tmp);
}
ClipData.Item tmpI = tmp.getItemAt(0);
String ts = tmpI.getText().toString();
ts += link + " ";
tmp = ClipData.newPlainText("text",ts);
myCB.setPrimaryClip(tmp);
}
return true;
}
});
// detect clicked element
mWebView.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View view, MotionEvent event) {
WebView.HitTestResult hr = ((WebView)view).getHitTestResult();
Toast.makeText(MainActivity.this, "getExtra = "+ hr.getExtra() + "Type= " + hr.getType(),
Toast.LENGTH_LONG).show();
//return true;
return false;
}
});
mWebView.getSettings().setDefaultFontSize(14);
mWebView.setBackgroundColor(0x00000000);
mWebView.getSettings().setBuiltInZoomControls(true);
mWebView.getSettings().setDisplayZoomControls(false);
updateFields(); // applies custom fontface and fontsize to elements incl. mWebView
mWebView.loadDataWithBaseURL("file:///android_asset/",wV,"text/html; charset=utf-8","utf-8",null);
And here's the code for generating html inserted into the webView (a separate function fetching text, adding headings and chapter titles, formatting the text)-the actual text is stored in tmpStr variable:
public String fetchText(Bible mB, boolean append, boolean filter, String wV) {
BibleInfo.Error mE;
String tN;
char tT;
int[] Lines;
String[] Lttrs;
int[] noVerses = new int[3];
String fS1 = "<p class='ps'>";
String Psalm = getResources().getString(R.string.Psalm);
String Chapter = getResources().getString(R.string.Chapter);
boolean estExc = false;
char lttr = 'a'-1;
mE = mB.mBI.mE;
int tmp = 0;
for (int i=0; i<3; i++) {
tmp += mB.mBI.noBooks(i);
noVerses[i] = mB.mBI.getLine(tmp,1,1);
}
// separating link data
int cnt = 0;
Lines = new int[mB.mLink.getLines().length];
Lttrs = new String[mB.mLink.getLines().length];
for (String tS:mB.mLink.getLines()) {
tN = "";
Lttrs[cnt] = "";
for (tmp=0; tmp<tS.length(); tmp++) {
tT = tS.charAt(tmp);
if (tT>='0' && tT<='9') {
tN += tT;
} else {
Lttrs[cnt] += tT;
}
}
Lines[cnt++] = Integer.valueOf(tN);
}
if (!append) {
wV = "";
}
noVerses = mB.mBI.getTriLink(Lines[0],mE);
if ((noVerses[1] == 0 || (noVerses[1] == 1 && noVerses[0] != mB.mBI.getSirach())) && mB.mLink.isLong()) {
if (mB.mBI.getTitles() != null) {
wV += "<h1>" + mB.mBI.getTitles()[noVerses[0]] + "</h1>";
}
}
for (int i=0; i<Lines.length; i++) {
int tX = Lines[i];
int[] temp = mB.mBI.getTriLink(tX, mE);
if (temp[2] == 1 && mB.mLink.isLong() && temp[1] != 0) {
if (temp[0] == mB.mBI.getPsalms()) {
wV += "<h2>" + Psalm + " " + temp[1] + "</h2>";
} else {
wV += "<h2>" + Chapter + " " + temp[1] + "</h2>";
}
}
String tmpStr = mB.getLineText(tX - 1,filter);
if (noVerses[0] == mB.mBI.getPsalms()) {
wV += fS1;
if (Lttrs[i] != "") {
tmpStr = parseVerse(tmpStr,Lttrs[i]);
}
tmpStr = tmpStr.replace(mB.mBI.mSeparator, "<br>");
} else {
wV += "<p>";
if (noVerses[0] == mB.mBI.getEsther()) {
int noBrks = 0;
int lastOccurrence = 0;
if ((temp[1]==1) & (temp[2]==1)) {
estExc = true;
while (lastOccurrence != -1){
lastOccurrence = tmpStr.indexOf(mB.mBI.mSeparator,lastOccurrence);
String tStr = "<br><sup>" + temp[2] + (char)(98+noBrks) + "</sup> ";
if (lastOccurrence != -1) {
if (noBrks == 0) {
tmpStr = "<span id='apoch'>" + tmpStr;
}
tmpStr = tmpStr.replaceFirst("\\|", tStr);
noBrks +=1;
}
}
if (tmpStr.lastIndexOf(mB.mBI.mSeparator) != -1) {
tmpStr += "</span>";
}
// remove character before last </sup>
} else {
estExc = false;
while (lastOccurrence != -1){
lastOccurrence = tmpStr.indexOf(mB.mBI.mSeparator,lastOccurrence);
String tStr = "<br><sup>" + temp[2] + (char)(97+noBrks) + "</sup> ";
if (lastOccurrence != -1) {
if (noBrks == 0) {
tmpStr = "<span id='apoch'>" + tmpStr;
}
tmpStr = tmpStr.replaceFirst("\\|", tStr);
noBrks +=1;
}
}
if (noBrks != 0) {
tmpStr += "</span>";
}
tmpStr = tmpStr.replaceFirst("<span id='apoch'>","");
tmpStr = tmpStr.replaceFirst("<br>","<br><span id='apoch'>");
}
}
}
// add hyperlink for links
if (!TextUtils.isEmpty(mB.getLinks()[tX-1])) {
lttr++;
if (lttr>'z') { lttr = 'a'; }
String ts= "<sup><span id='links'><a href='L" + (tX-1) + "'>" + lttr;
ts += "</a></span></sup>";
tmpStr += ts;
}
if (estExc) {
wV += "<sup>" + temp[2] + "a</sup> " + tmpStr + "</p>";
} else {
wV += "<sup>" + temp[2] + "</sup> " + tmpStr + "</p>";
}
}
wV = fS2 + wV;
return wV;
}
Calls to log events or set user properties fired from within a WebView must be forwarded to native code before they can be sent to Google Analytics for Firebase.
Implement JavaScript handler
The first step in using Google Analytics for Firebase in a WebView is to create JavaScript functions to forward events and user properties to native code. The following example shows how to do this in a way that is compatible with both Android and iOS native code
function logEvent(name, params) {
if (!name) {
return;
}
if (window.AnalyticsWebInterface) {
// Call Android interface
window.AnalyticsWebInterface.logEvent(name, JSON.stringify(params));
} else if (window.webkit
&& window.webkit.messageHandlers
&& window.webkit.messageHandlers.firebase) {
// Call iOS interface
var message = {
command: 'logEvent',
name: name,
parameters: params
};
window.webkit.messageHandlers.firebase.postMessage(message);
} else {
// No Android or iOS interface found
console.log("No native APIs found.");
}
}
function setUserProperty(name, value) {
if (!name || !value) {
return;
}
if (window.AnalyticsWebInterface) {
// Call Android interface
window.AnalyticsWebInterface.setUserProperty(name, value);
} else if (window.webkit
&& window.webkit.messageHandlers
&& window.webkit.messageHandlers.firebase) {
// Call iOS interface
var message = {
command: 'setUserProperty',
name: name,
value: value
};
window.webkit.messageHandlers.firebase.postMessage(message);
} else {
// No Android or iOS interface found
console.log("No native APIs found.");
}
}
Implement native interface
public class AnalyticsWebInterface {
public static final String TAG = "AnalyticsWebInterface";
private FirebaseAnalytics mAnalytics;
public AnalyticsWebInterface(Context context) {
mAnalytics = FirebaseAnalytics.getInstance(context);
}
@JavascriptInterface
public void logEvent(String name, String jsonParams) {
LOGD("logEvent:" + name);
mAnalytics.logEvent(name, bundleFromJson(jsonParams));
}
@JavascriptInterface
public void setUserProperty(String name, String value) {
LOGD("setUserProperty:" + name);
mAnalytics.setUserProperty(name, value);
}
private void LOGD(String message) {
// Only log on debug builds, for privacy
if (BuildConfig.DEBUG) {
Log.d(TAG, message);
}
}
private Bundle bundleFromJson(String json) {
// ...
}
}
Once you have created the native interface, register it with your WebView so that it is visible to Javascript code running in the WebView:
// Only add the JavaScriptInterface on API version JELLY_BEAN_MR1 and above, due to
// security concerns, see link below for more information:
// https://developer.android.com/reference/android/webkit/WebView.html#addJavascriptInterface(java.lang.Object,%20java.lang.String)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
mWebView.addJavascriptInterface(
new AnalyticsWebInterface(this), AnalyticsWebInterface.TAG);
} else {
Log.w(TAG, "Not adding JavaScriptInterface, API Version: " + Build.VERSION.SDK_INT);
}
Source: Firebase
User contributions licensed under CC BY-SA 3.0