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.

image

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. :)

  • event handler returns get 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>&#x2005;";
                            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>&#x2005;";
                            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>&#x2005;" + tmpStr + "</p>";
            } else {
                wV += "<sup>" + temp[2] + "</sup>&#x2005;" + tmpStr + "</p>";
            }
        }
        wV = fS2 + wV;

        return wV;
    }

1 Answers

0
PSN On

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