diff --git a/.gitignore b/.gitignore index 3816578..5f7aae7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Built application files *.apk *.ap_ +*.aab # Files for the ART/Dalvik VM *.dex @@ -32,17 +33,14 @@ proguard/ # Android Studio captures folder captures/ -# Intellij +# IntelliJ *.iml -.idea/workspace.xml -.idea/tasks.xml -.idea/gradle.xml -.idea/dictionaries -.idea/libraries -.idea/caches/ +.idea/ # Keystore files -*.jks +# Uncomment the following lines if you do not want to check your keystore files in. +#*.jks +#*.keystore # External native build folder generated in Android Studio 2.2 and later .externalNativeBuild @@ -55,6 +53,24 @@ freeline.py freeline/ freeline_project_description.json +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md + +# Version control +vcs.xml + +# lint +lint/intermediates/ +lint/generated/ +lint/outputs/ +lint/tmp/ +# lint/reports/ + + # Deploy files app/upload.keystore app/upload.json diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml deleted file mode 100644 index 9570c35..0000000 --- a/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - - - - -
- - - - xmlns:android - ^$ - - - -
-
- - - - xmlns:.* - ^$ - - - BY_NAME - -
-
- - - - .*:id - http://schemas.android.com/apk/res/android - - - -
-
- - - - .*:name - http://schemas.android.com/apk/res/android - - - -
-
- - - - name - ^$ - - - -
-
- - - - style - ^$ - - - -
-
- - - - .* - ^$ - - - BY_NAME - -
-
- - - - .*:layout_width - http://schemas.android.com/apk/res/android - - - -
-
- - - - .*:layout_height - http://schemas.android.com/apk/res/android - - - -
-
- - - - .*:layout_.* - http://schemas.android.com/apk/res/android - - - BY_NAME - -
-
- - - - .*:width - http://schemas.android.com/apk/res/android - - - BY_NAME - -
-
- - - - .*:height - http://schemas.android.com/apk/res/android - - - BY_NAME - -
-
- - - - .* - http://schemas.android.com/apk/res/android - - - BY_NAME - -
-
- - - - .* - .* - - - BY_NAME - -
-
-
-
-
-
\ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100644 index 8f1a3b7..0000000 --- a/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index 15a15b2..0000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 707ee6e..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index a7b061e..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml deleted file mode 100644 index 7f68460..0000000 --- a/.idea/runConfigurations.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 960078e..4ad0dfc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ env: global: - ANDROID_API=29 - EMULATOR_API=21 - - ANDROID_BUILD_TOOLS=29.0.1 + - ANDROID_BUILD_TOOLS=29.0.2 android: components: - build-tools-$ANDROID_BUILD_TOOLS diff --git a/app/build.gradle b/app/build.gradle index 7d8f879..4189b16 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,6 +2,12 @@ plugins { id 'com.android.application' id 'com.github.triplet.play' version '2.4.2' } +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +repositories { + mavenCentral() +} android { applicationVariants.all { variant -> @@ -11,13 +17,13 @@ android { } compileSdkVersion 29 - buildToolsVersion '29.0.1' + buildToolsVersion '29.0.2' defaultConfig { applicationId "com.jtmcn.archwiki.viewer" minSdkVersion 21 targetSdkVersion 29 - versionCode 14 - versionName "1.0.13" + versionCode 15 + versionName "1.0.14" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -53,11 +59,6 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } } dependencies { @@ -67,14 +68,17 @@ dependencies { exclude group: 'com.android.support', module: 'support-annotations' }) - implementation 'com.jakewharton:butterknife:10.1.0' - annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0' - implementation 'com.google.code.gson:gson:2.8.5' + implementation 'com.jakewharton.timber:timber:4.7.1' + + implementation 'com.squareup.okhttp3:okhttp:3.11.0' implementation 'com.google.android.material:material:1.0.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.preference:preference:1.1.0' + implementation 'androidx.core:core-ktx:1.1.0' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } play { diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 7cbbc76..0d98bb9 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -19,7 +19,15 @@ -keep class android.support.v7.** { *; } -keep interface android.support.v7.** { *; } -# butterknife --dontwarn butterknife.internal.** --keep class **$$ViewInjector { *; } --keepnames class * { @butterknife.InjectView *;} \ No newline at end of file +# OkHttp +# JSR 305 annotations are for embedding nullability information. +-dontwarn javax.annotation.** + +# A resource is loaded with a relative path so the package of this class must be preserved. +-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase + +# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java. +-dontwarn org.codehaus.mojo.animal_sniffer.* + +# OkHttp platform used only on JVM and when Conscrypt dependency is available. +-dontwarn okhttp3.internal.platform.ConscryptPlatform diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0a33abf..2c54fca 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,9 +1,11 @@ + - - - - - + android:label="@string/menu_settings" + android:parentActivityName=".MainActivity"> + diff --git a/app/src/main/java/com/jtmcn/archwiki/viewer/ArchWikiViewerApp.kt b/app/src/main/java/com/jtmcn/archwiki/viewer/ArchWikiViewerApp.kt new file mode 100644 index 0000000..3bed536 --- /dev/null +++ b/app/src/main/java/com/jtmcn/archwiki/viewer/ArchWikiViewerApp.kt @@ -0,0 +1,13 @@ +package com.jtmcn.archwiki.viewer + +import android.app.Application +import timber.log.Timber +import timber.log.Timber.DebugTree + + +class ArchwikiViewerApp : Application() { + override fun onCreate() { + super.onCreate() + Timber.plant(DebugTree()) + } +} diff --git a/app/src/main/java/com/jtmcn/archwiki/viewer/Constants.java b/app/src/main/java/com/jtmcn/archwiki/viewer/Constants.java deleted file mode 100644 index 2721570..0000000 --- a/app/src/main/java/com/jtmcn/archwiki/viewer/Constants.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.jtmcn.archwiki.viewer; - -/** - * Various constants used throughout the program. - */ -public class Constants { - //format types - public static final String TEXT_HTML_MIME = "text/html"; - public static final String TEXT_PLAIN_MIME = "text/plain"; - public static final String UTF_8 = "UTF-8"; - - //arch wiki urls - public static final String ARCHWIKI_BASE = "https://wiki.archlinux.org/"; - public static final String ARCHWIKI_MAIN = ARCHWIKI_BASE + "index.php/Main_page"; - public static final String ARCHWIKI_SEARCH_URL = ARCHWIKI_BASE + "index.php?&search=%s"; - - //local file paths - public static final String ASSETS_FOLDER = "file:///android_asset/"; - public static final String LOCAL_CSS = ASSETS_FOLDER + "style.css"; -} diff --git a/app/src/main/java/com/jtmcn/archwiki/viewer/Constants.kt b/app/src/main/java/com/jtmcn/archwiki/viewer/Constants.kt new file mode 100644 index 0000000..dcfcdba --- /dev/null +++ b/app/src/main/java/com/jtmcn/archwiki/viewer/Constants.kt @@ -0,0 +1,16 @@ +package com.jtmcn.archwiki.viewer + + +//format types +const val TEXT_HTML_MIME = "text/html" +const val TEXT_PLAIN_MIME = "text/plain" +const val UTF_8 = "UTF-8" + +//arch wiki urls +const val ARCHWIKI_BASE = "https://wiki.archlinux.org" +const val ARCHWIKI_MAIN = "${ARCHWIKI_BASE}/index.php/Main_page" +const val ARCHWIKI_SEARCH_URL = "${ARCHWIKI_BASE}/index.php?&search=%s" + +//local file paths +const val ASSETS_FOLDER = "file:///android_asset" +const val LOCAL_CSS = "${ASSETS_FOLDER}/style.css" diff --git a/app/src/main/java/com/jtmcn/archwiki/viewer/MainActivity.java b/app/src/main/java/com/jtmcn/archwiki/viewer/MainActivity.java deleted file mode 100644 index fb2b54d..0000000 --- a/app/src/main/java/com/jtmcn/archwiki/viewer/MainActivity.java +++ /dev/null @@ -1,212 +0,0 @@ -package com.jtmcn.archwiki.viewer; - -import android.app.SearchManager; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; - -import androidx.annotation.NonNull; -import androidx.core.view.MenuItemCompat; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.SearchView; -import androidx.appcompat.widget.Toolbar; -import android.util.Log; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.webkit.WebSettings; -import android.widget.ProgressBar; - -import com.jtmcn.archwiki.viewer.data.SearchResult; -import com.jtmcn.archwiki.viewer.data.SearchResultsBuilder; -import com.jtmcn.archwiki.viewer.data.WikiPage; -import com.jtmcn.archwiki.viewer.tasks.Fetch; -import com.jtmcn.archwiki.viewer.tasks.FetchUrl; -import com.jtmcn.archwiki.viewer.utils.SettingsUtils; - -import java.util.ArrayList; -import java.util.List; - -import butterknife.BindView; -import butterknife.ButterKnife; - -import static com.jtmcn.archwiki.viewer.Constants.TEXT_PLAIN_MIME; - -public class MainActivity extends AppCompatActivity implements FetchUrl.OnFinish> { - public static final String TAG = MainActivity.class.getSimpleName(); - @BindView(R.id.wiki_view) WikiView wikiViewer; - @BindView(R.id.toolbar) Toolbar toolbar; - private SearchView searchView; - private MenuItem searchMenuItem; - private List currentSuggestions; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - ButterKnife.bind(this); - - setSupportActionBar(toolbar); - - ProgressBar progressBar = findViewById(R.id.progress_bar); - wikiViewer.buildView(progressBar, getSupportActionBar()); - - handleIntent(getIntent()); - } - - @Override - protected void onResume() { - super.onResume(); - updateWebSettings(); - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - handleIntent(intent); - } - - private void handleIntent(Intent intent) { - if (intent == null) { - return; - } - - if (Intent.ACTION_SEARCH.equals(intent.getAction())) { - String query = intent.getStringExtra(SearchManager.QUERY); - wikiViewer.passSearch(query); - hideSearchView(); - } else if (Intent.ACTION_VIEW.equals(intent.getAction())) { - final String url = intent.getDataString(); - wikiViewer.wikiClient.shouldOverrideUrlLoading(wikiViewer, url); - } - } - - /** - * Update the font size used in the webview. - */ - public void updateWebSettings() { - WebSettings webSettings = wikiViewer.getSettings(); - int fontSize = SettingsUtils.getFontSize(this); - - //todo this setting should be changed to a slider, remove deprecated call - // deprecated method must be used until Android API 14 - // https://developer.android.com/reference/android/webkit/WebSettings.TextSize.html#NORMAL - switch (fontSize) { - case 0: - webSettings.setTextSize(WebSettings.TextSize.SMALLEST); //50% - break; - case 1: - webSettings.setTextSize(WebSettings.TextSize.SMALLER); //75% - break; - case 2: - webSettings.setTextSize(WebSettings.TextSize.NORMAL); //100% - break; - case 3: - webSettings.setTextSize(WebSettings.TextSize.LARGER); //150% - break; - case 4: - webSettings.setTextSize(WebSettings.TextSize.LARGEST); //200% - break; - } - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE); - searchMenuItem = menu.findItem(R.id.menu_search); - searchView = (SearchView) MenuItemCompat.getActionView(searchMenuItem); - searchView.setOnQueryTextFocusChangeListener((v, hasFocus) -> { - if (!hasFocus) { - hideSearchView(); - } - }); - searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName())); - searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { - @Override - public boolean onQueryTextSubmit(String query) { - wikiViewer.passSearch(query); - return false; - } - - @Override - public boolean onQueryTextChange(String newText) { - if (newText.isEmpty()) { - setCursorAdapter(new ArrayList<>()); - return true; - } else { - String searchUrl = SearchResultsBuilder.getSearchQuery(newText); - Fetch.search(MainActivity.this, searchUrl); - return true; - } - } - }); - - searchView.setOnSuggestionListener(new SearchView.OnSuggestionListener() { - @Override - public boolean onSuggestionSelect(int position) { - return false; - } - - @Override - public boolean onSuggestionClick(int position) { - SearchResult searchResult = currentSuggestions.get(position); - Log.d(TAG, "Opening '" + searchResult.getPageName() + "' from search suggestion."); - wikiViewer.wikiClient.shouldOverrideUrlLoading(wikiViewer, searchResult.getPageURL()); - hideSearchView(); - return true; - } - }); - return true; - } - - public void hideSearchView() { - searchMenuItem.collapseActionView(); - wikiViewer.requestFocus(); //pass control back to the wikiview - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.menu, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(@NonNull MenuItem item) { - switch (item.getItemId()) { - case R.id.menu_share: - WikiPage wikiPage = wikiViewer.getCurrentWebPage(); - if (wikiPage != null) { - Intent sharingIntent = new Intent(); - sharingIntent.setType(TEXT_PLAIN_MIME); - sharingIntent.setAction(Intent.ACTION_SEND); - sharingIntent.putExtra(Intent.EXTRA_TITLE, wikiPage.getPageTitle()); - sharingIntent.putExtra(Intent.EXTRA_TEXT, wikiPage.getPageUrl()); - startActivity(Intent.createChooser(sharingIntent, null)); - } - break; - case R.id.refresh: - wikiViewer.onRefresh(); - break; - case R.id.menu_settings: - startActivity(new Intent(this, PreferencesActivity.class)); - break; - case R.id.exit: - finish(); - break; - } - return super.onOptionsItemSelected(item); - } - - @Override - public void onFinish(List results) { - currentSuggestions = results; - setCursorAdapter(currentSuggestions); - } - - private void setCursorAdapter(List currentSuggestions) { - searchView.setSuggestionsAdapter( - SearchResultsAdapter.getCursorAdapter(this, currentSuggestions) - ); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/jtmcn/archwiki/viewer/MainActivity.kt b/app/src/main/java/com/jtmcn/archwiki/viewer/MainActivity.kt new file mode 100644 index 0000000..30ebcb8 --- /dev/null +++ b/app/src/main/java/com/jtmcn/archwiki/viewer/MainActivity.kt @@ -0,0 +1,149 @@ +package com.jtmcn.archwiki.viewer + +import android.app.SearchManager +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.SearchView +import com.jtmcn.archwiki.viewer.data.SearchResult +import com.jtmcn.archwiki.viewer.data.getSearchQuery +import com.jtmcn.archwiki.viewer.tasks.Fetch +import com.jtmcn.archwiki.viewer.utils.getTextZoom +import kotlinx.android.synthetic.main.activity_main.* +import kotlinx.android.synthetic.main.toolbar.* +import timber.log.Timber +import java.util.* + +class MainActivity : AppCompatActivity() { + private lateinit var searchView: SearchView + private lateinit var searchMenuItem: MenuItem + private var currentSuggestions: List? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + setSupportActionBar(toolbar) + + wikiViewer.buildView(progressBar, supportActionBar) + + handleIntent(intent) + } + + override fun onResume() { + super.onResume() + updateWebSettings() + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + handleIntent(intent) + } + + private fun handleIntent(intent: Intent?) { + if (intent == null) { + return + } + + if (Intent.ACTION_SEARCH == intent.action) { + val query = intent.getStringExtra(SearchManager.QUERY) + wikiViewer.passSearch(query!!) + hideSearchView() + } else if (Intent.ACTION_VIEW == intent.action) { + val url = intent.dataString + wikiViewer.wikiClient.shouldOverrideUrlLoading(wikiViewer, url!!) + } + } + + /** + * Update the font size used in the webview. + */ + private fun updateWebSettings() { + wikiViewer.settings.textZoom = getTextZoom(this) + } + + override fun onPrepareOptionsMenu(menu: Menu): Boolean { + val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager + searchMenuItem = menu.findItem(R.id.menu_search) + searchView = searchMenuItem.actionView as SearchView + searchView.setOnQueryTextFocusChangeListener { _, hasFocus -> + if (!hasFocus) { + hideSearchView() + } + } + searchView.setSearchableInfo(searchManager.getSearchableInfo(componentName)) + searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { + override fun onQueryTextSubmit(query: String): Boolean { + wikiViewer.passSearch(query) + return false + } + + override fun onQueryTextChange(newText: String): Boolean { + if (newText.isEmpty()) { + setCursorAdapter(ArrayList()) + return true + } else { + val searchUrl = getSearchQuery(newText) + Fetch.search({ + currentSuggestions = it + setCursorAdapter(currentSuggestions) + }, searchUrl) + return true + } + } + }) + + searchView.setOnSuggestionListener(object : SearchView.OnSuggestionListener { + override fun onSuggestionSelect(position: Int): Boolean { + return false + } + + override fun onSuggestionClick(position: Int): Boolean { + val (pageName, pageURL) = currentSuggestions!![position] + Timber.d("Opening '$pageName' from search suggestion.") + wikiViewer.wikiClient.shouldOverrideUrlLoading(wikiViewer, pageURL) + hideSearchView() + return true + } + }) + return true + } + + private fun hideSearchView() { + searchMenuItem.collapseActionView() + wikiViewer.requestFocus() //pass control back to the wikiview + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + val inflater = menuInflater + inflater.inflate(R.menu.menu, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.menu_share -> { + val wikiPage = wikiViewer.currentWebPage + if (wikiPage != null) { + val sharingIntent = Intent() + sharingIntent.type = TEXT_PLAIN_MIME + sharingIntent.action = Intent.ACTION_SEND + sharingIntent.putExtra(Intent.EXTRA_TITLE, wikiPage.pageTitle) + sharingIntent.putExtra(Intent.EXTRA_TEXT, wikiPage.pageUrl) + startActivity(Intent.createChooser(sharingIntent, null)) + } + } + R.id.refresh -> wikiViewer.onRefresh() + R.id.menu_settings -> startActivity(Intent(this, PreferencesActivity::class.java)) + R.id.exit -> finish() + } + return super.onOptionsItemSelected(item) + } + + private fun setCursorAdapter(currentSuggestions: List?) { + searchView.suggestionsAdapter = SearchResultsAdapter.getCursorAdapter(this, currentSuggestions!!) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/jtmcn/archwiki/viewer/PreferencesActivity.java b/app/src/main/java/com/jtmcn/archwiki/viewer/PreferencesActivity.java deleted file mode 100644 index f95a3c0..0000000 --- a/app/src/main/java/com/jtmcn/archwiki/viewer/PreferencesActivity.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.jtmcn.archwiki.viewer; - -import android.os.Bundle; -import android.preference.PreferenceActivity; -import android.preference.PreferenceFragment; -import android.preference.PreferenceManager; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.Toolbar; -import android.view.MenuItem; - -/** - * The {@link PreferenceActivity} to change settings for the application. - */ -public class PreferencesActivity extends AppCompatActivity { - public static final String KEY_TEXT_SIZE = "textSize"; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - PreferenceManager.setDefaultValues(this, R.xml.prefs, false); - } - - @Override - protected void onPostCreate(@Nullable Bundle savedInstanceState) { - super.onPostCreate(savedInstanceState); - setContentView(R.layout.activity_preferences); - - getFragmentManager().beginTransaction() - .replace(R.id.settings_content, new ApplicationPreferenceFragment()) - .commit(); - - Toolbar toolbar = findViewById(R.id.toolbar); - setSupportActionBar(toolbar); - setTitle(R.string.menu_settings); - - if (getSupportActionBar() != null) { - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setHomeButtonEnabled(true); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - onBackPressed(); - break; - } - return true; - } - - /** - * Loads the activities preferences into the fragment. - */ - public static class ApplicationPreferenceFragment extends PreferenceFragment { - @Override - public void onCreate(Bundle bundle) { - super.onCreate(bundle); - addPreferencesFromResource(R.xml.prefs); - } - } -} diff --git a/app/src/main/java/com/jtmcn/archwiki/viewer/PreferencesActivity.kt b/app/src/main/java/com/jtmcn/archwiki/viewer/PreferencesActivity.kt new file mode 100644 index 0000000..43632a4 --- /dev/null +++ b/app/src/main/java/com/jtmcn/archwiki/viewer/PreferencesActivity.kt @@ -0,0 +1,37 @@ +package com.jtmcn.archwiki.viewer + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.preference.PreferenceFragmentCompat +import com.jtmcn.archwiki.viewer.utils.getTextZoom +import kotlinx.android.synthetic.main.toolbar.* + +class PreferencesActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_preferences) + + setSupportActionBar(toolbar) + + supportFragmentManager + .beginTransaction() + .replace(R.id.settings, SettingsFragment()) + .commit() + supportActionBar?.setDisplayHomeAsUpEnabled(true) + + getTextZoom(this) + } + + class SettingsFragment : PreferenceFragmentCompat() { + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.preferences, rootKey) + } + } +} + +object Prefs { + @Deprecated(message = "Should use textZoom", replaceWith = ReplaceWith("KEY_TEXT_ZOOM")) + const val KEY_TEXT_SIZE = "textSize" + const val KEY_TEXT_ZOOM = "textZoom" +} \ No newline at end of file diff --git a/app/src/main/java/com/jtmcn/archwiki/viewer/SearchResultsAdapter.java b/app/src/main/java/com/jtmcn/archwiki/viewer/SearchResultsAdapter.java deleted file mode 100644 index 3059c5c..0000000 --- a/app/src/main/java/com/jtmcn/archwiki/viewer/SearchResultsAdapter.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.jtmcn.archwiki.viewer; - -import android.app.SearchManager; -import android.content.Context; -import android.database.MatrixCursor; -import android.provider.BaseColumns; -import androidx.cursoradapter.widget.CursorAdapter; -import androidx.cursoradapter.widget.SimpleCursorAdapter; - -import com.jtmcn.archwiki.viewer.data.SearchResult; - -import java.util.List; - -/** - * Helper for creating a {@link SimpleCursorAdapter} which will - * list the search results for a {@link android.widget.SearchView} - */ -public class SearchResultsAdapter { - private static final String[] columnNames = {BaseColumns._ID, SearchManager.SUGGEST_COLUMN_TEXT_1}; - private static final String[] from = {SearchManager.SUGGEST_COLUMN_TEXT_1}; - private static final int[] to = new int[]{R.id.url}; - - /** - * Creates a cursor adapter given a {@link List}. - * https://stackoverflow.com/questions/11628172/converting-an-arrayadapter-to-cursoradapter-for-use-in-a-searchview/11628527#11628527 - * - * @param results the results to be placed in the adapter. - * @return the adapter. - */ - public static CursorAdapter getCursorAdapter(Context context, List results) { - int id = 0; - MatrixCursor cursor = new MatrixCursor(columnNames); - for (SearchResult item : results) { - String[] temp = new String[2]; - temp[0] = String.valueOf(id); // "_id" - temp[1] = item.getPageName(); // "title" - - cursor.addRow(temp); - id++; - } - - return new SimpleCursorAdapter( - context, - R.layout.search_suggestions_list_item, - cursor, - from, - to, - CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER - ); - } -} diff --git a/app/src/main/java/com/jtmcn/archwiki/viewer/SearchResultsAdapter.kt b/app/src/main/java/com/jtmcn/archwiki/viewer/SearchResultsAdapter.kt new file mode 100644 index 0000000..8c32b3e --- /dev/null +++ b/app/src/main/java/com/jtmcn/archwiki/viewer/SearchResultsAdapter.kt @@ -0,0 +1,49 @@ +package com.jtmcn.archwiki.viewer + +import android.app.SearchManager +import android.content.Context +import android.database.MatrixCursor +import android.provider.BaseColumns +import androidx.cursoradapter.widget.CursorAdapter +import androidx.cursoradapter.widget.SimpleCursorAdapter + +import com.jtmcn.archwiki.viewer.data.SearchResult + +/** + * Helper for creating a [SimpleCursorAdapter] which will + * list the search results for a [android.widget.SearchView] + */ +object SearchResultsAdapter { + private val columnNames = arrayOf(BaseColumns._ID, SearchManager.SUGGEST_COLUMN_TEXT_1) + private val from = arrayOf(SearchManager.SUGGEST_COLUMN_TEXT_1) + private val to = intArrayOf(R.id.url) + + /** + * Creates a cursor adapter given a [<]. + * https://stackoverflow.com/questions/11628172/converting-an-arrayadapter-to-cursoradapter-for-use-in-a-searchview/11628527#11628527 + * + * @param results the results to be placed in the adapter. + * @return the adapter. + */ + fun getCursorAdapter(context: Context, results: List): CursorAdapter { + var id = 0 + val cursor = MatrixCursor(columnNames) + for ((pageName) in results) { + val temp = arrayOfNulls(2) + temp[0] = id.toString() // "_id" + temp[1] = pageName // "title" + + cursor.addRow(temp) + id++ + } + + return SimpleCursorAdapter( + context, + R.layout.search_suggestions_list_item, + cursor, + from, + to, + CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER + ) + } +} diff --git a/app/src/main/java/com/jtmcn/archwiki/viewer/WikiClient.java b/app/src/main/java/com/jtmcn/archwiki/viewer/WikiClient.java deleted file mode 100644 index c932907..0000000 --- a/app/src/main/java/com/jtmcn/archwiki/viewer/WikiClient.java +++ /dev/null @@ -1,186 +0,0 @@ -package com.jtmcn.archwiki.viewer; - -import android.os.Handler; -import androidx.appcompat.app.ActionBar; -import android.util.Log; -import android.view.View; -import android.webkit.WebView; -import android.webkit.WebViewClient; -import android.widget.ProgressBar; - -import com.jtmcn.archwiki.viewer.data.WikiPage; -import com.jtmcn.archwiki.viewer.tasks.Fetch; -import com.jtmcn.archwiki.viewer.tasks.FetchUrl; -import com.jtmcn.archwiki.viewer.utils.AndroidUtils; - -import java.util.HashSet; -import java.util.Set; -import java.util.Stack; - -import static com.jtmcn.archwiki.viewer.Constants.ARCHWIKI_BASE; -import static com.jtmcn.archwiki.viewer.Constants.TEXT_HTML_MIME; -import static com.jtmcn.archwiki.viewer.Constants.UTF_8; - -public class WikiClient extends WebViewClient implements FetchUrl.OnFinish { - public static final String TAG = WikiClient.class.getSimpleName(); - private final WebView webView; - private final Stack webPageStack = new Stack<>(); - private final ProgressBar progressBar; - private final ActionBar actionBar; - private Set loadedUrls = new HashSet<>(); // this is used to see if we should restore the scroll position - private String lastLoadedUrl = null; //https://stackoverflow.com/questions/11601134/android-webview-function-onpagefinished-is-called-twice - - public WikiClient(ProgressBar progressBar, ActionBar actionBar, WebView wikiViewer) { - this.progressBar = progressBar; - this.actionBar = actionBar; - webView = wikiViewer; - } - - /* - * Manage page history - */ - public void addHistory(WikiPage wikiPage) { - if (webPageStack.size() > 0) { - Log.d(TAG, "Saving " + getCurrentWebPage().getPageTitle() + " at " + webView.getScrollY()); - getCurrentWebPage().setScrollPosition(webView.getScrollY()); - } - webPageStack.push(wikiPage); - Log.i(TAG, "Adding page " + wikiPage.getPageTitle() + ". Stack size= " + webPageStack.size()); - } - - /** - * Loads the html from a {@link WikiPage} into the webview. - * - * @param wikiPage the page to be loaded. - */ - public void loadWikiHtml(WikiPage wikiPage) { - webView.loadDataWithBaseURL( - wikiPage.getPageUrl(), - wikiPage.getHtmlString(), - TEXT_HTML_MIME, - UTF_8, - null - ); - - setSubtitle(wikiPage.getPageTitle()); - } - - /** - * Intercept url when clicked. If it's part of the wiki load it here. - * If not, open the device's default browser. - * - * @param view webview being loaded into - * @param url url being loaded - * @return true if should override url loading - */ - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - // deprecated until min api 21 is used - if (url.startsWith(ARCHWIKI_BASE)) { - webView.stopLoading(); - Fetch.page(this, url); - showProgress(); - - return false; - } else { - AndroidUtils.openLink(url, view.getContext()); - return true; - } - } - - @Override - public void onPageFinished(WebView view, String url) { - super.onPageFinished(view, url); - final WikiPage currentWebPage = getCurrentWebPage(); - Log.d(TAG, "Calling onPageFinished(view, " + currentWebPage.getPageTitle() + ")"); - // make sure we're loading the current page and that - // this page's url doesn't have an anchor (only on first page load) - if (url.equals(currentWebPage.getPageUrl()) && !url.equals(lastLoadedUrl)) { - if (!isFirstLoad(currentWebPage)) { - new Handler().postDelayed(() -> { - int scrollY = currentWebPage.getScrollPosition(); - Log.d(TAG, "Restoring " + currentWebPage.getPageTitle() + " at " + scrollY); - webView.setScrollY(scrollY); - }, 25); - } - - lastLoadedUrl = url; - hideProgress(); - } - } - - private boolean isFirstLoad(WikiPage currentWebPage) { - if (loadedUrls.contains(currentWebPage.getPageUrl())) { - return false; - } else { - loadedUrls.add(currentWebPage.getPageUrl()); - return true; - } - } - - public void showProgress() { - progressBar.setVisibility(View.VISIBLE); - } - - public void hideProgress() { - progressBar.setVisibility(View.GONE); - } - - public void setSubtitle(String title) { - if (actionBar != null) { - actionBar.setSubtitle(title); - } - } - - /** - * Get the number of pages that are in the history. - * - * @return number of pages on the stack. - */ - public int getHistoryStackSize() { - return webPageStack.size(); - } - - /** - * Go back to the last loaded page. - */ - public void goBackHistory() { - WikiPage removed = webPageStack.pop(); - loadedUrls.remove(removed.getPageUrl()); - Log.i(TAG, "Removing " + removed.getPageTitle() + " from stack"); - WikiPage newPage = webPageStack.peek(); - loadWikiHtml(newPage); - } - - /** - * Returns null or the current page. - * - * @return The current page - */ - public WikiPage getCurrentWebPage() { - return webPageStack.size() == 0 ? null : webPageStack.peek(); - } - - @Override - public void onFinish(WikiPage results) { - addHistory(results); - loadWikiHtml(getCurrentWebPage()); - } - - public void refreshPage() { - lastLoadedUrl = null; // set to null if page should restore position, otherwise start at top of page - WikiPage currentWebPage = getCurrentWebPage(); - if (currentWebPage != null) { - final int scrollPosition = currentWebPage.getScrollPosition(); - - String url = currentWebPage.getPageUrl(); - showProgress(); - Fetch.page(wikiPage -> { - webPageStack.pop(); - webPageStack.push(wikiPage); - wikiPage.setScrollPosition(scrollPosition); - loadWikiHtml(wikiPage); - }, url); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/jtmcn/archwiki/viewer/WikiClient.kt b/app/src/main/java/com/jtmcn/archwiki/viewer/WikiClient.kt new file mode 100644 index 0000000..3f05fe8 --- /dev/null +++ b/app/src/main/java/com/jtmcn/archwiki/viewer/WikiClient.kt @@ -0,0 +1,159 @@ +package com.jtmcn.archwiki.viewer + +import android.os.Handler +import android.view.View +import android.webkit.WebView +import android.webkit.WebViewClient +import android.widget.ProgressBar +import androidx.appcompat.app.ActionBar +import com.jtmcn.archwiki.viewer.data.WikiPage +import com.jtmcn.archwiki.viewer.tasks.Fetch +import com.jtmcn.archwiki.viewer.utils.openLink +import timber.log.Timber +import java.util.* + + +class WikiClient(private val progressBar: ProgressBar, private val actionBar: ActionBar?, private val webView: WebView) : WebViewClient() { + private val webPageStack = Stack() + private val loadedUrls = HashSet() // this is used to see if we should restore the scroll position + private var lastLoadedUrl: String? = null //https://stackoverflow.com/questions/11601134/android-webview-function-onpagefinished-is-called-twice + + /** + * Get the number of pages that are in the history. + * + * @return number of pages on the stack. + */ + val historyStackSize: Int + get() = webPageStack.size + + /** + * Returns null or the current page. + * + * @return The current page + */ + val currentWebPage: WikiPage? + get() = if (webPageStack.size == 0) null else webPageStack.peek() + + /* + * Manage page history + */ + private fun addHistory(wikiPage: WikiPage) { + if (webPageStack.size > 0) { + Timber.d("Saving ${currentWebPage?.pageTitle} at ${webView.scrollY}") + currentWebPage!!.scrollPosition = webView.scrollY + } + webPageStack.push(wikiPage) + Timber.i("Adding page ${wikiPage.pageTitle}. Stack size= ${webPageStack.size}") + } + + /** + * Loads the html from a [WikiPage] into the webview. + * + * @param wikiPage the page to be loaded. + */ + fun loadWikiHtml(wikiPage: WikiPage) { + webView.loadDataWithBaseURL( + wikiPage.pageUrl, + wikiPage.htmlString, + TEXT_HTML_MIME, + UTF_8, + null + ) + + setSubtitle(wikiPage.pageTitle) + } + + /** + * Intercept url when clicked. If it's part of the wiki load it here. + * If not, open the device's default browser. + * + * @param view webview being loaded into + * @param url url being loaded + * @return true if should override url loading + */ + override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { + // deprecated until min api 21 is used + if (url.startsWith(ARCHWIKI_BASE)) { + webView.stopLoading() + Fetch.page({ + addHistory(it) + loadWikiHtml(currentWebPage!!) + }, url) + showProgress() + + return false + } else { + openLink(url, view.context) + return true + } + } + + override fun onPageFinished(view: WebView, url: String) { + super.onPageFinished(view, url) + val currentWebPage = currentWebPage + Timber.d("Calling onPageFinished(view, ${currentWebPage?.pageTitle})") + // make sure we're loading the current page and that + // this page's url doesn't have an anchor (only on first page load) + if (url == currentWebPage?.pageUrl && url != lastLoadedUrl) { + if (!isFirstLoad(currentWebPage)) { + Handler().postDelayed({ + val scrollY = currentWebPage.scrollPosition + Timber.d("Restoring ${currentWebPage.pageTitle} at $scrollY") + webView.scrollY = scrollY + }, 25) + } + + lastLoadedUrl = url + hideProgress() + } + } + + private fun isFirstLoad(currentWebPage: WikiPage): Boolean { + return if (loadedUrls.contains(currentWebPage.pageUrl)) { + false + } else { + loadedUrls.add(currentWebPage.pageUrl) + true + } + } + + private fun showProgress() { + progressBar.visibility = View.VISIBLE + } + + private fun hideProgress() { + progressBar.visibility = View.GONE + } + + private fun setSubtitle(title: String?) { + actionBar?.subtitle = title + } + + /** + * Go back to the last loaded page. + */ + fun goBackHistory() { + val (pageUrl, pageTitle) = webPageStack.pop() + loadedUrls.remove(pageUrl) + Timber.i("Removing $pageTitle from stack") + val newPage = webPageStack.peek() + loadWikiHtml(newPage) + } + + fun refreshPage() { + lastLoadedUrl = null // set to null if page should restore position, otherwise start at top of page + val currentWebPage = currentWebPage + if (currentWebPage != null) { + val scrollPosition = currentWebPage.scrollPosition + + val url = currentWebPage.pageUrl + showProgress() + Fetch.page({ wikiPage -> + webPageStack.pop() + webPageStack.push(wikiPage) + wikiPage.scrollPosition = scrollPosition + loadWikiHtml(wikiPage) + }, url) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/jtmcn/archwiki/viewer/WikiView.java b/app/src/main/java/com/jtmcn/archwiki/viewer/WikiView.java deleted file mode 100644 index 31a7f7f..0000000 --- a/app/src/main/java/com/jtmcn/archwiki/viewer/WikiView.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.jtmcn.archwiki.viewer; - -import android.content.Context; -import android.os.Build; - -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; -import androidx.appcompat.app.ActionBar; - -import android.util.AttributeSet; -import android.util.Log; -import android.view.KeyEvent; -import android.webkit.WebSettings; -import android.widget.ProgressBar; - -import com.github.takahirom.webview_in_coodinator_layout.NestedWebView; -import com.jtmcn.archwiki.viewer.data.WikiPage; - -import static com.jtmcn.archwiki.viewer.Constants.ARCHWIKI_MAIN; -import static com.jtmcn.archwiki.viewer.Constants.ARCHWIKI_SEARCH_URL; - -public class WikiView extends NestedWebView implements SwipeRefreshLayout.OnRefreshListener { - public static final String TAG = WikiView.class.getSimpleName(); - WikiClient wikiClient; - - public WikiView(Context context, AttributeSet attrs) { - super(context, attrs); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && !isInEditMode()) { - // This allows the webview to inject the css (otherwise it blocks it for security reasons) - getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); - } - } - - /** - * Initializes the wiki client and loads the main page. - */ - public void buildView(ProgressBar progressBar, ActionBar actionBar) { - wikiClient = new WikiClient(progressBar, actionBar, this); - setWebViewClient(wikiClient); - wikiClient.shouldOverrideUrlLoading(this, ARCHWIKI_MAIN); - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_BACK && wikiClient.getHistoryStackSize() > 1) { - Log.i(TAG, "Loading previous page."); - Log.d(TAG, "Position on page currently at " + getScrollY()); - wikiClient.goBackHistory(); - return true; - } else { - Log.d(TAG, "Passing up button press."); - return super.onKeyDown(keyCode, event); - } - } - - /** - * Performs a search against the wiki. - * - * @param query the text to search for. - */ - public void passSearch(String query) { - Log.d(TAG, "Searching for " + query); - String searchUrl = String.format(ARCHWIKI_SEARCH_URL, query); - wikiClient.shouldOverrideUrlLoading(this, searchUrl); - } - - /** - * Returns the current {@link WikiPage} being shown or null. - * - * @return current wiki page being shown. - */ - public WikiPage getCurrentWebPage() { - return wikiClient.getCurrentWebPage(); - } - - @Override - public void onRefresh() { - wikiClient.refreshPage(); - stopLoading(); - } -} diff --git a/app/src/main/java/com/jtmcn/archwiki/viewer/WikiView.kt b/app/src/main/java/com/jtmcn/archwiki/viewer/WikiView.kt new file mode 100644 index 0000000..c747296 --- /dev/null +++ b/app/src/main/java/com/jtmcn/archwiki/viewer/WikiView.kt @@ -0,0 +1,69 @@ +package com.jtmcn.archwiki.viewer + +import android.content.Context +import android.os.Build +import android.util.AttributeSet +import android.view.KeyEvent +import android.webkit.WebSettings +import android.widget.ProgressBar +import androidx.appcompat.app.ActionBar +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout +import com.github.takahirom.webview_in_coodinator_layout.NestedWebView +import com.jtmcn.archwiki.viewer.data.WikiPage +import timber.log.Timber + +class WikiView(context: Context, attrs: AttributeSet) : NestedWebView(context, attrs), SwipeRefreshLayout.OnRefreshListener { + lateinit var wikiClient: WikiClient + + /** + * Returns the current [WikiPage] being shown or null. + * + * @return current wiki page being shown. + */ + val currentWebPage: WikiPage? + get() = wikiClient.currentWebPage + + init { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && !isInEditMode) { + // This allows the webview to inject the css (otherwise it blocks it for security reasons) + settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW + } + } + + /** + * Initializes the wiki client and loads the main page. + */ + fun buildView(progressBar: ProgressBar, actionBar: ActionBar?) { + wikiClient = WikiClient(progressBar, actionBar, this) + webViewClient = wikiClient + wikiClient.shouldOverrideUrlLoading(this, ARCHWIKI_MAIN) + } + + override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { + if (keyCode == KeyEvent.KEYCODE_BACK && wikiClient.historyStackSize > 1) { + Timber.i("Loading previous page.") + Timber.d("Position on page currently at $scrollY") + wikiClient.goBackHistory() + return true + } else { + Timber.d("Passing up button press.") + return super.onKeyDown(keyCode, event) + } + } + + /** + * Performs a search against the wiki. + * + * @param query the text to search for. + */ + fun passSearch(query: String) { + Timber.d("Searching for $query") + val searchUrl = String.format(ARCHWIKI_SEARCH_URL, query) + wikiClient.shouldOverrideUrlLoading(this, searchUrl) + } + + override fun onRefresh() { + wikiClient.refreshPage() + stopLoading() + } +} diff --git a/app/src/main/java/com/jtmcn/archwiki/viewer/data/SearchResult.java b/app/src/main/java/com/jtmcn/archwiki/viewer/data/SearchResult.java deleted file mode 100644 index f54ef59..0000000 --- a/app/src/main/java/com/jtmcn/archwiki/viewer/data/SearchResult.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.jtmcn.archwiki.viewer.data; - -/** - * A page on the wiki which only knows the name and url. - */ -public class SearchResult { - private final String pageName; - private final String pageURL; - - /** - * Create a search result. - * - * @param pageName the name of the page as shown on the wiki. - * @param pageURL the string url on the wiki. - */ - public SearchResult(String pageName, String pageURL) { - this.pageName = pageName; - this.pageURL = pageURL; - } - - public String getPageName() { - return pageName; - } - - public String getPageURL() { - return pageURL; - } - - @Override - public String toString() { - return "SearchResult{" + "title='" + pageName + '\'' + - ", url='" + pageURL + '\'' + '}'; - } -} diff --git a/app/src/main/java/com/jtmcn/archwiki/viewer/data/SearchResult.kt b/app/src/main/java/com/jtmcn/archwiki/viewer/data/SearchResult.kt new file mode 100644 index 0000000..c562b6e --- /dev/null +++ b/app/src/main/java/com/jtmcn/archwiki/viewer/data/SearchResult.kt @@ -0,0 +1,6 @@ +package com.jtmcn.archwiki.viewer.data + +/** + * A page on the wiki which only knows the name and url. + */ +data class SearchResult(val pageName: String, val pageURL: String) diff --git a/app/src/main/java/com/jtmcn/archwiki/viewer/data/SearchResultsBuilder.java b/app/src/main/java/com/jtmcn/archwiki/viewer/data/SearchResultsBuilder.java deleted file mode 100644 index ee11f65..0000000 --- a/app/src/main/java/com/jtmcn/archwiki/viewer/data/SearchResultsBuilder.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.jtmcn.archwiki.viewer.data; - -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonParser; - -import java.util.ArrayList; -import java.util.List; - -import static com.jtmcn.archwiki.viewer.Constants.ARCHWIKI_BASE; - -/** - * Provides a simple interface to make queries against - * and parse data from the arch wiki for searches. - */ -public class SearchResultsBuilder { - public static final String SEARCH_URL = ARCHWIKI_BASE + "api.php?action=opensearch" + - "&format=json&formatversion=2&namespace=0&limit=%d" + - "&suggest=true&search=%s"; - private static final int DEFAULT_LIMIT = 10; - - private SearchResultsBuilder() { - - } - - /** - * Builds a string url to fetch search results. - * - * @param query the text to search for. - * @return a url to fetch. - */ - public static String getSearchQuery(String query) { - return getSearchQuery(query, DEFAULT_LIMIT); - } - - /** - * Builds a string url to fetch search results. - * - * @param query the text to search for. - * @param limit the maximum number of results to retrieve. - * @return a url to fetch. - */ - public static String getSearchQuery(String query, int limit) { - return String.format(SEARCH_URL, limit, query); - } - - /** - * Builds a {@link List} from the result of fetching with {@link #getSearchQuery(String, int)}. - * - * @param jsonResult the string returned from the query. - * @return a parsed list of the results. - */ - public static List parseSearchResults(String jsonResult) { - JsonParser jsonParser = new JsonParser(); - JsonElement jsonRoot = jsonParser.parse(jsonResult); - List toReturn = new ArrayList<>(); - if (jsonRoot.isJsonArray()) { - JsonArray jsonArray = jsonRoot.getAsJsonArray(); - if (jsonArray.size() == 4) { - String[] listOfPageTitles = getJsonArrayAsStringArray(jsonArray.get(1).getAsJsonArray()); - String[] listOfPageUrls = getJsonArrayAsStringArray(jsonArray.get(3).getAsJsonArray()); - for (int i = 0; i < listOfPageTitles.length; i++) { - toReturn.add(new SearchResult(listOfPageTitles[i], listOfPageUrls[i])); - } - } - } - return toReturn; - } - - /** - * Convert a {@link JsonArray} into an array of strings. - * - * @param jsonArray the array to be parsed. - * @return the string array which was parsed. - */ - private static String[] getJsonArrayAsStringArray(JsonArray jsonArray) { - String[] s2 = new String[jsonArray.size()]; - for (int i = 0; i < jsonArray.size(); i++) { - s2[i] = jsonArray.get(i).getAsString(); - } - return s2; - } -} diff --git a/app/src/main/java/com/jtmcn/archwiki/viewer/data/SearchResultsBuilder.kt b/app/src/main/java/com/jtmcn/archwiki/viewer/data/SearchResultsBuilder.kt new file mode 100644 index 0000000..b32be63 --- /dev/null +++ b/app/src/main/java/com/jtmcn/archwiki/viewer/data/SearchResultsBuilder.kt @@ -0,0 +1,36 @@ +package com.jtmcn.archwiki.viewer.data + +import com.google.gson.JsonParser +import com.jtmcn.archwiki.viewer.ARCHWIKI_BASE + +/** + * Builds a string url to fetch search results. + * + * @param query the text to search for. + * @param limit the maximum number of results to retrieve. + * @return a url to fetch. + */ +fun getSearchQuery(query: String, limit: Int = 10): String { + return "${ARCHWIKI_BASE}/api.php?" + + "action=opensearch&format=json&formatversion=2&namespace=0&suggest=true" + + "&search=$query" + + "&limit=$limit" +} + +/** + * Builds a [List] from the result of fetching with [getSearchQuery]. + * + * @param jsonResult the string returned from the query. + * @return a parsed list of the results. + */ +fun parseSearchResults(jsonResult: String): List { + val jsonRoot = JsonParser().parse(jsonResult) + if (!jsonRoot.isJsonArray || jsonRoot.asJsonArray.size() != 4) return listOf() + + val jsonArray = jsonRoot.asJsonArray + + val listOfPageTitles = jsonArray.get(1).asJsonArray.mapNotNull { it.asString } + val listOfPageUrls = jsonArray.get(3).asJsonArray.mapNotNull { it.asString } + + return listOfPageTitles.zip(listOfPageUrls).map { SearchResult(it.first, it.second) } +} \ No newline at end of file diff --git a/app/src/main/java/com/jtmcn/archwiki/viewer/data/WikiPage.java b/app/src/main/java/com/jtmcn/archwiki/viewer/data/WikiPage.java deleted file mode 100644 index 0ecfeac..0000000 --- a/app/src/main/java/com/jtmcn/archwiki/viewer/data/WikiPage.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.jtmcn.archwiki.viewer.data; - -/** - * Wrapper for a downloaded wiki page which holds the title and html. - */ -public class WikiPage { - private final String pageUrl; - private final String pageTitle; - private final String htmlString; - private int scrollPosition = 0; - - /** - * Store the url, title, and html of a page on the wiki. - * - * @param pageUrl the string url on the wiki. - * @param pageTitle the title of the page on the wiki. - * @param htmlString the html which should be shown to represent the page. - */ - public WikiPage(String pageUrl, String pageTitle, String htmlString) { - this.pageUrl = pageUrl; - this.pageTitle = pageTitle; - this.htmlString = htmlString; - } - - public String getPageUrl() { - return pageUrl; - } - - public String getPageTitle() { - return pageTitle; - } - - public String getHtmlString() { - return htmlString; - } - - public int getScrollPosition() { - return scrollPosition; - } - - public void setScrollPosition(int scrollPosition) { - this.scrollPosition = scrollPosition; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - - if (!(obj instanceof WikiPage)) { - return false; - } - - WikiPage wikiPage = (WikiPage) obj; - - return getPageUrl() != null ? getPageUrl().equals(wikiPage.getPageUrl()) : wikiPage.getPageUrl() == null; - } - - @Override - public int hashCode() { - return getPageUrl() != null ? getPageUrl().hashCode() : 0; - } - - @Override - public String toString() { - return "WikiPage{" + - "pageUrl='" + pageUrl + '\'' + - ", pageTitle='" + pageTitle + '\'' + - ", htmlString='" + htmlString + '\'' + - ", scrollPosition=" + scrollPosition + - '}'; - } -} diff --git a/app/src/main/java/com/jtmcn/archwiki/viewer/data/WikiPage.kt b/app/src/main/java/com/jtmcn/archwiki/viewer/data/WikiPage.kt new file mode 100644 index 0000000..864da17 --- /dev/null +++ b/app/src/main/java/com/jtmcn/archwiki/viewer/data/WikiPage.kt @@ -0,0 +1,8 @@ +package com.jtmcn.archwiki.viewer.data + +/** + * Wrapper for a downloaded wiki page which holds the title and html. + */ +data class WikiPage(val pageUrl: String, val pageTitle: String?, val htmlString: String) { + var scrollPosition = 0 +} diff --git a/app/src/main/java/com/jtmcn/archwiki/viewer/data/WikiPageBuilder.java b/app/src/main/java/com/jtmcn/archwiki/viewer/data/WikiPageBuilder.java deleted file mode 100644 index ddaa06e..0000000 --- a/app/src/main/java/com/jtmcn/archwiki/viewer/data/WikiPageBuilder.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.jtmcn.archwiki.viewer.data; - -import static com.jtmcn.archwiki.viewer.Constants.LOCAL_CSS; - -/** - * Helps with creating a {@link WikiPage} by extracting content from the - * html fetched from the ArchWiki. - */ -public class WikiPageBuilder { - //NOTE: spaces are allowed in ""/etc, but parsing this way should be fine - public static final String HTML_HEAD_OPEN = ""; - public static final String HTML_HEAD_CLOSE = ""; - public static final String HTML_TITLE_OPEN = ""; - public static final String HTML_TITLE_CLOSE = ""; - public static final String HEAD_TO_INJECT = "" - + ""; - public static final String DEFAULT_TITLE = " - ArchWiki"; - - private WikiPageBuilder() { - - } - - /** - * Builds a page containing the title, url, and injects local css. - * - * @param stringUrl url to download. - * @param html StringBuilder containing the html of the wikipage - * @return {@link WikiPage} containing downloaded page. - */ - public static WikiPage buildPage(String stringUrl, StringBuilder html) { - String pageTitle = getPageTitle(html); - injectLocalCSS(html, LOCAL_CSS); - return new WikiPage(stringUrl, pageTitle, html.toString()); - } - - /** - * Finds the name of the page within the title block of the html. - * The returned string removes the " - ArchWiki" if found. - * - * @param htmlString The html of the page as a string. - * @return the extracted title from the page. - */ - public static String getPageTitle(StringBuilder htmlString) { - int titleStart = (htmlString.indexOf(HTML_TITLE_OPEN) + HTML_TITLE_OPEN.length()); - int titleEnd = htmlString.indexOf(HTML_TITLE_CLOSE, titleStart); - if (titleStart > 0 && titleEnd > titleStart) { // if there is an html title block - String title = htmlString.substring(titleStart, titleEnd); - return title.replace(DEFAULT_TITLE, ""); // drop DEFAULT_TITLE from page title - } - //todo should be handled somewhere else when no title is found - return "No title found"; - } - - /** - * Removes the contents within the head block of the html - * and replaces it with the a reference to a local css file. - * - * @param htmlString The html of the page as a string. - * @param localCSSFilePath The path of the css file to inject. - * @return true if the block was successfully replaced. - */ - public static boolean injectLocalCSS(StringBuilder htmlString, String localCSSFilePath) { - int headStart = htmlString.indexOf(HTML_HEAD_OPEN) + HTML_HEAD_OPEN.length(); - int headEnd = htmlString.indexOf(HTML_HEAD_CLOSE, headStart); - - if (headStart > 0 && headEnd >= headStart) { - String injectedHeadHtml = String.format(HEAD_TO_INJECT, localCSSFilePath); - htmlString.replace(headStart, headEnd, injectedHeadHtml); - return true; - } - - return false; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/jtmcn/archwiki/viewer/data/WikiPageBuilder.kt b/app/src/main/java/com/jtmcn/archwiki/viewer/data/WikiPageBuilder.kt new file mode 100644 index 0000000..517d614 --- /dev/null +++ b/app/src/main/java/com/jtmcn/archwiki/viewer/data/WikiPageBuilder.kt @@ -0,0 +1,68 @@ +package com.jtmcn.archwiki.viewer.data + +import com.jtmcn.archwiki.viewer.LOCAL_CSS + +/** + * Helps with creating a [WikiPage] by extracting content from the + * html fetched from the ArchWiki. + */ + +//NOTE: spaces are allowed in ""/etc, but parsing this way should be fine +const val HTML_HEAD_OPEN = "" +const val HTML_HEAD_CLOSE = "" +const val HTML_TITLE_OPEN = "" +const val HTML_TITLE_CLOSE = "" +private const val HEAD_TO_INJECT = "" + + "" +private const val DEFAULT_TITLE = " - ArchWiki" + +/** + * Builds a page containing the title, url, and injects local css. + * + * @param url url to download. + * @param html [StringBuilder] containing the html of the wikipage + * @return [WikiPage] containing downloaded page. + */ +fun buildPage(url: String, html: StringBuilder): WikiPage { + val pageTitle = getPageTitle(html) + injectLocalCSS(html, LOCAL_CSS) + return WikiPage(url, pageTitle, html.toString()) +} + +/** + * Finds the name of the page within the title block of the html. + * The returned string removes the " - ArchWiki" if found. + * + * @param htmlString The html of the page as a string. + * @return the extracted title from the page. + */ +fun getPageTitle(htmlString: StringBuilder): String? { + val titleStart = htmlString.indexOf(HTML_TITLE_OPEN) + HTML_TITLE_OPEN.length + val titleEnd = htmlString.indexOf(HTML_TITLE_CLOSE, titleStart) + if (titleStart in 1..titleEnd) { // if there is an html title block + val title = htmlString.substring(titleStart, titleEnd) + return title.replace(DEFAULT_TITLE, "") // drop DEFAULT_TITLE from page title + } + return null +} + +/** + * Removes the contents within the head block of the html + * and replaces it with the a reference to a local css file. + * + * @param htmlString The html of the page as a string. + * @param localCSSFilePath The path of the css file to inject. + * @return true if the block was successfully replaced. + */ +fun injectLocalCSS(htmlString: StringBuilder, localCSSFilePath: String): Boolean { + val headStart = htmlString.indexOf(HTML_HEAD_OPEN) + HTML_HEAD_OPEN.length + val headEnd = htmlString.indexOf(HTML_HEAD_CLOSE, headStart) + + if (headStart in 1..headEnd) { + val injectedHeadHtml = String.format(HEAD_TO_INJECT, localCSSFilePath) + htmlString.replace(headStart, headEnd, injectedHeadHtml) + return true + } + + return false +} \ No newline at end of file diff --git a/app/src/main/java/com/jtmcn/archwiki/viewer/tasks/Fetch.java b/app/src/main/java/com/jtmcn/archwiki/viewer/tasks/Fetch.java deleted file mode 100644 index 29c0cbd..0000000 --- a/app/src/main/java/com/jtmcn/archwiki/viewer/tasks/Fetch.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.jtmcn.archwiki.viewer.tasks; - -import android.os.AsyncTask; - -import com.jtmcn.archwiki.viewer.data.SearchResult; -import com.jtmcn.archwiki.viewer.data.SearchResultsBuilder; -import com.jtmcn.archwiki.viewer.data.WikiPage; -import com.jtmcn.archwiki.viewer.data.WikiPageBuilder; - -import java.util.List; - -/** - * Wrapper for {@link FetchUrl} which gives an easy to use interface - * for fetching {@link SearchResult} and {@link WikiPage}. - */ -public class Fetch { - public static final FetchUrl.FetchUrlMapper> SEARCH_RESULTS_MAPPER = - (url, stringBuilder) -> SearchResultsBuilder.parseSearchResults(stringBuilder.toString()); - - public static final FetchUrl.FetchUrlMapper WIKI_PAGE_MAPPER = - WikiPageBuilder::buildPage; - - private Fetch() { - - } - - /** - * Fetches a {@link List} from the url. - * - * @param onFinish The listener called when search results are ready. - * @param url The url to fetch the search results from. - * @return the async task fetching the data. - */ - public static AsyncTask> search( - FetchUrl.OnFinish> onFinish, - String url - ) { - return new FetchUrl<>(onFinish, SEARCH_RESULTS_MAPPER).execute(url); - } - - /** - * Fetches a {@link WikiPage} from the url. - * - * @param onFinish The listener called when the page is ready. - * @param url The url to fetch the page from. - * @return the async task fetching the data. - */ - public static AsyncTask page( - FetchUrl.OnFinish onFinish, - String url - ) { - return new FetchUrl<>(onFinish, WIKI_PAGE_MAPPER).execute(url); - } - -} diff --git a/app/src/main/java/com/jtmcn/archwiki/viewer/tasks/Fetch.kt b/app/src/main/java/com/jtmcn/archwiki/viewer/tasks/Fetch.kt new file mode 100644 index 0000000..d6d5ed7 --- /dev/null +++ b/app/src/main/java/com/jtmcn/archwiki/viewer/tasks/Fetch.kt @@ -0,0 +1,39 @@ +package com.jtmcn.archwiki.viewer.tasks + +import android.os.AsyncTask +import com.jtmcn.archwiki.viewer.data.SearchResult +import com.jtmcn.archwiki.viewer.data.WikiPage +import com.jtmcn.archwiki.viewer.data.buildPage +import com.jtmcn.archwiki.viewer.data.parseSearchResults + +/** + * Wrapper for [FetchUrl] which gives an easy to use interface + * for fetching [SearchResult] and [WikiPage]. + */ +object Fetch { + private val SEARCH_RESULTS_MAPPER = { _: String, html: StringBuilder -> parseSearchResults(html.toString()) } + + private val WIKI_PAGE_MAPPER = { url: String, html: StringBuilder -> buildPage(url, html) } + + /** + * Fetches a List from the url. + * + * @param onFinish The listener called when search results are ready. + * @param url The url to fetch the search results from. + * @return the async task fetching the data. + */ + fun search(onFinish: (List) -> Unit, url: String): AsyncTask> { + return FetchUrl(onFinish, SEARCH_RESULTS_MAPPER).execute(url) + } + + /** + * Fetches a [WikiPage] from the url. + * + * @param onFinish The listener called when the page is ready. + * @param url The url to fetch the page from. + * @return the async task fetching the data. + */ + fun page(onFinish: (WikiPage) -> Unit, url: String): AsyncTask { + return FetchUrl(onFinish, WIKI_PAGE_MAPPER).execute(url) + } +} diff --git a/app/src/main/java/com/jtmcn/archwiki/viewer/tasks/FetchUrl.java b/app/src/main/java/com/jtmcn/archwiki/viewer/tasks/FetchUrl.java deleted file mode 100644 index da0db51..0000000 --- a/app/src/main/java/com/jtmcn/archwiki/viewer/tasks/FetchUrl.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.jtmcn.archwiki.viewer.tasks; - -import android.os.AsyncTask; -import android.util.Log; - -import com.jtmcn.archwiki.viewer.utils.NetworkUtils; - -import java.io.IOException; - -/** - * Fetches a url, maps it to a {@link Result}, and returns it. - * - * @param The type which the fetched url's text will be mapped to. - */ -public class FetchUrl extends AsyncTask { - private static final String TAG = FetchUrl.class.getSimpleName(); - private final OnFinish onFinish; - private final FetchUrlMapper mapper; - private final boolean caching; - - public FetchUrl(OnFinish onFinish, FetchUrlMapper mapper) { - this(onFinish, mapper, true); - } - - /** - * Fetches the first url and notifies the {@link OnFinish} listener. - * - * @param onFinish The function to be called when the result is ready. - * @param mapper The function to map from the url and downloaded page to the desired type. - * @param caching Whether or not to use cached results - */ - public FetchUrl(OnFinish onFinish, FetchUrlMapper mapper, boolean caching) { - this.onFinish = onFinish; - this.mapper = mapper; - this.caching = caching; - } - - @Override - protected Result doInBackground(String... params) { - if (params.length >= 1) { - String url = params[0]; - StringBuilder toAdd = getItem(url); - return mapper.mapTo(url, toAdd); - } - return null; - } - - @Override - protected void onPostExecute(Result values) { - super.onPostExecute(values); - if (onFinish != null) { - onFinish.onFinish(values); - } - } - - /** - * Fetches a url and returns what was downloaded or null - * - * @param url to query - */ - private StringBuilder getItem(String url) { - StringBuilder toReturn; - try { - toReturn = NetworkUtils.fetchURL(url, caching); - } catch (IOException e) { //network exception - Log.w(TAG, "Could not connect to: " + url, e); - toReturn = new StringBuilder(); - } - - return toReturn; - } - - /** - * A listener which is called when {@link Result} is ready. - * - * @param the type of object which has been created. - */ - public interface OnFinish { - void onFinish(Result result); - } - - /** - * Maps the url and fetched text to {@link R} - * - * @param The type which the text will be mapped to. - */ - public interface FetchUrlMapper { - R mapTo(String url, StringBuilder sb); - } -} diff --git a/app/src/main/java/com/jtmcn/archwiki/viewer/tasks/FetchUrl.kt b/app/src/main/java/com/jtmcn/archwiki/viewer/tasks/FetchUrl.kt new file mode 100644 index 0000000..48263a4 --- /dev/null +++ b/app/src/main/java/com/jtmcn/archwiki/viewer/tasks/FetchUrl.kt @@ -0,0 +1,47 @@ +package com.jtmcn.archwiki.viewer.tasks + +import android.os.AsyncTask +import com.jtmcn.archwiki.viewer.utils.fetchURL +import timber.log.Timber +import java.io.IOException + +/** + * Fetches a url, [mapper] maps it to a [Result], and returns it. + * */ +class FetchUrl( + private val onFinish: (Result) -> Unit, + private val mapper: (url: String, html: StringBuilder) -> Result +) : AsyncTask() { + + override fun doInBackground(vararg params: String): Result? { + if (params.isNotEmpty()) { + val url = params[0] + val toAdd = getItem(url) + return mapper(url, toAdd) + } + return null + } + + override fun onPostExecute(values: Result) { + super.onPostExecute(values) + onFinish(values) + } + + /** + * Fetches a url and returns what was downloaded or null + * + * @param url to query + */ + private fun getItem(url: String): StringBuilder { + var toReturn: StringBuilder + try { + val response = fetchURL(url).execute().body()?.string() ?: "" + toReturn = StringBuilder(response) + } catch (e: IOException) { //network exception + Timber.w(e,"Could not connect to $url") + toReturn = StringBuilder() + } + + return toReturn + } +} diff --git a/app/src/main/java/com/jtmcn/archwiki/viewer/utils/AndroidUtils.java b/app/src/main/java/com/jtmcn/archwiki/viewer/utils/AndroidUtils.java deleted file mode 100644 index ee685de..0000000 --- a/app/src/main/java/com/jtmcn/archwiki/viewer/utils/AndroidUtils.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.jtmcn.archwiki.viewer.utils; - -import android.content.Context; -import android.content.Intent; -import android.net.Uri; - -/** - * Utilities class for Android specific actions. - */ -public class AndroidUtils { - - private AndroidUtils() { - - } - - /** - * Creates an intent to open a link. - * - * @param url The url to be opened. - * @param context The context needed to start the intent. - */ - public static void openLink(String url, Context context) { - Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - context.startActivity(intent); - } -} diff --git a/app/src/main/java/com/jtmcn/archwiki/viewer/utils/AndroidUtils.kt b/app/src/main/java/com/jtmcn/archwiki/viewer/utils/AndroidUtils.kt new file mode 100644 index 0000000..914663c --- /dev/null +++ b/app/src/main/java/com/jtmcn/archwiki/viewer/utils/AndroidUtils.kt @@ -0,0 +1,16 @@ +package com.jtmcn.archwiki.viewer.utils + +import android.content.Context +import android.content.Intent +import android.net.Uri + +/** + * Creates an intent to open a link. + * + * @param url The url to be opened. + * @param context The context needed to start the intent. + */ +fun openLink(url: String, context: Context) { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) + context.startActivity(intent) +} diff --git a/app/src/main/java/com/jtmcn/archwiki/viewer/utils/NetworkUtils.java b/app/src/main/java/com/jtmcn/archwiki/viewer/utils/NetworkUtils.java deleted file mode 100644 index 59bb998..0000000 --- a/app/src/main/java/com/jtmcn/archwiki/viewer/utils/NetworkUtils.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.jtmcn.archwiki.viewer.utils; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.HashMap; -import java.util.Map; - -/** - * Utility class for performing basic network tasks. - */ -public class NetworkUtils { - private static final Map downloadCache = new HashMap<>(); - - private NetworkUtils() { - - } - - /** - * Fetches a url with caching. - * - * @param stringUrl url to be fetched. - * @return the string that was fetched. - * @throws IOException on network failure. - */ - public static StringBuilder fetchURL(String stringUrl) throws IOException { - return fetchURL(stringUrl, true); - } - - /** - * Fetches a url with optional caching. - * - * @param stringUrl url to be fetched. - * @param useCache whether or not it should return a cached value. - * @return the string that was fetched. - * @throws IOException on network failure. - */ - public static StringBuilder fetchURL(String stringUrl, boolean useCache) throws IOException { - StringBuilder sb = new StringBuilder(); - URL url = new URL(stringUrl); - if (useCache && downloadCache.containsKey(url)) { - return new StringBuilder(downloadCache.get(url)); - } - HttpURLConnection urlConnection = (HttpURLConnection) url - .openConnection(); - - urlConnection.setReadTimeout(10000); // milliseconds - urlConnection.setConnectTimeout(15000); // milliseconds - urlConnection.setRequestMethod("GET"); - - BufferedReader in = new BufferedReader(new InputStreamReader( - urlConnection.getInputStream()), 8);// buffer 8k - - String line; - String lineSeparator = System.getProperty("line.separator"); - - while ((line = in.readLine()) != null) { - sb.append(line).append(lineSeparator); - } - - urlConnection.disconnect(); - in.close(); - - downloadCache.put(url, new StringBuilder(sb)); - return sb; - } -} diff --git a/app/src/main/java/com/jtmcn/archwiki/viewer/utils/NetworkUtils.kt b/app/src/main/java/com/jtmcn/archwiki/viewer/utils/NetworkUtils.kt new file mode 100644 index 0000000..956b27c --- /dev/null +++ b/app/src/main/java/com/jtmcn/archwiki/viewer/utils/NetworkUtils.kt @@ -0,0 +1,19 @@ + +package com.jtmcn.archwiki.viewer.utils + +import okhttp3.Call +import okhttp3.OkHttpClient +import okhttp3.Request + + + +private val client = OkHttpClient.Builder().build() +private val builder = Request.Builder() + +/** + * Fetches a url with optional caching. + * + * @param url url to be fetched. + * @param cb callback to handle result + */ +fun fetchURL(url: String): Call = client.newCall(builder.url(url).build()) diff --git a/app/src/main/java/com/jtmcn/archwiki/viewer/utils/SettingsUtils.java b/app/src/main/java/com/jtmcn/archwiki/viewer/utils/SettingsUtils.java deleted file mode 100644 index f778506..0000000 --- a/app/src/main/java/com/jtmcn/archwiki/viewer/utils/SettingsUtils.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.jtmcn.archwiki.viewer.utils; - -import android.content.Context; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; - -import com.jtmcn.archwiki.viewer.PreferencesActivity; - -/** - * Created by kevin on 6/7/2017. - */ - -public class SettingsUtils { - - public static int getFontSize(Context context) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - - // https://stackoverflow.com/questions/11346916/listpreference-use-string-array-as-entry-and-integer-array-as-entry-values-does - // the value of this preference must be parsed as a string - String fontSizePref = prefs.getString(PreferencesActivity.KEY_TEXT_SIZE, "2"); - return Integer.valueOf(fontSizePref); - - } -} diff --git a/app/src/main/java/com/jtmcn/archwiki/viewer/utils/SettingsUtils.kt b/app/src/main/java/com/jtmcn/archwiki/viewer/utils/SettingsUtils.kt new file mode 100644 index 0000000..8e33801 --- /dev/null +++ b/app/src/main/java/com/jtmcn/archwiki/viewer/utils/SettingsUtils.kt @@ -0,0 +1,48 @@ +package com.jtmcn.archwiki.viewer.utils + +import android.content.Context +import android.content.SharedPreferences +import androidx.preference.PreferenceManager +import com.jtmcn.archwiki.viewer.Prefs + +@Deprecated("Use getTextZoom", replaceWith = ReplaceWith("getTextZoom(this)")) +fun getTextSize(prefs: SharedPreferences): Int? { + if(!prefs.contains(Prefs.KEY_TEXT_SIZE)) { + return null + } + + // https://stackoverflow.com/questions/11346916/listpreference-use-string-array-as-entry-and-integer-array-as-entry-values-does + // the value of this preference must be parsed as a string + val fontSizePref = prefs.getString(Prefs.KEY_TEXT_SIZE, "2")!! + return Integer.valueOf(fontSizePref) + +} + +@Deprecated("Use getTextZoom") +fun textSizeToTextZoom(fontSize: Int) = when(fontSize) { + 0 -> 50 + 1 -> 75 + 2 -> 100 + 3 -> 150 + 4 -> 200 + else -> 100 +} + +/** + * gets the [Prefs.KEY_TEXT_ZOOM] preference, and if needed migrates from [getTextSize=] + */ +fun getTextZoom(context: Context): Int { + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + val textSize = getTextSize(prefs) + if(textSize != null) { + val textZoom = textSizeToTextZoom(textSize) + prefs.edit() + .putInt(Prefs.KEY_TEXT_ZOOM, textZoom) + .remove(Prefs.KEY_TEXT_SIZE) + .apply() + + return textZoom + } + + return prefs.getInt(Prefs.KEY_TEXT_ZOOM, 100) +} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index ff9a183..3f15a14 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -11,7 +11,7 @@ diff --git a/app/src/main/res/layout/activity_preferences.xml b/app/src/main/res/layout/activity_preferences.xml index cc3c025..0975e4c 100644 --- a/app/src/main/res/layout/activity_preferences.xml +++ b/app/src/main/res/layout/activity_preferences.xml @@ -1,20 +1,16 @@ - - + android:orientation="vertical" + android:fitsSystemWindows="true" + tools:context=".PreferencesActivity" > - + - + android:layout_height="match_parent" + android:layout_below="@id/settings_app_bar"/> \ No newline at end of file diff --git a/app/src/main/res/layout/toolbar.xml b/app/src/main/res/layout/toolbar.xml index 5ddffa9..97347a2 100644 --- a/app/src/main/res/layout/toolbar.xml +++ b/app/src/main/res/layout/toolbar.xml @@ -1,5 +1,6 @@ - - - - - Sehr klein - Klein - Normal - Groß - Sehr groß - - - diff --git a/app/src/main/res/values-fr/array.xml b/app/src/main/res/values-fr/array.xml deleted file mode 100644 index 31155e4..0000000 --- a/app/src/main/res/values-fr/array.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - Très petit - Petit - Normal - Grand - Très grand - - - diff --git a/app/src/main/res/values-it/array.xml b/app/src/main/res/values-it/array.xml deleted file mode 100644 index 3484b5d..0000000 --- a/app/src/main/res/values-it/array.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - Molto piccolo - Piccolo - Normale - Grande - Molto grande - - - diff --git a/app/src/main/res/values-iw/array.xml b/app/src/main/res/values-iw/array.xml deleted file mode 100644 index 6fbf36b..0000000 --- a/app/src/main/res/values-iw/array.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - הכי קטן - קטן - רגיל - גדול - הגי גדול - - - diff --git a/app/src/main/res/values-pt-rBR/array.xml b/app/src/main/res/values-pt-rBR/array.xml deleted file mode 100644 index df068d3..0000000 --- a/app/src/main/res/values-pt-rBR/array.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - Muito pequeno - Pequeno - Normal - Grande - Muito grande - - - diff --git a/app/src/main/res/values-sk/array.xml b/app/src/main/res/values-sk/array.xml deleted file mode 100644 index aad0d6c..0000000 --- a/app/src/main/res/values-sk/array.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - Najmenšia - Menšia - Normálna - Väčšia - Najväčšia - - - diff --git a/app/src/main/res/values/array.xml b/app/src/main/res/values/array.xml deleted file mode 100644 index c0b5f5a..0000000 --- a/app/src/main/res/values/array.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - Smallest - Smaller - Normal - Larger - Largest - - - - 0 - 1 - 2 - 3 - 4 - - - diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml new file mode 100644 index 0000000..f776307 --- /dev/null +++ b/app/src/main/res/xml/preferences.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/prefs.xml b/app/src/main/res/xml/prefs.xml deleted file mode 100644 index 76c6ef2..0000000 --- a/app/src/main/res/xml/prefs.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/test/java/com/jtmcn/archwiki/viewer/data/SearchResultsBuilderTest.java b/app/src/test/java/com/jtmcn/archwiki/viewer/data/SearchResultsBuilderTest.java deleted file mode 100644 index d630fa0..0000000 --- a/app/src/test/java/com/jtmcn/archwiki/viewer/data/SearchResultsBuilderTest.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.jtmcn.archwiki.viewer.data; - -import org.junit.Test; - -import java.util.List; - -import static junit.framework.Assert.assertEquals; - -public class SearchResultsBuilderTest { - private final String realResult = "[\"arch\", [\"Arch-based Distros\", \"Arch-based distributions\", \"Arch-chroot\", \"Arch32\", \"Arch64 FAQ\"],\n" + - "\t[\"\", \"\", \"\", \"\", \"\"],\n" + - "\t[\"https://wiki.archlinux.org/index.php/Arch-based_Distros\", \"https://wiki.archlinux.org/index.php/Arch-based_distributions\", \"https://wiki.archlinux.org/index.php/Arch-chroot\", \"https://wiki.archlinux.org/index.php/Arch32\", \"https://wiki.archlinux.org/index.php/Arch64_FAQ\"]\n" + - "]"; - - @Test - public void parseSearchResults() throws Exception { - List searchResults = SearchResultsBuilder.parseSearchResults(realResult); - - assertEquals("Arch-based Distros",searchResults.get(0).getPageName()); - assertEquals("https://wiki.archlinux.org/index.php/Arch-based_Distros", searchResults.get(0).getPageURL()); - - assertEquals("Arch-chroot",searchResults.get(2).getPageName()); - assertEquals("https://wiki.archlinux.org/index.php/Arch-chroot", searchResults.get(2).getPageURL()); - } - - @Test - public void getSearchQuery() { - String query = SearchResultsBuilder.getSearchQuery("arch"); - assertEquals("https://wiki.archlinux.org/api.php?action=opensearch&format=json&formatversion=2&namespace=0&limit=10&suggest=true&search=arch",query); - - String queryWithLength = SearchResultsBuilder.getSearchQuery("arch",9); - assertEquals("https://wiki.archlinux.org/api.php?action=opensearch&format=json&formatversion=2&namespace=0&limit=9&suggest=true&search=arch",queryWithLength); - } - - @Test - public void emptySearchCorrectFormat() throws Exception { - String fakeResult = "[\"\", [],\n" + - "\t[],\n" + - "\t[]\n" + - "]"; - List searchResults = SearchResultsBuilder.parseSearchResults(fakeResult); - assertEquals(0,searchResults.size()); - } - - @Test - public void emptyStringSearch() throws Exception { - String fakeResult = ""; - List searchResults = SearchResultsBuilder.parseSearchResults(fakeResult); - assertEquals(0,searchResults.size()); - } -} \ No newline at end of file diff --git a/app/src/test/java/com/jtmcn/archwiki/viewer/data/SearchResultsBuilderTest.kt b/app/src/test/java/com/jtmcn/archwiki/viewer/data/SearchResultsBuilderTest.kt new file mode 100644 index 0000000..b9728fc --- /dev/null +++ b/app/src/test/java/com/jtmcn/archwiki/viewer/data/SearchResultsBuilderTest.kt @@ -0,0 +1,48 @@ +package com.jtmcn.archwiki.viewer.data + +import org.junit.Test +import org.junit.Assert.* + +class SearchResultsBuilderTest { + private val realResult = """["arch", ["Arch-based Distros", "Arch-based distributions", "Arch-chroot", "Arch32", "Arch64 FAQ"], + ["", "", "", "", ""], + ["https://wiki.archlinux.org/index.php/Arch-based_Distros", "https://wiki.archlinux.org/index.php/Arch-based_distributions", "https://wiki.archlinux.org/index.php/Arch-chroot", "https://wiki.archlinux.org/index.php/Arch32", "https://wiki.archlinux.org/index.php/Arch64_FAQ"] +]""" + + @Test + @Throws(Exception::class) + fun parseSearchResultsTest() { + val searchResults = parseSearchResults(realResult) + assertEquals("Arch-based Distros", searchResults[0].pageName) + assertEquals("https://wiki.archlinux.org/index.php/Arch-based_Distros", searchResults[0].pageURL) + assertEquals("Arch-chroot", searchResults[2].pageName) + assertEquals("https://wiki.archlinux.org/index.php/Arch-chroot", searchResults[2].pageURL) + } + + @Test + fun getSearchQuery() { + val query: String = getSearchQuery("arch") + assertEquals("https://wiki.archlinux.org/api.php?action=opensearch&format=json&formatversion=2&namespace=0&suggest=true&search=arch&limit=10", query) + val queryWithLength: String = getSearchQuery("arch", 9) + assertEquals("https://wiki.archlinux.org/api.php?action=opensearch&format=json&formatversion=2&namespace=0&suggest=true&search=arch&limit=9", queryWithLength) + } + + @Test + @Throws(Exception::class) + fun emptySearchCorrectFormat() { + val fakeResult = "[\"\", [],\n" + + "\t[],\n" + + "\t[]\n" + + "]" + val searchResults = parseSearchResults(fakeResult) + assertEquals(0, searchResults.size) + } + + @Test + @Throws(Exception::class) + fun emptyStringSearch() { + val fakeResult = "" + val searchResults = parseSearchResults(fakeResult) + assertEquals(0, searchResults.size) + } +} \ No newline at end of file diff --git a/app/src/test/java/com/jtmcn/archwiki/viewer/data/WikiPageBuilderTest.java b/app/src/test/java/com/jtmcn/archwiki/viewer/data/WikiPageBuilderTest.java deleted file mode 100644 index 3178c5e..0000000 --- a/app/src/test/java/com/jtmcn/archwiki/viewer/data/WikiPageBuilderTest.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.jtmcn.archwiki.viewer.data; - -import com.jtmcn.archwiki.viewer.Constants; - -import org.junit.Test; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -public class WikiPageBuilderTest { - @Test - public void getPageTitle() throws Exception { - String fakeTitle = "fake title..1!@#!@#!"; - StringBuilder wrapped = new StringBuilder(WikiPageBuilder.HTML_TITLE_OPEN) - .append(fakeTitle) - .append(WikiPageBuilder.HTML_TITLE_CLOSE); - assertEquals(fakeTitle, WikiPageBuilder.getPageTitle(wrapped)); - } - - @Test - public void getEmptyTitle() throws Exception { - assertEquals("No title found", WikiPageBuilder.getPageTitle(new StringBuilder())); - } - - @Test - public void injectLocalCSS() throws Exception { - StringBuilder head = new StringBuilder(WikiPageBuilder.HTML_HEAD_OPEN) - .append(WikiPageBuilder.HTML_HEAD_CLOSE); - boolean passed = WikiPageBuilder.injectLocalCSS(head, Constants.LOCAL_CSS); - assertTrue(passed); - assertEquals("", - head.toString()); - } - - @Test - public void injectLocalCSSFail() throws Exception { - String fakeHead = " "; - StringBuilder head = new StringBuilder(fakeHead); - boolean passed = WikiPageBuilder.injectLocalCSS(head, Constants.LOCAL_CSS); - assertFalse(passed); - assertEquals(fakeHead, head.toString()); - } - -} \ No newline at end of file diff --git a/app/src/test/java/com/jtmcn/archwiki/viewer/data/WikiPageBuilderTest.kt b/app/src/test/java/com/jtmcn/archwiki/viewer/data/WikiPageBuilderTest.kt new file mode 100644 index 0000000..8e52ca0 --- /dev/null +++ b/app/src/test/java/com/jtmcn/archwiki/viewer/data/WikiPageBuilderTest.kt @@ -0,0 +1,47 @@ +package com.jtmcn.archwiki.viewer.data + +import com.jtmcn.archwiki.viewer.LOCAL_CSS +import org.junit.Assert.* + +import org.junit.Test + +class WikiPageBuilderTest { + @Test + @Throws(Exception::class) + fun getPageTitle() { + val fakeTitle = "fake title..1!@#!@#!" + assertEquals(fakeTitle, getPageTitle(wrappedTitle(fakeTitle))) + + assertEquals("", getPageTitle(wrappedTitle(""))) + } + + private fun wrappedTitle(fakeTitle: String) = + StringBuilder("${HTML_TITLE_OPEN}${fakeTitle}${HTML_TITLE_CLOSE}") + + @Test + @Throws(Exception::class) + fun getEmptyTitle() { + assertNull(getPageTitle(StringBuilder())) + } + + @Test + @Throws(Exception::class) + fun injectLocalCSS() { + val head = StringBuilder("${HTML_HEAD_OPEN}${HTML_HEAD_CLOSE}") + val passed = injectLocalCSS(head, LOCAL_CSS) + assertTrue(passed) + assertEquals("", + head.toString()) + } + + @Test + @Throws(Exception::class) + fun injectLocalCSSFail() { + val fakeHead = " " + val head = StringBuilder(fakeHead) + val passed = injectLocalCSS(head, LOCAL_CSS) + assertFalse(passed) + assertEquals(fakeHead, head.toString()) + } + +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 532a98c..1a2e6e8 100644 --- a/build.gradle +++ b/build.gradle @@ -1,23 +1,19 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { + ext.kotlin_version = '1.3.61' repositories { jcenter() - maven { - url 'https://maven.google.com/' - name 'Google' - } + google() } dependencies { - classpath 'com.android.tools.build:gradle:3.5.1' + classpath 'com.android.tools.build:gradle:3.5.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } allprojects { repositories { jcenter() - maven { - url 'https://maven.google.com/' - name 'Google' - } + google() } }