Partial Domain implementation.
authorSoren Stoutner <soren@stoutner.com>
Sat, 4 Mar 2017 03:19:59 +0000 (20:19 -0700)
committerSoren Stoutner <soren@stoutner.com>
Sat, 4 Mar 2017 03:19:59 +0000 (20:19 -0700)
31 files changed:
app/build.gradle
app/src/main/AndroidManifest.xml
app/src/main/java/com/stoutner/privacybrowser/activities/Bookmarks.java
app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksDatabaseView.java
app/src/main/java/com/stoutner/privacybrowser/activities/DomainsList.java
app/src/main/java/com/stoutner/privacybrowser/activities/MainWebView.java
app/src/main/java/com/stoutner/privacybrowser/dialogs/AddDomain.java [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmark.java
app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmarkFolder.java
app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateHomeScreenShortcut.java
app/src/main/java/com/stoutner/privacybrowser/dialogs/DownloadFile.java
app/src/main/java/com/stoutner/privacybrowser/dialogs/DownloadImage.java
app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmark.java
app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkFolder.java
app/src/main/java/com/stoutner/privacybrowser/dialogs/MoveToFolder.java
app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateError.java
app/src/main/java/com/stoutner/privacybrowser/dialogs/UrlHistory.java
app/src/main/java/com/stoutner/privacybrowser/dialogs/ViewSslCertificate.java
app/src/main/java/com/stoutner/privacybrowser/helpers/BookmarksDatabaseHelper.java
app/src/main/java/com/stoutner/privacybrowser/helpers/DomainsDatabaseHelper.java [new file with mode: 0644]
app/src/main/res/drawable/domains_list_selector.xml [new file with mode: 0644]
app/src/main/res/layout-w900dp/domains_list.xml
app/src/main/res/layout/add_domain_dialog.xml [new file with mode: 0644]
app/src/main/res/layout/create_bookmark_dialog.xml
app/src/main/res/layout/domain_details.xml [new file with mode: 0644]
app/src/main/res/layout/domain_name_linearlayout.xml [new file with mode: 0644]
app/src/main/res/layout/domains_list.xml
app/src/main/res/layout/domains_list_coordinatorlayout.xml
app/src/main/res/values/strings.xml
build.gradle
gradle/wrapper/gradle-wrapper.properties

index 72d7b3a..c1a932f 100644 (file)
@@ -51,14 +51,11 @@ android {
             applicationId "com.stoutner.privacybrowser.free"
         }
     }
-
-    // `return void` removes the lint error: `Not all execution paths return a value`.
-    return void
 }
 
 dependencies {
     compile fileTree(include: ['*.jar'], dir: 'libs')
-    compile 'com.android.support:design:25.1.1'
+    compile 'com.android.support:design:25.2.0'
 
     // Only compile `com.google.firebase:firebase-ads:9.8.0` for the free flavor.
     freeCompile 'com.google.firebase:firebase-ads:9.8.0'
index d19b67e..616c7ba 100644 (file)
@@ -42,8 +42,7 @@
         android:fullBackupContent="false"
         android:supportsRtl="true" >
 
-        <!-- If `android:name="android.webkit.WebView.MetricsOptOut"` is not `true` then `WebViews` will upload metrics to Google.
-            https://developer.android.com/reference/android/webkit/WebView.html -->
+        <!-- If `android:name="android.webkit.WebView.MetricsOptOut"` is not `true` then `WebViews` will upload metrics to Google.  <https://developer.android.com/reference/android/webkit/WebView.html> -->
         <meta-data
             android:name="android.webkit.WebView.MetricsOptOut"
             android:value="true" />
             android:persistableMode="persistNever"
             tools:ignore="UnusedAttribute" />
 
-        <!-- `android:configChanges="orientation|screenSize"` makes the activity not reload when the orientation changes.
-            `android:persistableMode="persistNever"` removes Privacy Browser from the recents screen on a device reboot.
+        <!-- `android:persistableMode="persistNever"` removes Privacy Browser from the recents screen on a device reboot.
             `tools:ignore="unusedAttribute"` removes the lint warning that `persistableMode` does not apply to API < 21. -->
         <activity
             android:name=".activities.DomainsList"
             android:label="@string/domains"
             android:theme="@style/PrivacyBrowser.SecondaryActivity"
             android:parentActivityName=".activities.MainWebView"
-            android:configChanges="orientation|screenSize"
             android:screenOrientation="fullUser"
             android:persistableMode="persistNever"
             tools:ignore="UnusedAttribute" />
index 27a9afe..03ee0d1 100644 (file)
@@ -1,5 +1,5 @@
-/**
- * Copyright 2016 Soren Stoutner <soren@stoutner.com>.
+/*
+ * Copyright 2016-2017 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
@@ -61,9 +61,8 @@ import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolder;
 
 import java.io.ByteArrayOutputStream;
 
-public class Bookmarks extends AppCompatActivity implements CreateBookmark.CreateBookmarkListener,
-        CreateBookmarkFolder.CreateBookmarkFolderListener, EditBookmark.EditBookmarkListener,
-        EditBookmarkFolder.EditBookmarkFolderListener, MoveToFolder.MoveToFolderListener {
+public class Bookmarks extends AppCompatActivity implements CreateBookmark.CreateBookmarkListener, CreateBookmarkFolder.CreateBookmarkFolderListener, EditBookmark.EditBookmarkListener, EditBookmarkFolder.EditBookmarkFolderListener,
+        MoveToFolder.MoveToFolderListener {
 
     // `bookmarksDatabaseHelper` is public static so it can be accessed from `EditBookmark` and `MoveToFolder`.  It is also used in `onCreate()`,
     // `onCreateBookmarkCreate()`, `updateBookmarksListView()`, and `updateBookmarksListViewExcept()`.
@@ -111,12 +110,12 @@ public class Bookmarks extends AppCompatActivity implements CreateBookmark.Creat
         appBar.setDisplayHomeAsUpEnabled(true);
 
 
-        // Initialize the database handler and the ListView.  `this` specifies the context.  The two `null`s do not specify the database name or a `CursorFactory`.
-        // The `0` is to specify a database version, but that is set instead using a constant in `BookmarksDatabaseHelper`.
+        // Initialize the database handler and the `ListView`.  `this` specifies the context.  The two `nulls` do not specify the database name or a `CursorFactory`.
+        // The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
         bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this, null, null, 0);
         bookmarksListView = (ListView) findViewById(R.id.bookmarks_listview);
 
-        // Set currentFolder to the home folder, which is null in the database.
+        // Set currentFolder to the home folder, which is `""` in the database.
         currentFolder = "";
 
         // Display the bookmarks in the ListView.
@@ -130,7 +129,7 @@ public class Bookmarks extends AppCompatActivity implements CreateBookmark.Creat
                 // Convert the id from long to int to match the format of the bookmarks database.
                 int databaseID = (int) id;
 
-                // Get the bookmark `Cursor` and move it to the first row.
+                // Get the bookmark `Cursor` for this ID and move it to the first row.
                 Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmarkCursor(databaseID);
                 bookmarkCursor.moveToFirst();
 
@@ -572,10 +571,12 @@ public class Bookmarks extends AppCompatActivity implements CreateBookmark.Creat
 
     @Override
     public void onCreateBookmark(AppCompatDialogFragment dialogFragment) {
-        // Get the `EditText`s from the `createBookmarkDialogFragment` and extract the strings.
+        // Get the `EditTexts` from the `dialogFragment`.
         EditText createBookmarkNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.create_bookmark_name_edittext);
-        String bookmarkNameString = createBookmarkNameEditText.getText().toString();
         EditText createBookmarkUrlEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.create_bookmark_url_edittext);
+
+        // Extract the strings from the `EditTexts`.
+        String bookmarkNameString = createBookmarkNameEditText.getText().toString();
         String bookmarkUrlString = createBookmarkUrlEditText.getText().toString();
 
         // Convert the favoriteIcon Bitmap to a byte array.
@@ -590,7 +591,7 @@ public class Bookmarks extends AppCompatActivity implements CreateBookmark.Creat
         // Create the bookmark.
         bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, newBookmarkDisplayOrder, currentFolder, favoriteIconByteArray);
 
-        // Refresh the ListView.  `setSelection` scrolls to the bottom of the list.
+        // Refresh the `ListView`.  `setSelection` scrolls to the bottom of the list.
         updateBookmarksListView(currentFolder);
         bookmarksListView.setSelection(newBookmarkDisplayOrder);
     }
@@ -782,7 +783,7 @@ public class Bookmarks extends AppCompatActivity implements CreateBookmark.Creat
         // Get a `Cursor` with the current contents of the bookmarks database.
         bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(folderName);
 
-        // Setup `bookmarksCursorAdapter` with `this` context.  `false` disables autoRequery.
+        // Setup `bookmarksCursorAdapter` with `this` context.  `false` disables `autoRequery`.
         CursorAdapter bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
             @Override
             public View newView(Context context, Cursor cursor, ViewGroup parent) {
@@ -868,7 +869,7 @@ public class Bookmarks extends AppCompatActivity implements CreateBookmark.Creat
             }
         };
 
-        // Update the ListView.
+        // Update the `ListView`.
         bookmarksListView.setAdapter(bookmarksCursorAdapter);
     }
 
index 5a3ccf0..44f23bf 100644 (file)
@@ -1,5 +1,5 @@
-/**
- * Copyright 2016 Soren Stoutner <soren@stoutner.com>.
+/*
+ * Copyright 2016-2017 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
index 3cf13f2..fb994f4 100644 (file)
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright 2017 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
 
 package com.stoutner.privacybrowser.activities;
 
+import android.content.Context;
+import android.database.Cursor;
 import android.os.Bundle;
+import android.support.design.widget.FloatingActionButton;
+import android.support.v7.app.ActionBar;
 import android.support.v7.app.AppCompatActivity;
+import android.support.v7.app.AppCompatDialogFragment;
+import android.support.v7.widget.Toolbar;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.CursorAdapter;
+import android.widget.EditText;
+import android.widget.ListView;
+import android.widget.TextView;
 
 import com.stoutner.privacybrowser.R;
+import com.stoutner.privacybrowser.dialogs.AddDomain;
+import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
+
+public class DomainsList extends AppCompatActivity implements AddDomain.AddDomainListener {
+    // `domainsDatabaseHelper` is used in `onCreate()`, `onAddDomain()`, and `updateDomainsRecyclerView()`.
+    private static DomainsDatabaseHelper domainsDatabaseHelper;
+
+    // `domainsRecyclerView` is used in `onCreate()` and `updateDomainsListView()`.
+    private ListView domainsListView;
 
-public class DomainsList extends AppCompatActivity {
     private boolean twoPaneMode;
 
     @Override
@@ -32,5 +53,88 @@ public class DomainsList extends AppCompatActivity {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.domains_list_coordinatorlayout);
 
+        // We need to use the `SupportActionBar` from `android.support.v7.app.ActionBar` until the minimum API is >= 21.
+        final Toolbar bookmarksAppBar = (Toolbar) findViewById(R.id.domains_toolbar);
+        setSupportActionBar(bookmarksAppBar);
+
+        // Display the home arrow on `SupportActionBar`.
+        ActionBar appBar = getSupportActionBar();
+        assert appBar != null;// This assert removes the incorrect warning in Android Studio on the following line that `appBar` might be null.
+        appBar.setDisplayHomeAsUpEnabled(true);
+
+        // Initialize the database handler.  `this` specifies the context.  The two `nulls` do not specify the database name or a `CursorFactory`.
+        // The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
+        domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
+
+        // Determine if we are in two pane mode.  `domains_list_framelayout` is only populated if two panes are present.
+        twoPaneMode = ((findViewById(R.id.domains_list_framelayout)) != null);
+
+        // Initialize `domainsListView`.
+        domainsListView = (ListView) findViewById(R.id.domains_listview);
+
+        domainsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+            @Override
+            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+                // Convert the id from long to int to match the format of the domains database.
+                int databaseId = (int) id;
+
+                // Get the database `Cursor` for this ID and move it to the first row.
+                Cursor domainCursor = domainsDatabaseHelper.getCursorForId(databaseId);
+                domainCursor.moveToFirst();
+
+                // If the
+            }
+        });
+
+        FloatingActionButton addDomainFAB = (FloatingActionButton) findViewById(R.id.add_domain_fab);
+        addDomainFAB.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                // Show the `AddDomain` `AlertDialog` and name the instance `@string/add_domain`.
+                AppCompatDialogFragment addDomainDialog = new AddDomain();
+                addDomainDialog.show(getSupportFragmentManager(), getResources().getString(R.string.add_domain));
+            }
+        });
+
+        // Load the `ListView`.
+        updateDomainsListView();
+    }
+
+    @Override
+    public void onAddDomain(AppCompatDialogFragment dialogFragment) {
+        // Get the `domainNameEditText` from `dialogFragment` and extract the string.
+        EditText domainNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.domain_name_edittext);
+        String domainNameString = domainNameEditText.getText().toString();
+
+        // Create the domain.
+        domainsDatabaseHelper.addDomain(domainNameString);
+
+        // Refresh the `ListView`.
+        updateDomainsListView();
+    }
+
+    private void updateDomainsListView() {
+        // Get a `Cursor` with the current contents of the domains database.
+        Cursor domainsCursor = domainsDatabaseHelper.getCursorOrderedByDomain();
+
+        // Setup `domainsCursorAdapter` with `this` context.  `false` disables `autoRequery`.
+        CursorAdapter domainsCursorAdapter = new CursorAdapter(this, domainsCursor, false) {
+            @Override
+            public View newView(Context context, Cursor cursor, ViewGroup parent) {
+                // Inflate the individual item layout.  `false` does not attach it to the root.
+                return getLayoutInflater().inflate(R.layout.domain_name_linearlayout, parent, false);
+            }
+
+            @Override
+            public void bindView(View view, Context context, Cursor cursor) {
+                // Set the domain name.
+                String domainNameString = cursor.getString(cursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN));
+                TextView domainNameTextView = (TextView) view.findViewById(R.id.domain_name_textview);
+                domainNameTextView.setText(domainNameString);
+            }
+        };
+
+        // Update the `RecyclerView`.
+        domainsListView.setAdapter(domainsCursorAdapter);
     }
 }
\ No newline at end of file
index 1939711..e6e5304 100644 (file)
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright 2015-2017 Soren Stoutner <soren@stoutner.com>.
  *
  * Download cookie code contributed 2017 Hendrik Knackstedt.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/AddDomain.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/AddDomain.java
new file mode 100644 (file)
index 0000000..c176633
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2017 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+ *
+ * Privacy Browser is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Privacy Browser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.dialogs;
+
+import android.annotation.SuppressLint;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <= 22.
+import android.support.annotation.NonNull;
+import android.support.v7.app.AppCompatDialogFragment;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.EditText;
+
+import com.stoutner.privacybrowser.R;
+
+public class AddDomain extends AppCompatDialogFragment {
+    // The public interface is used to send information back to the parent activity.
+    public interface AddDomainListener {
+        void onAddDomain(AppCompatDialogFragment dialogFragment);
+    }
+
+    // `addDomainListener` is used in `onAttach()` and `onCreateDialog()`.
+    private AddDomainListener addDomainListener;
+
+
+    public void onAttach(Context context) {
+        super.onAttach(context);
+
+        // Get a handle for `AddDomainListener` from `context`.
+        try {
+            addDomainListener = (AddDomainListener) context;
+        } catch(ClassCastException exception) {
+            throw new ClassCastException(context.toString() + " must implement `AddDomainListener`.");
+        }
+    }
+
+    // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`.
+    @SuppressLint("InflateParams")
+    @Override
+    @NonNull
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        // Use `AlertDialog.Builder` to create the `AlertDialog`.  The style formats the color of the button text.
+        AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.LightAlertDialog);
+        dialogBuilder.setTitle(R.string.add_domain);
+        // The parent view is `null` because it will be assigned by the `AlertDialog`.
+        dialogBuilder.setView(getActivity().getLayoutInflater().inflate(R.layout.add_domain_dialog, null));
+
+        // Set an `onClick()` listener for the negative button.
+        dialogBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                // Do nothing.  The `AlertDialog` will close automatically.
+            }
+        });
+
+        // Set an `onClick()` listener for the positive button.
+        dialogBuilder.setPositiveButton(R.string.add, new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                // Return the `DialogFragment` to the parent activity on add.
+                addDomainListener.onAddDomain(AddDomain.this);
+            }
+        });
+
+        // Create an `AlertDialog` from the `AlertDialog.Builder`.
+        final AlertDialog alertDialog = dialogBuilder.create();
+
+        // Remove the warning below the `setSoftInputMode` might produce `java.lang.NullPointerException`.
+        assert alertDialog.getWindow() != null;
+
+        // Show the keyboard when the `AlertDialog` is displayed on the screen.
+        alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+
+        // We need to show the `AlertDialog` before w3e can call `setOnKeyListener()` below.
+        alertDialog.show();
+
+        // Allow the `enter` key on the keyboard to create the domain from `add_domain_edittext`.
+        EditText addDomainEditText = (EditText) alertDialog.findViewById(R.id.domain_name_edittext);
+        addDomainEditText.setOnKeyListener(new View.OnKeyListener() {
+            public boolean onKey(View view, int keyCode, KeyEvent event) {
+                // If the event is a key-down on the `enter` key, select the `PositiveButton` `Add`.
+                if ((keyCode == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) {
+                    // Trigger `addDomainListener` and return the `DialogFragment` to the parent activity.
+                    addDomainListener.onAddDomain(AddDomain.this);
+                    // Manually dismiss the `AlertDialog`.
+                    alertDialog.dismiss();
+                    // Consume the event.
+                    return true;
+                } else { // If any other key was pressed, do not consume the event.
+                    return false;
+                }
+            }
+        });
+
+        // `onCreateDialog()` requires the return of an `AlertDialog`.
+        return alertDialog;
+    }
+}
index 77c5860..5ac074b 100644 (file)
@@ -1,5 +1,5 @@
-/**
- * Copyright 2016 Soren Stoutner <soren@stoutner.com>.
+/*
+ * Copyright 2016-2017 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
@@ -20,6 +20,7 @@
 package com.stoutner.privacybrowser.dialogs;
 
 import android.annotation.SuppressLint;
+import android.app.AlertDialog;
 import android.app.Dialog;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -27,9 +28,7 @@ import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
-// If we don't use `android.support.v7.app.AlertDialog` instead of `android.app.AlertDialog` then the dialog will be covered by the keyboard.
-import android.support.v7.app.AlertDialog;
-// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <=22.
+// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <= 22.
 import android.support.v7.app.AppCompatDialogFragment;
 import android.view.KeyEvent;
 import android.view.View;
@@ -56,7 +55,7 @@ public class CreateBookmark extends AppCompatDialogFragment {
         try {
             createBookmarkListener = (CreateBookmarkListener) context;
         } catch(ClassCastException exception) {
-            throw new ClassCastException(context.toString() + " must implement CreateBookmarkListener.");
+            throw new ClassCastException(context.toString() + " must implement `CreateBookmarkListener`.");
         }
     }
 
@@ -99,7 +98,7 @@ public class CreateBookmark extends AppCompatDialogFragment {
         // Remove the warning below that `setSoftInputMode` might produce `java.lang.NullPointerException`.
         assert alertDialog.getWindow() != null;
 
-        // Show the keyboard when the `Dialog` is displayed on the screen.
+        // Show the keyboard when the `AlertDialog` is displayed on the screen.
         alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
 
         // We need to show the `AlertDialog` before we can call `setOnKeyListener()` below.
@@ -109,9 +108,9 @@ public class CreateBookmark extends AppCompatDialogFragment {
         EditText createBookmarkNameEditText = (EditText) alertDialog.findViewById(R.id.create_bookmark_name_edittext);
         assert createBookmarkNameEditText != null;  // Remove the warning below that `createBookmarkNameEditText` might be `null`.
         createBookmarkNameEditText.setOnKeyListener(new View.OnKeyListener() {
-            public boolean onKey(View v, int keyCode, KeyEvent event) {
-                // If the event is a key-down on the `enter` button, select the `PositiveButton` `Create`.
-                if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
+            public boolean onKey(View view, int keyCode, KeyEvent event) {
+                // If the event is a key-down on the `enter` key, select the `PositiveButton` `Create`.
+                if ((keyCode == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) {
                     // Trigger `createBookmarkListener` and return the `DialogFragment` to the parent activity.
                     createBookmarkListener.onCreateBookmark(CreateBookmark.this);
                     // Manually dismiss the `AlertDialog`.
@@ -132,8 +131,8 @@ public class CreateBookmark extends AppCompatDialogFragment {
         // Allow the `enter` key on the keyboard to create the bookmark from `create_bookmark_url_edittext`.
         createBookmarkUrlEditText.setOnKeyListener(new View.OnKeyListener() {
             public boolean onKey(View v, int keyCode, KeyEvent event) {
-                // If the event is a key-down on the "enter" button, select the PositiveButton "Create".
-                if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
+                // If the event is a key-down on the "enter" key, select the PositiveButton "Create".
+                if ((keyCode == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) {
                     // Trigger `createBookmarkListener` and return the DialogFragment to the parent activity.
                     createBookmarkListener.onCreateBookmark(CreateBookmark.this);
                     // Manually dismiss the `AlertDialog`.
index e3afd11..606915c 100644 (file)
@@ -1,5 +1,5 @@
-/**
- * Copyright 2016 Soren Stoutner <soren@stoutner.com>.
+/*
+ * Copyright 2016-2017 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
 package com.stoutner.privacybrowser.dialogs;
 
 import android.annotation.SuppressLint;
+import android.app.AlertDialog;
 import android.app.Dialog;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
-// If we don't use `android.support.v7.app.AlertDialog` instead of `android.app.AlertDialog` then the dialog will be covered by the keyboard.
-import android.support.v7.app.AlertDialog;
 // We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <=22.
 import android.support.v7.app.AppCompatDialogFragment;
 import android.view.KeyEvent;
index d55f7ff..50a3aef 100644 (file)
@@ -1,5 +1,5 @@
-/**
- * Copyright 2015-2016 Soren Stoutner <soren@stoutner.com>.
+/*
+ * Copyright 2015-2017 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
@@ -20,6 +20,7 @@
 package com.stoutner.privacybrowser.dialogs;
 
 import android.annotation.SuppressLint;
+import android.app.AlertDialog;
 import android.app.Dialog;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -27,8 +28,6 @@ import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
-// If we don't use android.support.v7.app.AlertDialog instead of android.app.AlertDialog then the dialog will be covered by the keyboard.
-import android.support.v7.app.AlertDialog;
 // We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <=22.
 import android.support.v7.app.AppCompatDialogFragment;
 import android.view.KeyEvent;
index fe2048a..e4c2156 100644 (file)
@@ -1,5 +1,5 @@
-/**
- * Copyright 2016 Soren Stoutner <soren@stoutner.com>.
+/*
+ * Copyright 2016-2017 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
 package com.stoutner.privacybrowser.dialogs;
 
 import android.annotation.SuppressLint;
+import android.app.AlertDialog;
 import android.app.Dialog;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.net.Uri;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
-// `android.support.v7.app.AlertDialog` uses more of the horizontal screen real estate versus `android.app.AlertDialog's` smaller width.
-import android.support.v7.app.AlertDialog;
 // We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <=22.
 import android.support.v7.app.AppCompatDialogFragment;
 import android.view.KeyEvent;
index ceb0d62..8de6898 100644 (file)
@@ -1,5 +1,5 @@
-/**
- * Copyright 2016 Soren Stoutner <soren@stoutner.com>.
+/*
+ * Copyright 2016-2017 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
 package com.stoutner.privacybrowser.dialogs;
 
 import android.annotation.SuppressLint;
+import android.app.AlertDialog;
 import android.app.Dialog;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.net.Uri;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
-import android.support.v7.app.AlertDialog;
+// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <= 22.
 import android.support.v7.app.AppCompatDialogFragment;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
index a02a4de..b502ca8 100644 (file)
@@ -1,5 +1,5 @@
-/**
- * Copyright 2016 Soren Stoutner <soren@stoutner.com>.
+/*
+ * Copyright 2016-2017 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
@@ -20,6 +20,7 @@
 package com.stoutner.privacybrowser.dialogs;
 
 import android.annotation.SuppressLint;
+import android.app.AlertDialog;
 import android.app.Dialog;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -27,9 +28,7 @@ import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.os.Bundle;
-// If we don't use `android.support.v7.app.AlertDialog` instead of `android.app.AlertDialog` then the dialog will be covered by the keyboard.
 import android.support.annotation.NonNull;
-import android.support.v7.app.AlertDialog;
 // We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <=22.
 import android.support.v7.app.AppCompatDialogFragment;
 import android.view.KeyEvent;
index 74bd890..77726eb 100644 (file)
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright 2016 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
@@ -20,6 +20,7 @@
 package com.stoutner.privacybrowser.dialogs;
 
 import android.annotation.SuppressLint;
+import android.app.AlertDialog;
 import android.app.Dialog;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -28,8 +29,6 @@ import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
-// If we don't use `android.support.v7.app.AlertDialog` instead of `android.app.AlertDialog` then the dialog will be covered by the keyboard.
-import android.support.v7.app.AlertDialog;
 // We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <=22.
 import android.support.v7.app.AppCompatDialogFragment;
 import android.view.KeyEvent;
index 38af5f2..217281c 100644 (file)
@@ -1,5 +1,5 @@
-/**
- * Copyright 2016 Soren Stoutner <soren@stoutner.com>.
+/*
+ * Copyright 2016-2017 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
@@ -20,6 +20,7 @@
 package com.stoutner.privacybrowser.dialogs;
 
 import android.annotation.SuppressLint;
+import android.app.AlertDialog;
 import android.app.Dialog;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -33,10 +34,8 @@ import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
-// If we don't use `android.support.v7.app.AlertDialog` instead of `android.app.AlertDialog` then the dialog will be covered by the keyboard.
 import android.support.v4.content.ContextCompat;
 // We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <=22.
-import android.support.v7.app.AlertDialog;
 import android.support.v7.app.AppCompatDialogFragment;
 import android.view.View;
 import android.view.ViewGroup;
index 10afe62..917b302 100644 (file)
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright 2016-2017 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
@@ -28,6 +28,7 @@ import android.net.http.SslCertificate;
 import android.net.http.SslError;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
+// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <= 22.
 import android.support.v7.app.AppCompatDialogFragment;
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
index 46a7bbb..a362b70 100644 (file)
@@ -1,5 +1,5 @@
-/**
- * Copyright 2016 Soren Stoutner <soren@stoutner.com>.
+/*
+ * Copyright 2016-2017 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
@@ -20,6 +20,7 @@
 package com.stoutner.privacybrowser.dialogs;
 
 import android.annotation.SuppressLint;
+import android.app.AlertDialog;
 import android.app.Dialog;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -28,10 +29,9 @@ import android.graphics.BitmapFactory;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
-// `android.support.v7.app.AlertDialog` uses more of the horizontal screen real estate versus `android.app.AlertDialog's` smaller width.
 import android.support.annotation.NonNull;
 import android.support.v4.content.ContextCompat;
-import android.support.v7.app.AlertDialog;
+// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <= 22.  `android.support.v7.app.AlertDialog` also uses more of the horizontal screen real estate versus `android.app.AlertDialog's` smaller width.
 import android.support.v7.app.AppCompatDialogFragment;
 import android.util.Base64;
 import android.view.LayoutInflater;
index b675c45..4b16ab1 100644 (file)
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright 2016-2017 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
index 051da66..ccd420e 100644 (file)
@@ -1,5 +1,5 @@
-/**
- * Copyright 2016 Soren Stoutner <soren@stoutner.com>.
+/*
+ * Copyright 2016-2017 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
@@ -40,14 +40,14 @@ public class BookmarksDatabaseHelper extends SQLiteOpenHelper {
     public static final String FAVORITE_ICON = "favoriteicon";
 
     // Initialize the database.  The lint warnings for the unused parameters are suppressed.
-    public BookmarksDatabaseHelper(Context context, @SuppressWarnings("UnusedParameters") String name, SQLiteDatabase.CursorFactory factory, @SuppressWarnings("UnusedParameters") int version) {
-        super(context, BOOKMARKS_DATABASE, factory, SCHEMA_VERSION);
+    public BookmarksDatabaseHelper(Context context, @SuppressWarnings("UnusedParameters") String name, SQLiteDatabase.CursorFactory cursorFactory, @SuppressWarnings("UnusedParameters") int version) {
+        super(context, BOOKMARKS_DATABASE, cursorFactory, SCHEMA_VERSION);
     }
 
     @Override
     public void onCreate(SQLiteDatabase bookmarksDatabase) {
-        // Create the database if it doesn't exist.
-        String CREATE_BOOKMARKS_TABLE = "CREATE TABLE " + BOOKMARKS_TABLE + " (" +
+        // Setup the SQL string to create the `bookmarks` table.
+        final String CREATE_BOOKMARKS_TABLE = "CREATE TABLE " + BOOKMARKS_TABLE + " (" +
                 _ID + " integer primary key, " +
                 DISPLAY_ORDER + " integer, " +
                 BOOKMARK_NAME + " text, " +
@@ -56,6 +56,7 @@ public class BookmarksDatabaseHelper extends SQLiteOpenHelper {
                 IS_FOLDER + " boolean, " +
                 FAVORITE_ICON + " blob);";
 
+        // Create the `bookmarks` table if it doesn't exist.
         bookmarksDatabase.execSQL(CREATE_BOOKMARKS_TABLE);
     }
 
@@ -65,6 +66,7 @@ public class BookmarksDatabaseHelper extends SQLiteOpenHelper {
     }
 
     public void createBookmark(String bookmarkName, String bookmarkURL, int displayOrder, String parentFolder, byte[] favoriteIcon) {
+        // We need to store the bookmark data in a `ContentValues`.
         ContentValues bookmarkContentValues = new ContentValues();
 
         // ID is created automatically.
@@ -78,7 +80,7 @@ public class BookmarksDatabaseHelper extends SQLiteOpenHelper {
         // Get a writable database handle.
         SQLiteDatabase bookmarksDatabase = this.getWritableDatabase();
 
-        // The second argument is `null`, which makes it so that completely null rows cannot be created.  Not a problem in our case.
+        // Insert a new row.  The second argument is `null`, which makes it so that a completely null row cannot be created.
         bookmarksDatabase.insert(BOOKMARKS_TABLE, null, bookmarkContentValues);
 
         // Close the database handle.
@@ -113,8 +115,7 @@ public class BookmarksDatabaseHelper extends SQLiteOpenHelper {
         final String GET_ONE_BOOKMARK = "Select * FROM " + BOOKMARKS_TABLE +
                 " WHERE " + _ID + " = " + databaseId;
 
-        // Return the results as a `Cursor`.  The second argument is `null` because there are no `selectionArgs`.
-        // We can't close the `Cursor` because we need to use it in the parent activity.
+        // Return the results as a `Cursor`.  The second argument is `null` because there are no `selectionArgs`.  We can't close the `Cursor` because we need to use it in the parent activity.
         return bookmarksDatabase.rawQuery(GET_ONE_BOOKMARK, null);
     }
 
@@ -219,7 +220,7 @@ public class BookmarksDatabaseHelper extends SQLiteOpenHelper {
         // Get a readable database handle.
         SQLiteDatabase bookmarksDatabase = this.getReadableDatabase();
 
-        // Get everything in the BOOKMARKS_TABLE.
+        // Get everything in `BOOKMARKS_TABLE`.
         final String GET_ALL_BOOKMARKS = "Select * FROM " + BOOKMARKS_TABLE;
 
         // Return the results as a Cursor.  The second argument is `null` because there are no selectionArgs.
@@ -234,13 +235,12 @@ public class BookmarksDatabaseHelper extends SQLiteOpenHelper {
         // SQL escape `folderName`.
         folderName = DatabaseUtils.sqlEscapeString(folderName);
 
-        // Get everything in the BOOKMARKS_TABLE.
+        // Get everything in the `BOOKMARKS_TABLE` with `folderName` as the `PARENT_FOLDER`.
         final String GET_ALL_BOOKMARKS = "Select * FROM " + BOOKMARKS_TABLE +
                 " WHERE " + PARENT_FOLDER + " = " + folderName +
                 " ORDER BY " + DISPLAY_ORDER + " ASC";
 
-        // Return the results as a Cursor.  The second argument is `null` because there are no selectionArgs.
-        // We can't close the Cursor because we need to use it in the parent activity.
+        // Return the results as a `Cursor`.  The second argument is `null` because there are no `selectionArgs`.  We can't close the `Cursor` because we need to use it in the parent activity.
         return bookmarksDatabase.rawQuery(GET_ALL_BOOKMARKS, null);
     }
 
diff --git a/app/src/main/java/com/stoutner/privacybrowser/helpers/DomainsDatabaseHelper.java b/app/src/main/java/com/stoutner/privacybrowser/helpers/DomainsDatabaseHelper.java
new file mode 100644 (file)
index 0000000..1a5c066
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2017 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+ *
+ * Privacy Browser is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Privacy Browser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.helpers;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+public class DomainsDatabaseHelper extends SQLiteOpenHelper {
+    private static final int SCHEMA_VERSION = 1;
+    private static final String DOMAINS_DATABASE = "domains.db";
+    private static final String DOMAINS_TABLE = "domains";
+    private static final String _ID = "_id";
+
+    public static final String DOMAIN = "domain";
+
+
+    // Initialize the database.  The lint warnings for the unused parameters are suppressed.
+    public DomainsDatabaseHelper(Context context, @SuppressWarnings("UnusedParameters") String name, SQLiteDatabase.CursorFactory cursorFactory, @SuppressWarnings("UnusedParameters") int version) {
+        super(context, DOMAINS_DATABASE, cursorFactory, SCHEMA_VERSION);
+    }
+
+    @Override
+    public void onCreate(SQLiteDatabase domainsDatabase) {
+        // Setup the SQL string to create the `domains` table.
+        final String CREATE_DOMAINS_TABLE = "CREATE TABLE " + DOMAINS_TABLE + " (" +
+                _ID + " integer primary key, " +
+                DOMAIN + " text);";
+
+        // Create the `domains` table if it doesn't exist.
+        domainsDatabase.execSQL(CREATE_DOMAINS_TABLE);
+    }
+
+    @Override
+    public void onUpgrade(SQLiteDatabase domainsDatabase, int oldVersion, int newVersion) {
+        // Code for upgrading the database will be added here when the schema version > 1.
+    }
+
+    public Cursor getCursorOrderedByDomain() {
+        // Get a readable database handle.
+        SQLiteDatabase domainsDatabase = this.getReadableDatabase();
+
+        // Get everything in `DOMAINS_TABLE` ordered by `DOMAIN`.
+        final String GET_CURSOR_SORTED_BY_DOMAIN = "Select * FROM " + DOMAINS_TABLE +
+                " ORDER BY " + DOMAIN + " ASC";
+
+        // Return the results as a `Cursor`.  The second argument is `null` because there are no `selectionArgs`.  We can't close the `Cursor` because we need to use it in the parent activity.
+        return domainsDatabase.rawQuery(GET_CURSOR_SORTED_BY_DOMAIN, null);
+    }
+
+    public Cursor getCursorForId(int databaseId) {
+        // Get a readable database handle.
+        SQLiteDatabase domainsDatabase = this.getReadableDatabase();
+
+        // Prepare the SQL statement to ge the `Cursor` for `databaseId`.
+        final String GET_CURSOR_FOR_ID = "Select * FROM " + DOMAINS_TABLE +
+                " WHERE " + _ID + " = " + databaseId;
+
+        // Return the results as a `Cursor`.  The second argument is `null` because there are no `selectionArgs`.  We can't close the `Cursor` because we need to use it in the parent activity.
+        return domainsDatabase.rawQuery(GET_CURSOR_FOR_ID, null);
+    }
+
+    public void addDomain(String domainName) {
+        // We need to store the domain data in a `ContentValues`.
+        ContentValues domainContentValues = new ContentValues();
+
+        // ID is created automatically.
+        domainContentValues.put(DOMAIN, domainName);
+
+        // Get a writable database handle.
+        SQLiteDatabase domainsDatabase = this.getWritableDatabase();
+
+        // Insert a new row.  The second argument is `null`, which makes it so that a completely null row cannot be created.
+        domainsDatabase.insert(DOMAINS_TABLE, null, domainContentValues);
+
+        // Close the database handle.
+        domainsDatabase.close();
+    }
+}
diff --git a/app/src/main/res/drawable/domains_list_selector.xml b/app/src/main/res/drawable/domains_list_selector.xml
new file mode 100644 (file)
index 0000000..4d726a6
--- /dev/null
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  Copyright 2017 Soren Stoutner <soren@stoutner.com>.
+
+  This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+
+  Privacy Browser is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Privacy Browser is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>. -->
+
+<!-- This selector changes the background of activated items in the domains `RecyclerView`. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:state_activated="true"
+        android:drawable="@color/blue_100" />
+</selector>
\ No newline at end of file
index 5a7d407..4d6df86 100644 (file)
 <!-- `android:baselineAligned="False"` reduces unneeded computational overhead with `RecyclerViews`. -->
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
+    tools:context=".activities.DomainsList"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="horizontal"
     android:baselineAligned="false"
     android:divider="?android:attr/dividerHorizontal"
-    android:showDividers="middle"
-    tools:context=".activities.DomainsList">
+    android:showDividers="middle" >
 
-    <android.support.v7.widget.RecyclerView
+    <!-- `android:dividerHeight` must be specified with `android:divider` or the height will be `0dp`.  In our case we want the height to be `0dp`. -->
+    <ListView
+        android:id="@+id/domains_listview"
         android:layout_height="match_parent"
-        android:layout_width="200dp"
-        app:layoutManager="LinearLayoutManager" />
+        android:layout_width="0dp"
+        android:layout_weight="1"
+        android:divider="@color/white"
+        android:dividerHeight="0dp" />
+
+    <FrameLayout
+        android:id="@+id/domains_list_framelayout"
+        android:layout_height="match_parent"
+        android:layout_width="0dp"
+        android:layout_weight="3" />
 </LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/add_domain_dialog.xml b/app/src/main/res/layout/add_domain_dialog.xml
new file mode 100644 (file)
index 0000000..981ae13
--- /dev/null
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  Copyright 2017 Soren Stoutner <soren@stoutner.com>.
+
+  This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+
+  Privacy Browser is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Privacy Browser is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>. -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="wrap_content"
+    android:layout_width="match_parent"
+    android:orientation="vertical" >
+
+    <!-- `android.support.design.widget.TextInputLayout` makes the `android:hint` float above the `EditText`. -->
+    <android.support.design.widget.TextInputLayout
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:layout_marginTop="12dp"
+        android:layout_marginBottom="12dp"
+        android:layout_marginStart="4dp"
+        android:layout_marginEnd="4dp" >
+
+        <!-- `android:imeOptions="actionGo" sets the keyboard to have a `go` key instead of a `new line` key.  `android:inputType="textUri"` disables spell check in the `EditText`. -->
+        <android.support.design.widget.TextInputEditText
+            android:id="@+id/domain_name_edittext"
+            android:layout_height="wrap_content"
+            android:layout_width="match_parent"
+            android:hint="@string/domain_name"
+            android:imeOptions="actionGo"
+            android:inputType="textUri" />
+    </android.support.design.widget.TextInputLayout>
+</LinearLayout>
\ No newline at end of file
index 4967002..51ec0f1 100644 (file)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <!--
-  Copyright 2016 Soren Stoutner <soren@stoutner.com>.
+  Copyright 2016-2017 Soren Stoutner <soren@stoutner.com>.
 
   This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
 
@@ -33,8 +33,7 @@
         android:layout_marginStart="4dp"
         android:layout_marginEnd="4dp" >
 
-        <!-- `android:imeOptions="actionGo"` sets the keyboard to have a "go" key instead of a "new line" key.
-            `android:inputType="textUri"` disables spell check in the EditText. -->
+        <!-- `android:imeOptions="actionGo"` sets the keyboard to have a `go` key instead of a `new line` key.  `android:inputType="textUri"` disables spell check in the `EditText`. -->
         <android.support.design.widget.TextInputEditText
             android:id="@+id/create_bookmark_name_edittext"
             android:layout_height="wrap_content"
@@ -53,8 +52,7 @@
         android:layout_marginStart="4dp"
         android:layout_marginEnd="4dp" >
 
-        <!-- `android:imeOptions="actionGo"` sets the keyboard to have a "go" key instead of a "new line" key.
-            `android:inputType="textUri"` disables spell check in the EditText.-->
+        <!-- `android:imeOptions="actionGo"` sets the keyboard to have a `go` key instead of a `new line` key.  `android:inputType="textUri"` disables spell check in the `EditText`. -->
         <EditText
             android:id="@+id/create_bookmark_url_edittext"
             android:layout_height="wrap_content"
diff --git a/app/src/main/res/layout/domain_details.xml b/app/src/main/res/layout/domain_details.xml
new file mode 100644 (file)
index 0000000..96fa117
--- /dev/null
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  Copyright 2017 Soren Stoutner <soren@stoutner.com>.
+
+  This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+
+  Privacy Browser is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Privacy Browser is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>. -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="match_parent"
+    android:layout_width="match_parent"
+    android:orientation="horizontal" >
+
+    <!-- Labels `LinearLayout`. -->
+    <LinearLayout
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:orientation="vertical" >
+
+        <TextView
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:text="@string/domain_name"
+            android:labelFor="@id/domain_name_details_edittext"/>
+    </LinearLayout>
+
+    <!-- Data `LinearLayout`. -->
+    <LinearLayout
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:orientation="vertical" >
+
+        <!-- `android:inputType="textUri"` disables spell check in the `EditText`. -->
+        <EditText
+            android:id="@+id/domain_name_details_edittext"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:inputType="textUri" />
+    </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/domain_name_linearlayout.xml b/app/src/main/res/layout/domain_name_linearlayout.xml
new file mode 100644 (file)
index 0000000..5a63377
--- /dev/null
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  Copyright 2017 Soren Stoutner <soren@stoutner.com>.
+
+  This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+
+  Privacy Browser is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Privacy Browser is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>. -->
+
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="wrap_content"
+    android:layout_width="match_parent"
+    android:orientation="horizontal"
+    android:background="@drawable/domains_list_selector" >
+
+    <TextView
+        android:id="@+id/domain_name_textview"
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:textColor="@color/black"
+        android:textSize="22sp"
+        android:layout_margin="10dp"
+        android:maxLines="1"
+        android:ellipsize="end" />
+</LinearLayout>
\ No newline at end of file
index 3d5fb64..6864bcb 100644 (file)
   You should have received a copy of the GNU General Public License
   along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>. -->
 
-<android.support.v7.widget.RecyclerView
+<!-- `android:dividerHeight` must be specified with `android:divider` or the height will be `0dp`.  In our case we want the height to be `0dp`. -->
+<ListView
+    android:id="@+id/domains_listview"
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:context=".activities.DomainsList"
     android:layout_height="match_parent"
-    android:layout_width="match_parent"/>
\ No newline at end of file
+    android:layout_width="match_parent"
+    android:divider="@color/white"
+    android:dividerHeight="0dp" />
\ No newline at end of file
index cbc89d9..7f870a9 100644 (file)
 
             <!-- android:theme="@style/PrivacyBrowser.DarkAppBar" makes the text and icons in the AppBar white. -->
             <android.support.v7.widget.Toolbar
+                android:id="@+id/domains_toolbar"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent"
                 android:background="@color/blue_700"
                 android:theme="@style/DarkAppBar"
                 app:popupTheme="@style/LightPopupOverlay" />
         </android.support.design.widget.AppBarLayout>
-    </LinearLayout>
 
-    <include layout="@layout/domains_list" />
+        <include layout="@layout/domains_list" />
+    </LinearLayout>
 
     <android.support.design.widget.FloatingActionButton
+        android:id="@+id/add_domain_fab"
         android:layout_height="wrap_content"
         android:layout_width="wrap_content"
         android:layout_gravity="bottom|end"
index 3fa168f..7689f4d 100644 (file)
 
     <!-- Domains. -->
     <string name="domains">Domains</string>
+    <string name="add_domain">Add Domain</string>
+    <string name="add">Add</string>
+    <string name="domain_name">Domain Name</string>
 
     <!-- Guide. -->
     <string name="privacy_browser_guide">Privacy Browser Guide</string>
index f46d653..c6f2f50 100644 (file)
@@ -5,7 +5,7 @@ buildscript {
         jcenter()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:2.2.3'
+        classpath 'com.android.tools.build:gradle:2.3.0'
 
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files
index 6907c5b..92cecb1 100644 (file)
@@ -1,6 +1,6 @@
-#Tue Aug 16 16:10:23 MST 2016
+#Thu Mar 02 15:10:19 MST 2017
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip