diff --git a/.gitignore b/.gitignore
index da4cf251..4543d919 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,4 +14,5 @@
.cxx
app/release/**
app/standard/**
-app/extended/**
\ No newline at end of file
+app/extended/**
+app/extendedGithub/**
diff --git a/README.md b/README.md
index 9185af23..fda15c80 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
# Native Alpha

-
-
+
+
[](https://github.com/cylonid/NativeAlphaForAndroid/releases)
[](https://somsubhra.github.io/github-release-stats/?username=cylonid&repository=NativeAlphaForAndroid&page=1&per_page=20)
[](https://github.com/cylonid/NativeAlphaForAndroid/blob/master/LICENSE)
@@ -14,32 +14,45 @@
* Create home screen shortcuts and retrieves icons in suitable resolution.
* Various settings (JavaScript, cookies, adblocking, location/camera/microphone access) can be set for every web app individually
* Navigation with multi-touch gestures while browsing.
- * Opt-in adblock using an AdBlock Plus custom webview.
+ * Opt-in adblock with user-selected filter lists.
* Less memory footprint and no privacy-invading app permissions in comparison to native apps
* Dark mode for Android 10+
## Download Options
[](https://apt.izzysoft.de/fdroid/index/apk/com.cylonid.nativealpha)
-[](https://github.com/cylonid/NativeAlphaForAndroid/releases/download/v1.3.0/NativeAlpha-standard-universal-release-v1.3.0.apk)
+[](https://github.com/cylonid/NativeAlphaForAndroid/releases/download/v1.5.2/NativeAlpha-extendedGithub-universal-release-v1.5.2.apk)
[](https://play.google.com/store/apps/details?id=com.cylonid.nativealpha)
### Paid Download
[](https://play.google.com/store/apps/details?id=com.cylonid.nativealpha.pro)
+
+
+
+[](https://liberapay.com/cylonid/donate)
+
## Paid Features
+
+__Note: From v1.5.0, the GitHub and IzzyOnDroid release is functionally equivalent to Native Alpha Plus.__
+
* Sandbox containers: Web Apps are loaded in fully separated sandboxes, cookies or other data are not shared with other Web Apps
* Kiosk Mode: Fullscreen with menubars hidden
* Biometric Access Protection: For every Web App, you can enable access protection (Fingerprint + fallback to lockscreen PIN)
* Experimental "Force Dark Mode" also available for websites (configurable with respect to day-time)
-## Latest Changes (v1.3.0)
+## Latest Major Changes (v1.5.x)
-* Resolved unusual going back behaviour on certain websites
-* Added support for Google OAuth-enabled sites
-* Context Menu: Long-press context menu with several options (Share, going back/forward, reload...)
-* Added pinch-to-zoom setting
-* Added option to freely set start URL of Web Apps to support non-standard URLs (expert settings)
-* Build for x86 and x86_64 platform included
-* Several bugfixes and general improvements
+* New adblock engine that allows users to add their own selection of block lists. By default, the app will download and use "Fanboy Ultimate List" from https://fanboy.co.nz. You can change your block list sources at any time.
+* Material Design 3-based components and theme
+* Cleaner main screen, less buttons: "Delete" and "Open settings" actions are available via swipe, Web Apps are opened by clicking on the label.
+* Login using HTTP Auth is supported
+* Several bugfixes, most notably regarding the top system bar on devices running Android 15
+
+### Small release (v1.5.2)
+* Fixed cases where OK button could not be pressed on intro screen
+* Changed system top bar to neutral color again
+* Fixed an issue with desktop mode on large screens
+* Fixed crashes when opening pop up menu
+* Information dialog regarding adblock-related crashes
### Native Alpha Plus
@@ -48,46 +61,55 @@
## FAQ
-*Q: Why would I need this app if any mobile browser can do the same?*
-
+
+ Q: Why would I need this app if any mobile browser can do the same?
A: Mobile browsers usually only are able to create shortcuts which give a native, borderless fullscreen experience if the website has a Progressive Web App (PWA) manifest. Unfortunately, most websites do not offer this feature yet. Additionally, you cannot set different settings for different websites with an usual browser.
+
-*Q: Can I keep multiple log-in sessions of the same website?*
-
+
+ Q: Can I keep multiple log-in sessions of the same website?
A: Yes, this is possible using the sandbox feature of Native Alpha Plus.
+
-*Q: Why isn't the sandbox feature in Native Alpha Plus enabled by default?*
-
+
+ Q: Why isn't the sandbox feature in Native Alpha Plus enabled by default?
A: The sandboxing approach is recommended for specific usage rather than general usage because it can limit the performance of the application and increase the disk usage. Therefore, use it for privacy-invasive websites or websites where you want to be logged in twice, but not for any website just because you can.
+
-*Q: Is this app a dedicated web browser with its own browser engine?*
-
+
+ Q: Is this app a dedicated web browser with its own browser engine?
A: No. As stated, this app relies on the system built-in Android WebView in order to display the website. For privacy reasons, you can opt to use alternative webviews such as [Bromite](https://www.bromite.org/system_web_view) on rooted phones. Always make sure to use to most recent version of any WebView implementation you use!
+
-*Q: Why is it not possible to find an icon for a certain website?*
-
+
+ Q: Why is it not possible to find an icon for a certain website?
A: This problem can occur due to multiple reasons. In most cases, the website does not offer a high-resolution icon. If you are a website maintainer and your website icon cannot be found, look at [RealFaviconGenerator](https://realfavicongenerator.net) for further information. If you think it should work, feel free to post the URL and I will look into it.
+
-*Q: In constrast to your promise, this app has a large memory footprint!*
-
+
+ Q: In constrast to your promise, this app has a large memory footprint!
A: This is because Native Alpha makes use of caching in the same way your browser app does, i.e., it saves web content locally on your device. Then it can be loaded faster if you visit the same page again. You can either delete cache regularly yourself or set the "Clear cache after usage" setting in the global settings if memory footprint is a concern for you. However, then websites will take a longer time to load because everything has to be loaded from net.
+
-*Q: What is the minimum Android version for running Native Alpha?*
-
-A: Oreo (8.0). This is because older versions use a discontinued API for creating screenshots which currently is not implemented.
-
-*Q: I don't want to use Google Play services, is there any other way to obtain Native Alpha Plus?*
+
+ Q: What is the minimum Android version for running Native Alpha?
+A: Android 9 and newer are supported.
+
-A: You can build the app yourself, everything is open-source including the paid features.
+
+ Q: I don't want to use Google Play services, is there any other way to obtain Native Alpha Plus?
+A: You can build the app yourself, everything is open-source including the paid features. Also, the GitHub release includes the Pro features.
+
-## Used libraries/resources
+## Notable used libraries/resources
* [CircularProgressBar](https://github.com/lopspower/CircularProgressBar)
* [JSoup](https://jsoup.org/)
-* [AdBlock+WebView](https://github.com/adblockplus/libadblockplus-android)
+* [AdblockAndroid](https://github.com/Edsuns/AdblockAndroid)
* [MovableFloatingActionButton](https://stackoverflow.com/questions/46370836/android-movable-draggable-floating-action-button-fab)
* [Android About Page](https://github.com/medyo/android-about-page)
* [Android Databinding](https://developer.android.com/topic/libraries/data-binding)
* [AboutLibraries](https://github.com/mikepenz/AboutLibraries)
+* [Drag & Drop n' Swipe Recyclerview](https://github.com/ernestoyaquello/DragDropSwipeRecyclerview)
For testing purposes:
* [Robolectric](https://github.com/robolectric/robolectric)
@@ -96,12 +118,15 @@ For testing purposes:
A list of used open-source libraries can also be found inside the app ("About" section).
## Screenshots
+
+ Click to see screenshots
+
## License
diff --git a/app/build.gradle b/app/build.gradle
index dc038ed7..27b5a6b1 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -2,30 +2,35 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'com.mikepenz.aboutlibraries.plugin'
-apply plugin: 'kotlin-android-extensions'
+
+aboutLibraries {
+ excludeFields = ["generated"]
+}
//https://gist.github.com/shakalaca/6422811
android {
- applicationVariants.all { variant ->
- variant.assembleProvider.get().doLast {
- delete fileTree('src/main/java/com/cylonid/nativealpha') {
- include '**/__WebViewActivity_*.java'
- }
- delete fileTree('src/main') {
- include '**/AndroidManifest.xml'
- }
- ant.move(file: 'src/main/AndroidManifest_original.xml', tofile:'src/main/AndroidManifest.xml')
-
- }
+ applicationVariants.all { variant ->
+ variant.assembleProvider.get().doLast {
+ delete fileTree('src/main/java/com/cylonid/nativealpha') {
+ include '**/__WebViewActivity_*.java'
+ }
+ delete fileTree('src/main') {
+ include '**/AndroidManifest.xml'
+ }
+ ant.move(file: 'src/main/AndroidManifest_original.xml', tofile:'src/main/AndroidManifest.xml')
+
+ }
+ }
+
+ dependenciesInfo {
+ includeInApk = false
}
-
- compileSdkVersion 32
- buildToolsVersion "32.1.0-rc1"
+ compileSdk 35
splits {
abi {
enable true
reset()
- include "armeabi-v7a", "arm64-v8a"
+ include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
universalApk true
}
}
@@ -34,30 +39,35 @@ android {
extended {
dimension "default"
applicationIdSuffix ".pro"
- resValue "string", "app_name_unicode", "Native α+"
+ resValue "string", "app_name", "Native Alpha +"
+ }
+ extendedGithub {
+ dimension "default"
+ resValue "string", "app_name", "Native Alpha"
}
standard {
dimension "default"
isDefault true
- resValue "string", "app_name_unicode", "Native α"
+ resValue "string", "app_name", "Native Alpha"
}
}
defaultConfig {
applicationId "com.cylonid.nativealpha"
- minSdkVersion 26
- targetSdkVersion 32
- versionCode 1305
- versionName "1.3.0"
+ minSdkVersion 28
+ targetSdkVersion 35
+ versionCode 1520
+ versionName "1.5.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunnerArguments clearPackageData: 'true'
}
buildFeatures {
dataBinding true
+ viewBinding true
}
kotlinOptions {
- jvmTarget = '1.8'
+ jvmTarget = '17'
}
buildTypes {
release {
@@ -66,7 +76,7 @@ android {
}
debug {
applicationIdSuffix = ".debug"
- resValue "string", "app_name_unicode", "_DEBUG-Nα"
+ resValue "string", "app_name", "_DEBUG-Native Alpha"
}
applicationVariants.all {
// this method is use to rename your release apk only
@@ -97,90 +107,83 @@ android {
execution 'ANDROIDX_TEST_ORCHESTRATOR'
}
compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
+ sourceCompatibility JavaVersion.VERSION_17
+ targetCompatibility JavaVersion.VERSION_17
}
+ namespace 'com.cylonid.nativealpha'
+
}
repositories {
- maven {
- url 'https://gitlab.com/api/v4/projects/8817162/packages/maven'
- }
+ google()
mavenCentral()
- jcenter()
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
- implementation 'com.mikhaellopez:circularprogressbar:3.0.3'
+ implementation 'com.mikhaellopez:circularprogressbar:3.1.0'
implementation 'org.jsoup:jsoup:1.14.1'
- // https://gitlab.com/eyeo/distpartners/libadblockplus-android/-/tags
- implementation 'org.adblockplus:adblock-android-webview:5.0.1'
implementation 'com.github.ihimanshurawat:Hasher:1.2'
- implementation 'androidx.navigation:navigation-fragment:2.5.2'
- implementation 'androidx.navigation:navigation-ui:2.5.2'
- implementation 'com.google.code.gson:gson:2.8.9'
+ implementation 'androidx.navigation:navigation-fragment:2.8.9'
+ implementation 'androidx.navigation:navigation-ui:2.8.9'
+ implementation 'com.google.code.gson:gson:2.11.0'
implementation 'io.github.medyo:android-about-page:2.0.0'
- implementation 'androidx.webkit:webkit:1.5.0'
+ implementation 'androidx.webkit:webkit:1.13.0'
implementation 'com.jakewharton:process-phoenix:2.1.2'
testImplementation 'junit:junit:4.13.2'
- testImplementation 'org.robolectric:robolectric:4.3.1'
- testImplementation 'org.json:json:20210307'
- androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.4.0'
- androidTestImplementation 'androidx.test.espresso:espresso-web:3.4.0'
- androidTestImplementation 'androidx.test:rules:1.4.0'
- androidTestImplementation 'androidx.test:runner:1.4.0'
- androidTestUtil 'androidx.test:orchestrator:1.4.1'
- androidTestImplementation 'androidx.test.ext:junit:1.1.3'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
- implementation "com.mikepenz:aboutlibraries:10.0.0"
- implementation "com.mikepenz:aboutlibraries-core:10.0.0"
+ testImplementation 'org.robolectric:robolectric:4.14.1'
+ testImplementation 'org.json:json:20250107'
+ testImplementation 'org.mockito:mockito-core:5.17.0'
+ testImplementation 'org.mockito:mockito-android:5.17.0'
+ testImplementation 'org.mockito.kotlin:mockito-kotlin:5.4.0'
+ androidTestImplementation 'org.mockito:mockito-android:5.17.0'
+ androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.6.1'
+ androidTestImplementation 'androidx.test.espresso:espresso-web:3.6.1'
+ androidTestImplementation 'androidx.test:rules:1.6.1'
+ androidTestImplementation 'androidx.test:runner:1.6.2'
+ androidTestUtil 'androidx.test:orchestrator:1.5.1'
+ androidTestImplementation 'androidx.test.ext:junit:1.2.1'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
+ implementation "com.mikepenz:aboutlibraries:11.6.3"
+ implementation "com.mikepenz:aboutlibraries-core:11.6.3"
implementation 'pub.devrel:easypermissions:3.0.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
implementation "androidx.biometric:biometric:1.1.0"
-
+ implementation 'com.ernestoyaquello.dragdropswiperecyclerview:drag-drop-swipe-recyclerview:1.2.0'
+ implementation "androidx.fragment:fragment-ktx:1.8.6"
+ implementation('com.github.Edsuns.AdblockAndroid:ad-filter:v0.9.1') {
+ exclude group: 'org.jetbrains.anko'
+ }
+ implementation 'androidx.work:work-runtime:2.10.0'
kapt "com.android.databinding:compiler:3.1.4"
+ implementation 'com.google.android.material:material:1.12.0'
}
-
-int NUM_OF_CONTAINERS = 8
-String placeholder = ""
-
-0.upto(NUM_OF_CONTAINERS-1) { int i ->
- def trTask = tasks.register( "createWebViewclass${i}", Copy ) {
- from 'src/main/java/com/cylonid/nativealpha/WebViewActivity.java'
- into 'src/main/java/com/cylonid/nativealpha'
- filter {
- String line -> line.replaceAll("WebViewActivity", "__WebViewActivity_${i}")
- }
- rename { String fileName ->
- fileName.replace("WebViewActivity.java", "__WebViewActivity_${i}.java")
- }
- }
- preBuild.dependsOn trTask
-}
-
def renameManifest = tasks.register("renameManifest") {
ant.move(file: 'src/main/AndroidManifest.xml', tofile:'src/main/AndroidManifest_original.xml')
}
preBuild.dependsOn renameManifest
+int NUM_OF_CONTAINERS = 8
def generateWebViewActivities = tasks.register( "extendAndroidManifest", Copy ) {
+ dependsOn(renameManifest)
from 'src/main/AndroidManifest_original.xml'
into 'src/main'
+ String placeholder = ""
String replacement = ""
for (int i = 0; i < NUM_OF_CONTAINERS; i++) {
replacement += "
+ TaskProvider trTask = tasks.register("createWebViewclass${i}", Copy) {
+ from 'src/main/java/com/cylonid/nativealpha/WebViewActivity.java'
+ into "src/main/java/com/cylonid/nativealpha/__WebViewActivity_${i}"
+ dependsOn(generateWebViewActivities)
+ mustRunAfter(i == 0 ? [] : tasks.getByName("createWebViewclass${i-1}"))
+
+ filter { String line ->
+ line.replaceAll("WebViewActivity", "__WebViewActivity_${i}")
+ }
+
+ rename { String fileName ->
+ fileName.replace("WebViewActivity.java", "__WebViewActivity_${i}.java")
+ }
+ }
+ preBuild.dependsOn(trTask)
+}
\ No newline at end of file
diff --git a/app/src/androidTest/java/com/cylonid/nativealpha/TestUtils.java b/app/src/androidTest/java/com/cylonid/nativealpha/TestUtils.java
index 302fdb1b..b532cc71 100644
--- a/app/src/androidTest/java/com/cylonid/nativealpha/TestUtils.java
+++ b/app/src/androidTest/java/com/cylonid/nativealpha/TestUtils.java
@@ -11,7 +11,14 @@
import androidx.test.espresso.UiController;
import androidx.test.espresso.ViewAction;
import androidx.test.espresso.ViewInteraction;
-
+import androidx.test.espresso.action.CoordinatesProvider;
+import androidx.test.espresso.action.GeneralSwipeAction;
+import androidx.test.espresso.action.Press;
+import androidx.test.espresso.action.Swipe;
+import androidx.test.espresso.matcher.ViewMatchers;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.junit.Assert;
@@ -58,6 +65,64 @@ public static void waitFor(final long ms) {
}
}
+ // Custom ViewAction to perform drag
+ public static ViewAction dragFromTo(final int amountInPixels) {
+ return new ViewAction() {
+ @Override
+ public Matcher getConstraints() {
+ return ViewMatchers.isDisplayed(); // Constraints to ensure the view is displayed
+ }
+
+ @Override
+ public String getDescription() {
+ return "Drag from a position given by resource ID and move by the specified amount of pixels";
+ }
+
+ @Override
+ public void perform(androidx.test.espresso.UiController uiController, View view) {
+ // Start position coordinates
+ CoordinatesProvider startCoordinates = v -> {
+ int[] location = new int[2];
+ v.getLocationOnScreen(location);
+ return new float[]{location[0] + v.getWidth() / 2f, location[1] + v.getHeight() / 2f};
+ };
+
+ // End position coordinates (move down by a specified amount of pixels)
+ CoordinatesProvider endCoordinates = v -> {
+ int[] location = new int[2];
+ v.getLocationOnScreen(location);
+ return new float[]{location[0] + v.getWidth() / 2f, location[1] + v.getHeight() / 2f + amountInPixels};
+ };
+
+ // Perform swipe action
+ new GeneralSwipeAction(Swipe.SLOW, startCoordinates, endCoordinates, Press.FINGER)
+ .perform(uiController, view);
+ }
+ };
+ }
+
+ public static Matcher getElementFromMatchAtPosition(final Matcher matcher, final int position) {
+ return new BaseMatcher() {
+ int counter = 0;
+ @Override
+ public boolean matches(final Object item) {
+ if (matcher.matches(item)) {
+ if(counter == position) {
+ counter++;
+ return true;
+ }
+ counter++;
+ }
+ return false;
+ }
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText("Element at hierarchy position " + position);
+ }
+ };
+ }
+
public static void waitForElementWithText(@IdRes int stringId) {
ViewInteraction element;
@@ -71,6 +136,16 @@ public static void waitForElementWithText(@IdRes int stringId) {
}
+ public static CoordinatesProvider percentX(final float percent) {
+ return view -> {
+ int[] location = new int[2];
+ view.getLocationOnScreen(location);
+ float x = location[0] + view.getWidth() * percent;
+ float y = location[1] + view.getHeight() / 2f;
+ return new float[]{x, y};
+ };
+ }
+
public static AppCompatActivity getCurrentActivity() {
final AppCompatActivity[] activity = new AppCompatActivity[1];
onView(isRoot()).check((view, noViewFoundException) -> activity[0] = (AppCompatActivity) view.getContext());
diff --git a/app/src/androidTest/java/com/cylonid/nativealpha/UITests.java b/app/src/androidTest/java/com/cylonid/nativealpha/UITests.java
index b174719d..6ca889eb 100644
--- a/app/src/androidTest/java/com/cylonid/nativealpha/UITests.java
+++ b/app/src/androidTest/java/com/cylonid/nativealpha/UITests.java
@@ -1,7 +1,8 @@
package com.cylonid.nativealpha;
-import androidx.appcompat.app.AppCompatDelegate;
import androidx.test.espresso.NoMatchingViewException;
+import androidx.test.espresso.action.GeneralSwipeAction;
+import androidx.test.espresso.action.ViewActions;
import androidx.test.espresso.web.webdriver.Locator;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.rule.ActivityTestRule;
@@ -9,37 +10,40 @@
import com.cylonid.nativealpha.model.DataManager;
import com.cylonid.nativealpha.model.WebApp;
+import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-
import java.util.List;
import java.util.concurrent.TimeUnit;
import static androidx.test.espresso.Espresso.onView;
-import static androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu;
+import static androidx.test.espresso.action.Press.FINGER;
+import static androidx.test.espresso.action.Swipe.FAST;
+import static androidx.test.espresso.action.ViewActions.actionWithAssertions;
import static androidx.test.espresso.action.ViewActions.clearText;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.scrollTo;
+import static androidx.test.espresso.action.ViewActions.swipeDown;
import static androidx.test.espresso.action.ViewActions.typeText;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
-import static androidx.test.espresso.matcher.ViewMatchers.withTagValue;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static androidx.test.espresso.web.assertion.WebViewAssertions.webMatches;
import static androidx.test.espresso.web.model.Atoms.getCurrentUrl;
import static androidx.test.espresso.web.sugar.Web.onWebView;
import static androidx.test.espresso.web.webdriver.DriverAtoms.findElement;
import static androidx.test.espresso.web.webdriver.DriverAtoms.getText;
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static com.cylonid.nativealpha.TestUtils.dragFromTo;
import static org.hamcrest.Matchers.allOf;
-import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.core.StringContains.containsString;
import static org.junit.Assert.assertEquals;
+import android.view.View;
+
/**
* Instrumented test, which will execute on an Android device.
*
@@ -57,39 +61,58 @@ public void addWebsite() {
onView(withId(R.id.switchCreateShortcut)).perform(click());
onView(withId(android.R.id.button1)).perform(click());
assertEquals(DataManager.getInstance().getWebApp(0).getBaseUrl(), "https://github.com");
- onView(allOf(withTagValue(is("btnOpenWebview0")), isDisplayed())).perform(click());
+ onView(allOf(withId(R.id.btnWebAppTitle), isDisplayed())).perform(click());
+ }
+
+ @Test
+ public void addMultipleWebsiteAndTestLoadedUrl() {
+ initMultipleWebsites(List.of("github.com", "orf.at"));
+ onView(TestUtils.getElementFromMatchAtPosition(withId(R.id.btnWebAppTitle), 1)).perform(click());
+ onWebView(Matchers.allOf(withId(R.id.webview))).withNoTimeout().check(webMatches(getCurrentUrl(), containsString("orf.at")));
+ }
+
+ @Test
+ public void setCustomOrderOfWebapps() {
+ initMultipleWebsites(List.of("github.com", "orf.at"));
+ onView(TestUtils.getElementFromMatchAtPosition(withId(R.id.dragAnchor), 0)).perform(swipeDown());
+ Matcher anchor = TestUtils.getElementFromMatchAtPosition(withId(R.id.dragAnchor), 0);
+
+ onView(anchor)
+ .perform(actionWithAssertions(dragFromTo(500)));
+ assertEquals(DataManager.getInstance().getWebApp(0).getOrder(), 1);
+
}
@Test
public void startWebView() {
- initSingleWebsite("https://twitter.com");
- onView(allOf(withTagValue(is("btnOpenWebview0")), isDisplayed())).perform(click());
+ initSingleWebsite("https://github.com");
+ onView(allOf(withId(R.id.btnWebAppTitle))).perform(click());
onView(withId(R.id.webview)).check(matches(isDisplayed()));
}
@Test(expected = NoMatchingViewException.class)
public void deleteWebsite() {
- initSingleWebsite("https://twitter.com");
- onView(allOf(withTagValue(is("btnDelete0")))).perform(click());
+ initSingleWebsite("https://github.com");
+ onView(allOf(withId(R.id.webAppListItem))).perform(ViewActions.swipeLeft());
TestUtils.alertDialogAccept();
- onView(allOf(withTagValue(is("btnDelete0")))).check(matches(not(isDisplayed()))); //Throws exception
+ onView(allOf(withId(R.id.webAppListItem))).check(matches(not(isDisplayed()))); //Throws exception
}
@Test
public void changeWebAppSettings() {
- initSingleWebsite("https://whatismybrowser.com/detect/are-cookies-enabled");
- onView(allOf(withTagValue(is("btnSettings0")))).perform(click());
- onView(withId(R.id.switchCookies)).perform(scrollTo()).perform(click());
+ initSingleWebsite("https://whatismybrowser.com/detect/are-third-party-cookies-enabled");
+ onView(withId(R.id.webAppListItem)).perform(new GeneralSwipeAction(FAST, TestUtils.percentX(0.25f), TestUtils.percentX(0.9f), FINGER));
+ onView(withId(R.id.switch3PCookies)).perform(scrollTo()).perform(click());
onView(withId(R.id.btnSave)).perform(click());
- onView(allOf(withTagValue(is("btnOpenWebview0")), isDisplayed())).perform(click());
- onWebView(Matchers.allOf(withId(R.id.webview))).withNoTimeout().withElement(findElement(Locator.ID, "detected_value")).check(webMatches(getText(), containsString("No")));
+ onView(allOf(withId(R.id.btnWebAppTitle), isDisplayed())).perform(click());
+ onWebView(Matchers.allOf(withId(R.id.webview))).withNoTimeout().withElement(findElement(Locator.ID, "detected_value")).check(webMatches(getText(), containsString("Yes")));
}
@Test
public void badSSLAccept() {
initSingleWebsite("https://untrusted-root.badssl.com/");
- onView(allOf(withTagValue(is("btnOpenWebview0")), isDisplayed())).perform(click());
+ onView(allOf(withId(R.id.btnWebAppTitle), isDisplayed())).perform(click());
TestUtils.waitForElementWithText(R.string.load_anyway);
onView(withText(R.string.load_anyway)).perform(click());
onWebView(Matchers.allOf(withId(R.id.webview))).withNoTimeout().withElement(findElement(Locator.ID, "content")).check(webMatches(getText(), containsString("untrusted-root")));
@@ -98,7 +121,7 @@ public void badSSLAccept() {
@Test(expected = java.lang.RuntimeException.class)
public void badSSLDismiss() {
initSingleWebsite("https://untrusted-root.badssl.com/");
- onView(allOf(withTagValue(is("btnOpenWebview0")), isDisplayed())).perform(click());
+ onView(allOf(withId(R.id.btnWebAppTitle), isDisplayed())).perform(click());
TestUtils.waitForElementWithText(android.R.string.cancel);
onView(withText(android.R.string.cancel)).perform(click());
onWebView(Matchers.allOf(withId(R.id.webview))).withTimeout(3, TimeUnit.SECONDS).withElement(findElement(Locator.ID, "content")).check(webMatches(getText(), containsString("untrusted-root")));
@@ -107,48 +130,22 @@ public void badSSLDismiss() {
@Test
public void openHTTPSite() {
- initSingleWebsite("http://annozone.de");
- onView(allOf(withTagValue(is("btnOpenWebview0")), isDisplayed())).perform(click());
+ initSingleWebsite("http://httpforever.com/");
+ onView(allOf(withId(R.id.btnWebAppTitle), isDisplayed())).perform(click());
TestUtils.waitForElementWithText(android.R.string.cancel);
onView(withId(android.R.id.button2)).perform(scrollTo()).perform(click());
-// onView(isRoot()).perform(ViewActions.pressBack());
- onView(allOf(withTagValue(is("btnOpenWebview0")), isDisplayed())).perform(click());
+ onView(allOf(withId(R.id.btnWebAppTitle), isDisplayed())).perform(click());
TestUtils.waitForElementWithText(android.R.string.cancel);
onView(withId(android.R.id.button1)).perform(scrollTo()).perform(click());
- onWebView(Matchers.allOf(withId(R.id.webview))).withTimeout(6, TimeUnit.SECONDS).check(webMatches(getCurrentUrl(), containsString("annozone")));
+ onWebView(Matchers.allOf(withId(R.id.webview))).withTimeout(6, TimeUnit.SECONDS).check(webMatches(getCurrentUrl(), containsString("httpforever.com")));
}
-// @Test
-// public void changeUIModes() {
-// String[] ui_modes = activityTestRule.getActivity().getResources().getStringArray(R.array.ui_modes);
-// TestUtils.alertDialogDismiss();
-//
-// //Open settings, set to dark mode and cancel
-// openActionBarOverflowOrOptionsMenu(getInstrumentation().getTargetContext());
-// onView(withText(R.string.action_settings)).perform(click());
-// onView(withId(R.id.dropDownTheme)).perform(click());
-// onView(withText(ui_modes[2])).perform(click());
-// assertEquals(AppCompatDelegate.getDefaultNightMode(), AppCompatDelegate.MODE_NIGHT_YES);
-// onView(withId(R.id.btnCancel)).perform(click());
-//
-// //Check that default mode is restored, change to light mode and check light mode
-// assertEquals(AppCompatDelegate.getDefaultNightMode(), AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
-// openActionBarOverflowOrOptionsMenu(getInstrumentation().getTargetContext());
-// onView(withText(R.string.action_settings)).perform(click());
-// onView(withId(R.id.dropDownTheme)).perform(click());
-// onView(withText(ui_modes[1])).perform(click());
-// onView(withId(R.id.btnSave)).perform(click());
-// assertEquals(AppCompatDelegate.getDefaultNightMode(), AppCompatDelegate.MODE_NIGHT_NO);
-//
-// }
-
-
private void initSingleWebsite(final String base_url) {
activityTestRule.getActivity().runOnUiThread(() -> {
DataManager.getInstance().addWebsite(new WebApp(base_url, DataManager.getInstance().getIncrementedID()));
- activityTestRule.getActivity().addActiveWebAppsToUI();
+ activityTestRule.getActivity().updateWebAppList();
});
TestUtils.acceptLicense();
@@ -161,8 +158,9 @@ private void initMultipleWebsites(final List urls) {
for (String base_url : urls) {
DataManager.getInstance().addWebsite(new WebApp(base_url, DataManager.getInstance().getIncrementedID()));
}
- activityTestRule.getActivity().addActiveWebAppsToUI();
+ activityTestRule.getActivity().updateWebAppList();
});
+ TestUtils.acceptLicense();
//Get rid of welcome message
TestUtils.alertDialogDismiss();
}
diff --git a/app/src/androidTest/kotlin/com/cylonid/nativealpha/AdblockProviderApiHelperTest.kt b/app/src/androidTest/kotlin/com/cylonid/nativealpha/AdblockProviderApiHelperTest.kt
new file mode 100644
index 00000000..8b36384a
--- /dev/null
+++ b/app/src/androidTest/kotlin/com/cylonid/nativealpha/AdblockProviderApiHelperTest.kt
@@ -0,0 +1,74 @@
+package com.cylonid.nativealpha
+
+import com.cylonid.nativealpha.helper.AdblockProviderApiHelper
+import com.cylonid.nativealpha.model.AdblockConfig
+import io.github.edsuns.adfilter.AdFilter
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.junit.MockitoJUnitRunner
+
+@RunWith(MockitoJUnitRunner::class)
+class AdblockProviderApiHelperTest {
+
+ private lateinit var adblockProviderApiHelper: AdblockProviderApiHelper
+ private lateinit var adFilterProvider: AdFilter
+
+
+ @Before
+ fun setUp() {
+ adFilterProvider = AdFilter.create(mock())
+ adblockProviderApiHelper = AdblockProviderApiHelper(adFilterProvider)
+ }
+
+
+ private fun getActiveUrls(): List {
+ return adFilterProvider.viewModel.filters.value?.values
+ ?.map { it.url } ?: emptyList()
+ }
+
+ @Test
+ fun shouldInitEmptyRuntimeConfig() {
+ val ourConfig = listOf(
+ AdblockConfig("Test", "https://someendpoint.xyzabc"),
+ AdblockConfig("Test2", "https://someendpoint.xyzdef")
+ )
+ adblockProviderApiHelper.synchronizeAdblockProviderWithSettings(ourConfig)
+ Assert.assertEquals(
+ listOf("https://someendpoint.xyzabc", "https://someendpoint.xyzdef"),
+ getActiveUrls()
+ )
+ }
+
+ @Test
+ fun shouldAddToExistingRuntimeConfig() {
+ adFilterProvider.viewModel.addFilter("Test", "https://someendpoint.xyzabc")
+ val ourConfig = listOf(
+ AdblockConfig("Test", "https://someendpoint.xyzabc"),
+ AdblockConfig("Test2", "https://someendpoint.xyzdef")
+ )
+ adblockProviderApiHelper.synchronizeAdblockProviderWithSettings(ourConfig)
+ Assert.assertEquals(
+ listOf("https://someendpoint.xyzabc", "https://someendpoint.xyzdef"),
+ getActiveUrls()
+ )
+ }
+
+ @Test
+ fun shouldRemoveDeletedRuntimeConfig() {
+ val ourConfig = listOf(
+ AdblockConfig("Test", "https://someendpoint.xyzabc"),
+ AdblockConfig("Test2", "https://someendpoint.xyzdef")
+ )
+ adblockProviderApiHelper.synchronizeAdblockProviderWithSettings(ourConfig)
+
+ adblockProviderApiHelper.synchronizeAdblockProviderWithSettings(emptyList())
+ Assert.assertEquals(
+ emptyList(),
+ getActiveUrls()
+ )
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 8cfbe950..933beb54 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -19,7 +19,7 @@
android:name="com.cylonid.nativealpha.util.App"
android:allowBackup="true"
android:icon="@mipmap/native_alpha"
- android:label="@string/app_name_unicode"
+ android:label="@string/app_name"
android:roundIcon="@mipmap/native_alpha"
android:supportsRtl="true"
android:screenOrientation="portrait"
@@ -42,9 +42,10 @@
+ android:theme="@style/AppTheme">
+ android:theme="@style/AppTheme">
+ android:theme="@style/AppTheme">
+ android:theme="@style/AppTheme">
+
+
+
@@ -22,21 +25,20 @@
Aktuelle Neuerungen
- Version: v1.3.0
-
- Zurück-Verhalten auf div. Webseiten verbessert
- Unterstützung für Webseiten mit Google OAuth
- Kontextmenü: Langes Drücken öffnen Menü mit mehreren Optionen (Teilen, Vor/Zurück, Neu laden...)
- Pinch-To-Zoom-Einstellung hinzugefügt
- Start-URL kann nun frei gewählt werden (Experten-Einstellungen)
- Build für x86- und x86_64-Plattformen enthalten
- Mehrere Bugfixes und generelle Verbesserungen
-
- Native Alpha Plus
+ Version: v1.5.0
+
- Biometrische Zugriffskontrolle: Für jede Web-App kann getrennt eine Zugriffskontrolle aktiviert werden (Fingerabdruck / Lockscreen-PIN als Fallback)
- Weitere Verbesserungen des Dark Modes
+ Neue Adblock-Engine, mit der Nutzer ihre eigene Auswahl an Blocklisten hinzufügen können. Standardmäßig lädt die App die „Fanboy Ultimate List“ von https://fanboy.co.nz herunter und verwendet sie. Sie können die Quellen Ihrer Blocklisten jederzeit ändern
+ Komponenten und Design basierend auf Material Design 3
+ Aufgeräumtere Hauptansicht, weniger Schaltflächen: „Löschen“ und „Einstellungen öffnen“ sind per Wischgeste verfügbar, Web-Apps werden durch Tippen auf das Label geöffnet
+ Login mittels HTTP Auth wird unterstützt
+ Seiten mit color-scheme (z.B. wikipedia.org) werden nun automatisch gemäß dem aktivem System-Farbschema (hell/dunkel) dargestellt.
+ Mehrere Fehlerbehebungen, insbesondere betreffend die obere Systemleiste auf Geräten mit Android 15
+
+
+
+ Nachdem Sie die über GitHub bzw. IzzyOnDroid verteilte App nutzen, erhalten Sie kostenlosen Zugang zu allen Features von Native Alpha Plus. Wenn Sie die App nützlich finden, können Sie den Entwickler auf Liberapay unterstützen.
Nutzungsbedingungen - Haftungsausschluss
@@ -52,6 +54,9 @@ Nutzungsbedingungen - Haftungsausschluss
oder
der Nutzung oder anderen Geschäften mit der Software ergeben.
+
+ OK
+
diff --git a/app/src/main/assets/news/latestUpdate_en.html b/app/src/main/assets/news/latestUpdate_en.html
index 98638d9d..f073f4f7 100644
--- a/app/src/main/assets/news/latestUpdate_en.html
+++ b/app/src/main/assets/news/latestUpdate_en.html
@@ -11,6 +11,9 @@
function hideById(id) {
document.getElementById(id).style.display = 'none';
}
+ function showById(id) {
+ document.getElementById(id).style.display = 'block';
+ }
@@ -22,27 +25,27 @@
News
- Version: v1.3.0
-
- Resolved unusual going back behaviour on certain websites
- Added support for Google OAuth-enabled sites
- Context Menu: Long-press context menu with several options (Share, going back/forward, reload...)
- Added pinch-to-zoom setting
- Added option to freely set start URL of Web Apps to support non-standard URLs (expert settings)
- Build for x86 and x86_64 platform included
- Several bugfixes and general improvements
-
- Native Alpha Plus
+ Version: v1.5.0
+
- Biometric access Protection: For every Web App, you can enable access protection (
- Fingerprint + fallback to lockscreen PIN)
- Further enhancements for Dark Mode
+ New adblock engine that allows users to add their own selection of block lists. By default, the app will download and use "Fanboy Ultimate List" from https://fanboy.co.nz. You can change your block list sources at any time
+ Material Design 3-based components and theme
+ Cleaner main screen, less buttons: "Delete" and "Open settings" actions are available via swipe, Web Apps are opened by clicking on the label
+ Login using HTTP Auth is supported
+ Websites with implemented native color-scheme (e.g., wikipedia.org) will now be displayed with respect to the active system theme (light/dark)
+ Several bugfixes, most notably regarding the top system bar on devices running Android 15
+
+
+
+
+
+ As you are using the app version distributed via GitHub or IzzyOnDroid, you get access to all Plus features free of charge. If you find Native Alpha useful, please consider supporting the developer on Liberapay .
Terms of Usage - Nonliability
This app is published under GNU General Public
- License v3. The source code is freely accessible on GitHub. More information in the "About" section.
+ License v3. The source code is freely accessible on GitHub. More information in the "About" section.
The software is provided “as is”, without warranty of any kind, express or implied, including but not limited
to
@@ -52,6 +55,9 @@
Terms of Usage - Nonliability
dealings
in the software.
+
+ OK
+
diff --git a/app/src/main/assets/news/news.css b/app/src/main/assets/news/news.css
index 42b96ba0..ec5b94b4 100644
--- a/app/src/main/assets/news/news.css
+++ b/app/src/main/assets/news/news.css
@@ -1,3 +1,31 @@
+:root {
+ --custom-red: #b71c1c;
+ --tonal-border-radius: 20px;
+ --tonal-font-weight: 500;
+ --tonal-font-size: 14px;
+ --button-max-width: 120px;
+ --button-margin: 15px;
+ --tonal-bg: #55442A;
+ --tonal-fg: #F8DFBB;
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ --tonal-bg: #FFDAD6;
+ --tonal-fg: #5D3F3C;
+ }
+}
+
+
+body {
+ font-family: Arial, Helvetica, sans-serif;
+ user-select: none;
+}
+
+button {
+ -webkit-tap-highlight-color: transparent;
+}
+
header {
display: flex;
justify-content: center;
@@ -12,6 +40,12 @@ header img {
#wrapper {
display: flex;
flex-direction: row;
+ justify-content: center;
+}
+
+#update {
+ display: flex;
+ flex-direction: column;
}
main {
@@ -20,6 +54,7 @@ main {
width: 80%;
flex-direction: column;
overflow-wrap: break-word;
+ text-align: center;
}
aside {
@@ -36,10 +71,42 @@ ul {
padding: 0;
}
+.list-wrapper {
+ text-align: start;
+ margin: 0 auto;
+}
+
ul li::before {
- content: "✔";
- color: rgb(122, 122, 122);
- font-weight: bold;
+ content: "»";
+ font-weight: 1000;
+ font-size: 18pt;
display: inline-block;
- width: 2em;
+ width: 1em;
+ color: var(--custom-red);
+}
+
+#nonGp {
+ display: none;
+ text-align: start;
+}
+
+.button-wrapper {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--button-margin);
+ justify-content: flex-end
+}
+
+.tonal-button {
+ background-color: var(--tonal-bg);
+ color: var(--tonal-fg);
+ border: none;
+ border-radius: var(--tonal-border-radius);
+ padding: 10px 16px;
+ font-size: var(--tonal-font-size);
+ font-weight: var(--tonal-font-weight);
+ cursor: pointer;
+ max-width: var(--button-max-width);
+ width: 100%;
+ text-align: center;
}
\ No newline at end of file
diff --git a/app/src/main/java/com/cylonid/nativealpha/AboutActivity.java b/app/src/main/java/com/cylonid/nativealpha/AboutActivity.java
deleted file mode 100644
index 53a0e443..00000000
--- a/app/src/main/java/com/cylonid/nativealpha/AboutActivity.java
+++ /dev/null
@@ -1,104 +0,0 @@
-package com.cylonid.nativealpha;
-
-
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.view.View;
-
-import androidx.appcompat.app.AppCompatActivity;
-
-import com.mikepenz.aboutlibraries.LibsBuilder;
-
-import java.time.Year;
-
-import mehdi.sakout.aboutpage.AboutPage;
-import mehdi.sakout.aboutpage.Element;
-
-public class AboutActivity extends AppCompatActivity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- View aboutPage = new AboutPage(this)
- .setDescription("Native Alpha for Android\nby cylonid © " + Year.now().getValue())
- .setImage(R.drawable.native_alpha_foreground)
- .addItem(new Element().setTitle("Version " + BuildConfig.VERSION_NAME))
- .addGitHub("cylonid", "GitHub")
- .addPlayStore("com.cylonid.nativealpha.pro", "Play Store")
- .addWebsite("https://github.com/cylonid/NativeAlphaForAndroid/blob/110releasePreparations/privacy_policy.md", getString(R.string.privacy_policy))
- .addGroup(getString(R.string.eula_title))
- .addItem(showEULA())
- .addGroup(getString(R.string.license))
- .addItem(showLicense())
- .addItem(showOpenSourcelibs())
- .create();
-
- setContentView(aboutPage);
- }
-
- Element showEULA() {
- Element license = new Element();
- license.setTitle(getString(R.string.eula_content));
- return license;
- }
- Element showLicense() {
- Element license = new Element();
-
- license.setTitle(getString(R.string.gnu_license));
- license.setOnClickListener(v -> {
- String url = "https://www.gnu.org/licenses/gpl-3.0.txt";
- Intent i = new Intent(Intent.ACTION_VIEW);
- i.setData(Uri.parse(url));
- startActivity(i);
- });
- return license;
-
- }
- Element showPayPal() {
- Element license = new Element();
-
- license.setTitle(getString(R.string.paypal));
- license.setOnClickListener(v -> {
- String url = "https://paypal.me/cylonid";
- Intent i = new Intent(Intent.ACTION_VIEW);
- i.setData(Uri.parse(url));
- startActivity(i);
- });
- return license;
-
- }
-
- Element showOpenSourcelibs() {
- Element os = new Element();
- os.setTitle(getString(R.string.open_source_libs));
- os.setOnClickListener(v -> {
- startActivity(new LibsBuilder()
- .withEdgeToEdge(true)
- .withSearchEnabled(true)
- .intent(this));
- });
- return os;
- }
-
-
-
-// Element getCopyRightsElement() {
-// Element copyRightsElement = new Element();
-// final String copyrights = String.format(getString(R.string.copy_right), Calendar.getInstance().get(Calendar.YEAR));
-// copyRightsElement.setTitle(copyrights);
-// copyRightsElement.setIconDrawable(R.drawable.about_icon_copy_right);
-// copyRightsElement.setAutoApplyIconTint(true);
-// copyRightsElement.setIconTint(mehdi.sakout.aboutpage.R.color.about_item_icon_color);
-// copyRightsElement.setIconNightTint(android.R.color.white);
-// copyRightsElement.setGravity(Gravity.CENTER);
-// copyRightsElement.setOnClickListener(new View.OnClickListener() {
-// @Override
-// public void onClick(View v) {
-// Toast.makeText(AboutActivity.this, copyrights, Toast.LENGTH_SHORT).show();
-// }
-// });
-// return copyRightsElement;
-// }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/cylonid/nativealpha/AboutActivity.kt b/app/src/main/java/com/cylonid/nativealpha/AboutActivity.kt
new file mode 100644
index 00000000..25966b4e
--- /dev/null
+++ b/app/src/main/java/com/cylonid/nativealpha/AboutActivity.kt
@@ -0,0 +1,154 @@
+package com.cylonid.nativealpha
+
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.view.View
+import androidx.activity.addCallback
+import androidx.appcompat.app.AppCompatActivity
+import com.cylonid.nativealpha.databinding.ActivityToolbarBaseBinding
+import com.cylonid.nativealpha.util.ColorUtils.getColorResFromThemeAttr
+import com.mikepenz.aboutlibraries.LibsBuilder
+import mehdi.sakout.aboutpage.AboutPage
+import mehdi.sakout.aboutpage.Element
+import java.time.Year
+
+class AboutActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ val baseBinding = ActivityToolbarBaseBinding.inflate(layoutInflater)
+ setContentView(baseBinding.root)
+
+ baseBinding.activityContent.addView(generateAboutPageView())
+
+ val toolbar = baseBinding.toolbar.topAppBar
+ setSupportActionBar(toolbar)
+ supportActionBar?.setDisplayHomeAsUpEnabled(true)
+ supportActionBar?.title = getString(R.string.app_info)
+
+ onBackPressedDispatcher.addCallback(this) {
+ finish()
+ }
+
+ toolbar.setNavigationOnClickListener {
+ onBackPressedDispatcher.onBackPressed()
+ }
+
+ }
+
+ private fun generateAboutPageView(): View {
+ val page = AboutPage(this).apply {
+ setDescription(
+ """
+ Native Alpha for Android
+ by cylonid © ${Year.now().value}
+ """.trimIndent()
+ )
+ setImage(R.drawable.native_alpha_foreground)
+ addItem(Element().setTitle("Version " + BuildConfig.VERSION_NAME))
+ addItem(addGitHubCustom("cylonid", "GitHub"))
+ addPlayStore("com.cylonid.nativealpha.pro", "Play Store")
+ addWebsite(
+ "https://github.com/cylonid/NativeAlphaForAndroid/blob/dev/privacy_policy.md",
+ getString(
+ R.string.privacy_policy
+ )
+ )
+ if(BuildConfig.FLAVOR == "extendedGithub") {
+ addItem(showLiberaPay())
+ }
+ addGroup(getString(R.string.eula_title))
+ addItem(showEULA())
+ addGroup(getString(R.string.license))
+ addItem(showLicense())
+ addItem(showOpenSourcelibs())
+ }
+
+ return page.create()
+
+ }
+
+ private fun addGitHubCustom(id: String, title: String): Element {
+ val gitHubElement = Element()
+ gitHubElement.setTitle(title)
+ gitHubElement.setIconDrawable(R.drawable.about_icon_github)
+ gitHubElement.setIconTint(
+ getColorResFromThemeAttr(
+ this, com.google.android.material.R.attr.colorOnSurface, R.color.about_github_color
+ )
+ )
+ gitHubElement.setIconNightTint(R.color.about_item_dark_text_color)
+ gitHubElement.setValue(id)
+
+ val intent = Intent()
+ intent.setAction(Intent.ACTION_VIEW)
+ intent.addCategory(Intent.CATEGORY_BROWSABLE)
+ intent.setData(Uri.parse(String.format("https://github.com/%s", id)))
+
+ gitHubElement.setIntent(intent)
+
+ return gitHubElement
+ }
+
+ fun showEULA(): Element {
+ val license = Element()
+ license.setTitle(getString(R.string.eula_content))
+ return license
+ }
+
+ fun showLicense(): Element {
+ val license = Element()
+
+ license.setTitle(getString(R.string.gnu_license))
+ license.setOnClickListener {
+ val url = "https://www.gnu.org/licenses/gpl-3.0.txt"
+ val i = Intent(Intent.ACTION_VIEW)
+ i.setData(Uri.parse(url))
+ startActivity(i)
+ }
+ return license
+ }
+
+ fun showLiberaPay(): Element {
+ val element = Element()
+ element.setTitle(getString(R.string.support_liberapay))
+ element.setIconDrawable(R.drawable.liberapay_logo)
+ element.skipTint = true
+
+ element.setOnClickListener {
+ val url = "https://liberapay.com/cylonid"
+ val i = Intent(Intent.ACTION_VIEW)
+ i.setData(Uri.parse(url))
+ startActivity(i)
+ }
+ return element
+ }
+
+ fun showPayPal(): Element {
+ val license = Element()
+
+ license.setTitle(getString(R.string.paypal))
+ license.setOnClickListener {
+ val url = "https://paypal.me/cylonid"
+ val i = Intent(Intent.ACTION_VIEW)
+ i.setData(Uri.parse(url))
+ startActivity(i)
+ }
+ return license
+ }
+
+ fun showOpenSourcelibs(): Element {
+ val os = Element()
+ os.setTitle(getString(R.string.open_source_libs))
+ os.setOnClickListener {
+ startActivity(
+ LibsBuilder()
+ .withEdgeToEdge(true)
+ .withSearchEnabled(true)
+ .intent(this)
+ )
+ }
+ return os
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/cylonid/nativealpha/MainActivity.java b/app/src/main/java/com/cylonid/nativealpha/MainActivity.java
deleted file mode 100644
index 992252f1..00000000
--- a/app/src/main/java/com/cylonid/nativealpha/MainActivity.java
+++ /dev/null
@@ -1,282 +0,0 @@
-package com.cylonid.nativealpha;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.text.Html;
-import android.text.Spanned;
-import android.text.TextUtils;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.ImageButton;
-import android.widget.LinearLayout;
-import android.widget.Switch;
-
-import androidx.annotation.StringRes;
-import androidx.appcompat.app.AlertDialog;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.appcompat.widget.Toolbar;
-import androidx.core.content.res.ResourcesCompat;
-
-import com.cylonid.nativealpha.activities.NewsActivity;
-import com.cylonid.nativealpha.model.DataManager;
-import com.cylonid.nativealpha.model.WebApp;
-import com.cylonid.nativealpha.util.Const;
-import com.cylonid.nativealpha.util.EntryPointUtils;
-import com.cylonid.nativealpha.util.LocaleUtils;
-import com.cylonid.nativealpha.util.Utility;
-import com.cylonid.nativealpha.util.WebViewLauncher;
-import com.google.android.material.floatingactionbutton.FloatingActionButton;
-import com.google.android.material.snackbar.Snackbar;
-
-import java.util.ArrayList;
-
-import static android.widget.LinearLayout.HORIZONTAL;
-
-public class MainActivity extends AppCompatActivity {
- private LinearLayout mainScreen;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- setTheme(R.style.AppTheme_NoActionBar);
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- mainScreen = findViewById(R.id.mainScreen);
-
- EntryPointUtils.entryPointReached(this);
- addActiveWebAppsToUI();
-
- if (DataManager.getInstance().getWebsites().size() == 0) {
- buildAddWebsiteDialog(getString(R.string.welcome_msg));
- }
-
- FloatingActionButton fab = findViewById(R.id.fab);
- fab.setOnClickListener(view -> buildAddWebsiteDialog(getString(R.string.add_webapp)));
- personalizeToolbar();
-
- }
- @Override
- protected void onNewIntent(Intent intent) {
- super.onNewIntent(intent);
- if (intent.getBooleanExtra(Const.INTENT_BACKUP_RESTORED, false)) {
-
- mainScreen.removeAllViews();
- addActiveWebAppsToUI();
-
- buildImportSuccessDialog();
- intent.putExtra(Const.INTENT_BACKUP_RESTORED, false);
- intent.putExtra(Const.INTENT_REFRESH_NEW_THEME, false);
- }
- if (intent.getBooleanExtra(Const.INTENT_WEBAPP_CHANGED, false)) {
- mainScreen.removeAllViews();
- addActiveWebAppsToUI();
- intent.putExtra(Const.INTENT_WEBAPP_CHANGED, false);
- }
- }
- private void personalizeToolbar() {
- Toolbar toolbar = findViewById(R.id.toolbar);
- toolbar.setLogo(R.mipmap.native_alpha_white);
- @StringRes int appName = !BuildConfig.FLAVOR.equals("extended") ? R.string.app_name : R.string.app_name_plus;
- toolbar.setTitle(appName);
- setSupportActionBar(toolbar);
- }
-
- private void buildImportSuccessDialog() {
- final AlertDialog.Builder builder = new AlertDialog.Builder(this);
-
- String message = getString(R.string.import_success_dialog_txt2) + "\n\n" + getString(R.string.import_success_dialog_txt3);
-
- builder.setMessage(message);
- builder.setCancelable(false);
- builder.setTitle(getString(R.string.import_success, DataManager.getInstance().getActiveWebsitesCount()));
- builder.setPositiveButton(getString(android.R.string.yes), (dialog, id) -> {
-
- ArrayList webapps = DataManager.getInstance().getActiveWebsites();
-
- for (int i = webapps.size() - 1; i >= 0; i--) {
- WebApp webapp = webapps.get(i);
- boolean last_webapp = i == webapps.size() - 1;
- Spanned msg = Html.fromHtml(getString(R.string.restore_shortcut, webapp.getTitle()), Html.FROM_HTML_MODE_COMPACT);
- final AlertDialog addition_dialog = new AlertDialog.Builder(this)
- .setMessage(msg)
- .setPositiveButton(android.R.string.yes, (dialog1, which) -> {
- ShortcutDialogFragment frag = ShortcutDialogFragment.newInstance(webapp);
- frag.show(getSupportFragmentManager(), "SCFetcher-" + webapp.getID());
- })
- .setNegativeButton(android.R.string.no, (dialog1, which) -> {
- })
- .create();
-
- addition_dialog.show();
-
- }
-
- });
- builder.setNegativeButton(getString(android.R.string.no), (dialog, id) -> { });
- builder.create().show();
- }
-
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- // Inflate the menu; this adds items to the action bar if it is present.
- getMenuInflater().inflate(R.menu.menu_main, menu);
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- // Handle action bar item clicks here. The action bar will
- // automatically handle clicks on the Home/Up button, so long
- // as you specify a parent activity in AndroidManifest.xml.
- int id = item.getItemId();
-
- if (id == R.id.action_settings) {
- Intent intent = new Intent(this, SettingsActivity.class);
- startActivity(intent);
- return true;
- }
- if (id == R.id.action_about) {
- Intent intent = new Intent(this, AboutActivity.class);
- startActivity(intent);
- return true;
-
- }
-
- return super.onOptionsItemSelected(item);
- }
-
- @Override
- public void onBackPressed() {
- moveTaskToBack(true);
- }
-
- private ImageButton generateImageButton(String name, int resourceID, int webappID, LinearLayout ll_row) {
- int row_height = (int) getResources().getDimension(R.dimen.line_height);
- int transparent_color = ResourcesCompat.getColor(getResources(), R.color.transparent, null);
-
- ImageButton btn = new ImageButton(this);
- btn.setTag(name + webappID);
- btn.setBackgroundColor(transparent_color);
- btn.setImageResource(resourceID);
- LinearLayout.LayoutParams layout_action_buttons = new LinearLayout.LayoutParams(0, row_height, 1);
- btn.setLayoutParams(layout_action_buttons);
- ll_row.addView(btn);
-
- return btn;
- }
- private void addRow(final WebApp webapp) {
- int row_height = (int) getResources().getDimension(R.dimen.line_height);
- int transparent_color = ResourcesCompat.getColor(getResources(), R.color.transparent, null);
-
- LinearLayout ll_row = new LinearLayout(this);
- ll_row.setOrientation(HORIZONTAL);
- LinearLayout.LayoutParams layout_row = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, row_height);
- ll_row.setLayoutParams(layout_row);
-
- Button btn_title = new Button(this);
- btn_title.setBackgroundColor(transparent_color);
- btn_title.setText(webapp.getTitle());
- btn_title.setMaxLines(1);
- btn_title.setEllipsize(TextUtils.TruncateAt.END);
- LinearLayout.LayoutParams layout_title = new LinearLayout.LayoutParams(0, row_height, 4);
- btn_title.setLayoutParams(layout_title);
- ll_row.addView(btn_title);
-
- ImageButton btn_open_webview = generateImageButton("btnOpenWebview", R.drawable.ic_baseline_open_in_browser_24, webapp.getID(), ll_row);
- btn_open_webview.setOnClickListener(v -> openWebView(webapp));
-
- ImageButton btn_settings = generateImageButton("btnSettings", R.drawable.ic_settings_black_24dp, webapp.getID(), ll_row);
- btn_settings.setOnClickListener(v -> {
- Intent intent = new Intent(MainActivity.this, WebAppSettingsActivity.class);
- intent.putExtra(Const.INTENT_WEBAPPID, webapp.getID());
- intent.setAction(Intent.ACTION_VIEW);
- startActivity(intent);
-
- });
-
- ImageButton btn_delete = generateImageButton("btnDelete", R.drawable.ic_delete_black_24dp, webapp.getID(), ll_row);
- btn_delete.setOnClickListener(v -> buildDeleteItemDialog(webapp.getID()));
-
- mainScreen.addView(ll_row);
-
- }
-
-
- private void buildAddWebsiteDialog(String title) {
- final View inflated_view = getLayoutInflater().inflate(R.layout.add_website_dialogue, null);
- final EditText url = inflated_view.findViewById(R.id.websiteUrl);
- final Switch create_shortcut = inflated_view.findViewById(R.id.switchCreateShortcut);
-
- final AlertDialog dialog = new AlertDialog.Builder(MainActivity.this)
- .setView(inflated_view)
- .setTitle(title)
- .setPositiveButton(android.R.string.ok, null) //Set to null. We override the onclick
- .setNegativeButton(android.R.string.cancel, null)
- .create();
-
- dialog.setOnShowListener(dialogInterface -> {
-
- Button positive = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
- url.requestFocus();
- positive.setOnClickListener(view -> {
- String str_url = url.getText().toString().trim();
- if(str_url.equals("")) {
- Utility.showInfoSnackbar(this, getString(R.string.no_url_entered), Snackbar.LENGTH_SHORT);
- } else {
- if (!(str_url.startsWith("https://")) && !(str_url.startsWith("http://"))) {
- str_url = "https://" + str_url;
- }
- WebApp new_site = new WebApp(str_url, DataManager.getInstance().getIncrementedID());
- new_site.applySettingsForNewWebApp();
- DataManager.getInstance().addWebsite(new_site);
-
- addRow(new_site);
- dialog.dismiss();
- if (create_shortcut.isChecked()) {
- ShortcutDialogFragment frag = ShortcutDialogFragment.newInstance(new_site);
- frag.show(getSupportFragmentManager(), "SCFetcher-" + new_site.getID());
- }
- }
- });
- });
- dialog.show();
- }
-
- private void buildDeleteItemDialog(final int ID) {
-
- AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
- builder.setMessage(getString(R.string.delete_question));
- builder.setPositiveButton(getString(android.R.string.yes), (dialog, id) -> {
- WebApp webapp = DataManager.getInstance().getWebApp(ID);
- if (webapp != null) {
- webapp.markInactive();
- DataManager.getInstance().saveWebAppData();
- }
- mainScreen.removeAllViews();
- addActiveWebAppsToUI();
-
-
- });
- builder.setNegativeButton(getString(android.R.string.no), (dialog, id) -> dialog.cancel());
- AlertDialog dialog = builder.create();
- dialog.show();
- }
-
- public void addActiveWebAppsToUI() {
- for (WebApp d : DataManager.getInstance().getWebsites()) {
- if (d.isActiveEntry())
- addRow(d);
- }
- }
-
- private void openWebView(WebApp webapp) {
- WebViewLauncher.startWebView(webapp, MainActivity.this);
- }
-
-
-}
-
-
diff --git a/app/src/main/java/com/cylonid/nativealpha/MainActivity.kt b/app/src/main/java/com/cylonid/nativealpha/MainActivity.kt
new file mode 100644
index 00000000..1292629d
--- /dev/null
+++ b/app/src/main/java/com/cylonid/nativealpha/MainActivity.kt
@@ -0,0 +1,190 @@
+package com.cylonid.nativealpha
+
+import android.content.DialogInterface
+import android.content.Intent
+import android.os.Bundle
+import android.text.Editable
+import android.text.Html
+import android.text.TextWatcher
+import android.view.Menu
+import android.view.MenuItem
+import androidx.annotation.StringRes
+import androidx.annotation.VisibleForTesting
+import androidx.appcompat.app.AlertDialog
+import androidx.appcompat.app.AppCompatActivity
+import androidx.appcompat.widget.Toolbar
+import com.cylonid.nativealpha.databinding.AddWebsiteDialogueBinding
+import com.cylonid.nativealpha.fragments.webapplist.WebAppListFragment
+import com.cylonid.nativealpha.helper.AdblockLifecycleHelper
+import com.cylonid.nativealpha.model.DataManager
+import com.cylonid.nativealpha.model.WebApp
+import com.cylonid.nativealpha.util.Const
+import com.cylonid.nativealpha.util.EntryPointUtils.entryPointReached
+import com.google.android.material.floatingactionbutton.FloatingActionButton
+import io.github.edsuns.adfilter.AdFilter
+
+
+class MainActivity : AppCompatActivity() {
+ private lateinit var webAppListFragment: WebAppListFragment
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ setTheme(R.style.AppTheme)
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+ webAppListFragment =
+ supportFragmentManager.findFragmentById(R.id.fragment_container_view) as WebAppListFragment
+ entryPointReached(this)
+
+ if (DataManager.getInstance().websites.size == 0) {
+ buildAddWebsiteDialog(getString(R.string.welcome_msg))
+ }
+
+ val fab = findViewById(R.id.fab)
+ fab.setOnClickListener { buildAddWebsiteDialog(getString(R.string.add_webapp)) }
+ personalizeToolbar()
+
+ AdblockLifecycleHelper(this).trySyncOperation({ AdFilter.create(applicationContext) })
+
+ }
+
+ override fun onResume() {
+ super.onResume()
+ DataManager.getInstance().loadAppData();
+ updateWebAppList()
+ }
+
+ override fun onNewIntent(intent: Intent) {
+ super.onNewIntent(intent)
+ if (intent.getBooleanExtra(Const.INTENT_BACKUP_RESTORED, false)) {
+ updateWebAppList()
+
+ buildImportSuccessDialog()
+ intent.putExtra(Const.INTENT_BACKUP_RESTORED, false)
+ intent.putExtra(Const.INTENT_REFRESH_NEW_THEME, false)
+ }
+ if (intent.getBooleanExtra(Const.INTENT_WEBAPP_CHANGED, false)) {
+ updateWebAppList()
+ intent.putExtra(Const.INTENT_WEBAPP_CHANGED, false)
+ }
+ }
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ fun updateWebAppList() {
+ webAppListFragment.updateWebAppList()
+ }
+
+ private fun personalizeToolbar() {
+ val toolbar = findViewById(R.id.toolbar)
+ @StringRes val appName =
+ if (BuildConfig.FLAVOR == "extended") R.string.app_name_plus else R.string.app_name
+ toolbar.setTitle(appName)
+ setSupportActionBar(toolbar)
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu): Boolean {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ menuInflater.inflate(R.menu.menu_main, menu)
+ return true
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ // Handle action bar item clicks here. The action bar will
+ // automatically handle clicks on the Home/Up button, so long
+ // as you specify a parent activity in AndroidManifest.xml.
+ val id = item.itemId
+
+ if (id == R.id.action_settings) {
+ val intent = Intent(this, SettingsActivity::class.java)
+ startActivity(intent)
+ return true
+ }
+ if (id == R.id.action_about) {
+ val intent = Intent(this, AboutActivity::class.java)
+ startActivity(intent)
+ return true
+ }
+
+ return super.onOptionsItemSelected(item)
+ }
+
+ private fun buildImportSuccessDialog() {
+ val message = """
+ ${getString(R.string.import_success_dialog_txt2)}
+
+ ${getString(R.string.import_success_dialog_txt3)}
+ """.trimIndent()
+
+ AlertDialog.Builder(this).setMessage(message)
+ .setCancelable(false)
+ .setTitle(
+ getString(
+ R.string.import_success,
+ DataManager.getInstance().activeWebsitesCount
+ )
+ )
+ .setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
+ val webapps = DataManager.getInstance().activeWebsites
+ for (i in webapps.indices.reversed()) {
+ val webapp = webapps[i]
+ val msg = Html.fromHtml(
+ getString(R.string.restore_shortcut, webapp.title),
+ Html.FROM_HTML_MODE_COMPACT
+ )
+ AlertDialog.Builder(this)
+ .setMessage(msg)
+ .setPositiveButton(R.string.ok) { _: DialogInterface?, _: Int ->
+ val frag = ShortcutDialogFragment.newInstance(webapp)
+ frag.show(supportFragmentManager, "SCFetcher-" + webapp.ID)
+ }
+ .setNegativeButton(R.string.cancel) { _: DialogInterface?, _: Int -> }
+ .create()
+ .show()
+
+ }
+ }
+ .setNegativeButton(getString(R.string.cancel)) { _: DialogInterface?, _: Int -> }
+ .create().show()
+ }
+
+ private fun buildAddWebsiteDialog(title: String) {
+ val localBinding = AddWebsiteDialogueBinding.inflate(layoutInflater)
+ val dialog = AlertDialog.Builder(this@MainActivity)
+ .setView(localBinding.root)
+ .setTitle(title)
+ .setPositiveButton(R.string.ok) { _: DialogInterface, _: Int ->
+ val url = localBinding.websiteUrl.text.toString().trim()
+ val urlWithProtocol =
+ if (url.startsWith("https://") || url.startsWith("http://")) url else "https://$url"
+ val newSite = WebApp(
+ urlWithProtocol,
+ DataManager.getInstance().incrementedID,
+ DataManager.getInstance().incrementedOrder
+ )
+ newSite.applySettingsForNewWebApp()
+ DataManager.getInstance().addWebsite(newSite)
+
+ updateWebAppList()
+ if (localBinding.switchCreateShortcut.isChecked) {
+ val frag = ShortcutDialogFragment.newInstance(newSite)
+ frag.show(supportFragmentManager, "SCFetcher-" + newSite.ID)
+ }
+ }
+ .setNegativeButton(R.string.cancel, null)
+ .create()
+
+ dialog.show()
+ val okButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE)
+ okButton.isEnabled = false
+ localBinding.websiteUrl.addTextChangedListener(object : TextWatcher {
+ override fun afterTextChanged(s: Editable?) {
+ okButton.isEnabled = !s.isNullOrBlank()
+ }
+
+ override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
+ override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
+ })
+
+ }
+}
+
+
diff --git a/app/src/main/java/com/cylonid/nativealpha/SettingsActivity.java b/app/src/main/java/com/cylonid/nativealpha/SettingsActivity.java
deleted file mode 100644
index 6ebcaa1d..00000000
--- a/app/src/main/java/com/cylonid/nativealpha/SettingsActivity.java
+++ /dev/null
@@ -1,117 +0,0 @@
-package com.cylonid.nativealpha;
-
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.widget.Button;
-
-import androidx.annotation.Nullable;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.databinding.DataBindingUtil;
-
-import com.cylonid.nativealpha.databinding.GlobalSettingsBinding;
-import com.cylonid.nativealpha.model.DataManager;
-import com.cylonid.nativealpha.model.GlobalSettings;
-import com.cylonid.nativealpha.util.Const;
-import com.cylonid.nativealpha.util.Utility;
-import com.google.android.material.snackbar.Snackbar;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-
-import static com.cylonid.nativealpha.util.Const.CODE_OPEN_FILE;
-import static com.cylonid.nativealpha.util.Const.CODE_WRITE_FILE;
-
-
-public class SettingsActivity extends AppCompatActivity {
-
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
- if(requestCode == CODE_WRITE_FILE && resultCode == RESULT_OK) {
- Uri uri = data.getData();
-
- DataManager.getInstance().saveGlobalSettings(); //Needed to write legacy settings to new XML
-
- if (!DataManager.getInstance().saveSharedPreferencesToFile(uri)) {
- Utility.showInfoSnackbar(this, getString(R.string.export_failed), Snackbar.LENGTH_LONG);
- } else {
- Utility.showInfoSnackbar(this, getString(R.string.export_success), Snackbar.LENGTH_SHORT);
- }
- }
- if (requestCode == CODE_OPEN_FILE && resultCode == RESULT_OK) {
- Uri uri = data.getData();
-
- if (!DataManager.getInstance().loadSharedPreferencesFromFile(uri)) {
- Utility.showInfoSnackbar(this, getString(R.string.import_failed), Snackbar.LENGTH_LONG);
- } else {
- Intent i = new Intent(SettingsActivity.this, MainActivity.class);
- DataManager.getInstance().loadAppData();
- i.putExtra(Const.INTENT_BACKUP_RESTORED, true);
- finish();
- startActivity(i);
- }
- }
- }
-
-
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- GlobalSettingsBinding binding = DataBindingUtil.setContentView(this, R.layout.global_settings);
- GlobalSettings settings = DataManager.getInstance().getSettings();
- final GlobalSettings modified_settings = new GlobalSettings(settings);
- binding.setSettings(modified_settings);
-
- Button btnSave = findViewById(R.id.btnSave);
- Button btnCancel = findViewById(R.id.btnCancel);
- Button btnExport = findViewById(R.id.btnExportSettings);
- Button btnImport = findViewById(R.id.btnImportSettings);
- Button btnGlobalWebApp = findViewById(R.id.btnGlobalWebApp);
-
- btnGlobalWebApp.setOnClickListener(v -> {
- Intent intent = new Intent(SettingsActivity.this, WebAppSettingsActivity.class);
- intent.putExtra(Const.INTENT_WEBAPPID, settings.getGlobalWebApp().getID());
- intent.setAction(Intent.ACTION_VIEW);
- startActivity(intent);
- });
-
-
- btnExport.setOnClickListener(v -> {
-
- Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT).addCategory(Intent.CATEGORY_OPENABLE).setType("*/*");
- SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault());
- String currentDateTime = sdf.format(new Date());
- intent.putExtra(Intent.EXTRA_TITLE, "NativeAlpha_" + currentDateTime);
- try {
- startActivityForResult(intent, CODE_WRITE_FILE);
- } catch (android.content.ActivityNotFoundException e) {
- Utility.showInfoSnackbar(SettingsActivity.this, getString(R.string.no_filemanager), Snackbar.LENGTH_LONG);
- e.printStackTrace();
- }
-
- });
-
- btnImport.setOnClickListener(v -> {
- Intent intent = new Intent().setType("*/*").setAction(Intent.ACTION_GET_CONTENT);
- try {
- startActivityForResult(Intent.createChooser(intent, "Select a file"), CODE_OPEN_FILE);
- } catch (android.content.ActivityNotFoundException e) {
- Utility.showInfoSnackbar(SettingsActivity.this, getString(R.string.no_filemanager), Snackbar.LENGTH_LONG);
- e.printStackTrace();
- }
-
- });
-
- btnSave.setOnClickListener(v -> {
- DataManager.getInstance().setSettings(modified_settings);
- onBackPressed();
- });
-
- btnCancel.setOnClickListener(v -> {
- onBackPressed();
- });
- }
-}
diff --git a/app/src/main/java/com/cylonid/nativealpha/SettingsActivity.kt b/app/src/main/java/com/cylonid/nativealpha/SettingsActivity.kt
new file mode 100644
index 00000000..82534ebc
--- /dev/null
+++ b/app/src/main/java/com/cylonid/nativealpha/SettingsActivity.kt
@@ -0,0 +1,153 @@
+package com.cylonid.nativealpha
+
+import android.content.ActivityNotFoundException
+import android.content.Intent
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.webkit.CookieManager
+import android.webkit.WebStorage
+import androidx.appcompat.app.AppCompatActivity
+import androidx.databinding.DataBindingUtil
+import com.cylonid.nativealpha.activities.AdblockConfigActivity
+import com.cylonid.nativealpha.activities.ToolbarBaseActivity
+import com.cylonid.nativealpha.databinding.GlobalSettingsBinding
+import com.cylonid.nativealpha.model.DataManager
+import com.cylonid.nativealpha.model.GlobalSettings
+import com.cylonid.nativealpha.util.Const
+import com.cylonid.nativealpha.util.NotificationUtils
+import com.cylonid.nativealpha.util.Utility
+import com.google.android.material.snackbar.Snackbar
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
+
+class SettingsActivity : ToolbarBaseActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setToolbarTitle(getString(R.string.global_settings))
+
+ val settings = DataManager.getInstance().settings
+ val modified_settings = settings.copy()
+ binding.settings = modified_settings
+ binding.btnAdblockConfig.setOnClickListener { v: View? ->
+ val intent = Intent(
+ this@SettingsActivity,
+ AdblockConfigActivity::class.java
+ )
+ intent.setAction(Intent.ACTION_VIEW)
+ startActivity(intent)
+ }
+
+ binding.btnGlobalWebApp.setOnClickListener { v: View? ->
+ val intent = Intent(
+ this@SettingsActivity,
+ WebAppSettingsActivity::class.java
+ )
+ intent.putExtra(
+ Const.INTENT_WEBAPPID,
+ settings.globalWebApp.ID
+ )
+ intent.setAction(Intent.ACTION_VIEW)
+ startActivity(intent)
+ }
+
+
+ binding.btnExportSettings.setOnClickListener { v: View? ->
+ val intent =
+ Intent(Intent.ACTION_CREATE_DOCUMENT).addCategory(Intent.CATEGORY_OPENABLE)
+ .setType("*/*")
+ val sdf =
+ SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault())
+ val currentDateTime = sdf.format(Date())
+ intent.putExtra(Intent.EXTRA_TITLE, "NativeAlpha_$currentDateTime")
+ try {
+ startActivityForResult(intent, Const.CODE_WRITE_FILE)
+ } catch (e: ActivityNotFoundException) {
+ NotificationUtils.showInfoSnackbar(
+ this@SettingsActivity,
+ getString(R.string.no_filemanager),
+ Snackbar.LENGTH_LONG
+ )
+ e.printStackTrace()
+ }
+ }
+
+ binding.btnImportSettings.setOnClickListener { v: View? ->
+ val intent = Intent().setType("*/*").setAction(Intent.ACTION_GET_CONTENT)
+ try {
+ startActivityForResult(
+ Intent.createChooser(intent, "Select a file"),
+ Const.CODE_OPEN_FILE
+ )
+ } catch (e: ActivityNotFoundException) {
+ NotificationUtils.showInfoSnackbar(
+ this@SettingsActivity,
+ getString(R.string.no_filemanager),
+ Snackbar.LENGTH_LONG
+ )
+ e.printStackTrace()
+ }
+ }
+
+ binding.btnSave.setOnClickListener {
+ DataManager.getInstance().settings = modified_settings
+ finish()
+ }
+
+ binding.btnCancel.setOnClickListener {
+ finish()
+ }
+ }
+
+ override fun inflateBinding(layoutInflater: LayoutInflater): GlobalSettingsBinding {
+ return GlobalSettingsBinding.inflate(layoutInflater)
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+ if (requestCode == Const.CODE_WRITE_FILE && resultCode == RESULT_OK) {
+ val uri = data?.data
+
+ DataManager.getInstance()
+ .saveGlobalSettings() //Needed to write legacy settings to new XML
+
+ if (!DataManager.getInstance().saveSharedPreferencesToFile(uri)) {
+ NotificationUtils.showInfoSnackbar(
+ this,
+ getString(R.string.export_failed),
+ Snackbar.LENGTH_LONG
+ )
+ } else {
+ NotificationUtils.showInfoSnackbar(
+ this,
+ getString(R.string.export_success),
+ Snackbar.LENGTH_SHORT
+ )
+ }
+ }
+ if (requestCode == Const.CODE_OPEN_FILE && resultCode == RESULT_OK) {
+ val uri = data?.data
+
+ if (!DataManager.getInstance().loadSharedPreferencesFromFile(uri)) {
+ NotificationUtils.showInfoSnackbar(
+ this,
+ getString(R.string.import_failed),
+ Snackbar.LENGTH_LONG
+ )
+ } else {
+ val i = Intent(this@SettingsActivity, MainActivity::class.java)
+
+ WebStorage.getInstance().deleteAllData()
+ CookieManager.getInstance().removeAllCookies(null)
+
+ DataManager.getInstance().loadAppData()
+ i.putExtra(Const.INTENT_BACKUP_RESTORED, true)
+ finish()
+ startActivity(i)
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/cylonid/nativealpha/ShortcutDialogFragment.java b/app/src/main/java/com/cylonid/nativealpha/ShortcutDialogFragment.java
index cacd9a96..c0a9eac9 100644
--- a/app/src/main/java/com/cylonid/nativealpha/ShortcutDialogFragment.java
+++ b/app/src/main/java/com/cylonid/nativealpha/ShortcutDialogFragment.java
@@ -19,7 +19,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
-import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.pm.ShortcutInfoCompat;
import androidx.core.content.pm.ShortcutManagerCompat;
import androidx.core.graphics.drawable.IconCompat;
@@ -29,7 +28,8 @@
import com.cylonid.nativealpha.model.WebApp;
import com.cylonid.nativealpha.util.App;
import com.cylonid.nativealpha.util.Const;
-import com.cylonid.nativealpha.util.Utility;
+import com.cylonid.nativealpha.util.NotificationUtils;
+import com.cylonid.nativealpha.util.ShortcutIconUtils;
import com.cylonid.nativealpha.util.WebViewLauncher;
import com.google.android.material.snackbar.Snackbar;
import com.mikhaellopez.circularprogressbar.CircularProgressBar;
@@ -54,6 +54,17 @@
import static androidx.appcompat.app.AppCompatActivity.RESULT_OK;
import static com.cylonid.nativealpha.util.Const.CODE_OPEN_FILE;
+enum IconFetchResult {
+ FAVICON(0),
+ TITLE(1),
+ NEW_BASEURL(2);
+
+ public final int index;
+ IconFetchResult(int index) {
+ this.index = index;
+ }
+
+}
public class ShortcutDialogFragment extends DialogFragment {
@@ -98,7 +109,7 @@ public void onActivityResult(int requestCode, int resultCode, @Nullable Intent d
}
catch(IOException e) {
- Utility.showToast(requireActivity(), getString(R.string.icon_not_found), Toast.LENGTH_SHORT);
+ NotificationUtils.showToast(requireActivity(), getString(R.string.icon_not_found), Toast.LENGTH_SHORT);
e.printStackTrace();
}
}
@@ -116,7 +127,6 @@ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
.setPositiveButton(android.R.string.ok, (dialog1, which) -> {
addShortcutToHomeScreen(bitmap);
dismiss();
-
})
.setNegativeButton(android.R.string.cancel, (dialog1, which) -> {
dismiss();
@@ -140,7 +150,7 @@ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
try {
startActivityForResult(Intent.createChooser(intent, "Select an icon"), CODE_OPEN_FILE);
} catch (android.content.ActivityNotFoundException e) {
- Utility.showInfoSnackbar((AppCompatActivity) requireActivity(), getString(R.string.no_filemanager), Snackbar.LENGTH_LONG);
+ NotificationUtils.showInfoSnackbar(requireActivity(), getString(R.string.no_filemanager), Snackbar.LENGTH_LONG);
e.printStackTrace();
}
});
@@ -171,11 +181,9 @@ private TreeMap buildIconMap() {
String host_part = base_url.replace("http://", "").replace("https://", "").replace("www.", "");
//No suitable icon
- if (host_part.startsWith("facebook."))
- found_icons.put(325, "https://static.xx.fbcdn.net/rsrc.php/v3/y3/r/UrYT8B96uSq.png");
if (host_part.startsWith("amazon."))
- found_icons.put(300, "https://s3.amazonaws.com/prod-widgetSource/in-shop/pub/images/amzn_favicon_blk.png");
+ found_icons.put(300, "https://upload.wikimedia.org/wikipedia/commons/d/de/Amazon_icon.png");
if (host_part.startsWith("paypal."))
found_icons.put(196, "https://www.paypalobjects.com/webstatic/icon/pp196.png");
@@ -191,17 +199,11 @@ private TreeMap buildIconMap() {
if (host_part.startsWith("oebb.at"))
found_icons.put(Integer.MAX_VALUE, "https://www.oebb.at/.resources/pv-2017/themes/images/favicons/android-chrome-192x192.png");
- //Wrong path in PWA manifest
- if (host_part.startsWith("explosm.net"))
- found_icons.put(Integer.MAX_VALUE, "https://files.explosm.net/img/favicons/site/android-chrome-192x192.png");
//Path in PWA manifest is HTTP
if (host_part.startsWith("oe3.orf.at"))
found_icons.put(Integer.MAX_VALUE, "https://tubestatic.orf.at/mojo/1_3/storyserver//tube/common/images/apple-icons/oe3.png");
- //Non-existing path
- if (host_part.startsWith("darfichrein.de"))
- found_icons.put(Integer.MAX_VALUE, "https://c.darfichrein.de/assets/img/logo1.png");
return found_icons;
}
@@ -235,12 +237,12 @@ public String[] fetchWebappData() {
JSONObject json = new JSONObject(data);
try {
- result[Const.RESULT_IDX_TITLE] = json.getString("name");
+ result[IconFetchResult.TITLE.index] = json.getString("name");
String start_url = json.getString("start_url");
if (!start_url.isEmpty()) {
URL base_url = new URL(mf.absUrl("href"));
URL fl_url = new URL(base_url, start_url);
- result[Const.RESULT_IDX_NEW_BASEURL] = fl_url.toString();
+ result[IconFetchResult.NEW_BASEURL.index] = fl_url.toString();
}
} catch (JSONException e) {
e.printStackTrace();
@@ -251,7 +253,7 @@ public String[] fetchWebappData() {
for (int i = 0; i < manifest_icons.length(); i++) {
String icon_href = manifest_icons.getJSONObject(i).getString("src");
String sizes = manifest_icons.getJSONObject(i).getString("sizes");
- Integer width = Utility.getWidthFromIcon(sizes);
+ Integer width = ShortcutIconUtils.getWidthFromIcon(sizes);
URL base_url = new URL(mf.absUrl("href"));
URL full_url = new URL(base_url, icon_href);
found_icons.put(width, full_url.toString());
@@ -265,7 +267,7 @@ public String[] fetchWebappData() {
Elements html_title = doc.select("title");
if (!html_title.isEmpty())
- result[Const.RESULT_IDX_TITLE] = html_title.first().text();
+ result[IconFetchResult.TITLE.index] = html_title.first().text();
Elements icons = doc.select("link[rel=icon]");
icons.addAll(doc.select("link[rel=shortcut icon]"));
@@ -281,7 +283,7 @@ public String[] fetchWebappData() {
String icon_href = icon.absUrl("href");
String sizes = icon.attr("sizes");
if (!sizes.equals("")) {
- Integer width = Utility.getWidthFromIcon(sizes);
+ Integer width = ShortcutIconUtils.getWidthFromIcon(sizes);
found_icons.put(width, icon_href);
} else
found_icons.put(1, icon_href);
@@ -295,7 +297,7 @@ public String[] fetchWebappData() {
if (!found_icons.isEmpty()) {
Map.Entry best_fit = found_icons.lastEntry();
- result[Const.RESULT_IDX_FAVICON] = best_fit.getValue();
+ result[IconFetchResult.FAVICON.index] = best_fit.getValue();
}
@@ -306,13 +308,13 @@ private void startFaviconFetching() {
faviconFetcherThread = new Thread(() -> {
String[] webappdata = fetchWebappData();
- bitmap = loadBitmap(webappdata[Const.RESULT_IDX_FAVICON]);
+ bitmap = loadBitmap(webappdata[IconFetchResult.FAVICON.index]);
if (isAdded()) {
requireActivity().runOnUiThread(() -> {
applyNewBitmapToDialog();
- setShortcutTitle(webappdata[Const.RESULT_IDX_TITLE]);
- applyNewBaseUrl(webappdata[Const.RESULT_IDX_NEW_BASEURL]);
+ setShortcutTitle(webappdata[IconFetchResult.TITLE.index]);
+ applyNewBaseUrl(webappdata[IconFetchResult.NEW_BASEURL.index]);
});
}
@@ -360,7 +362,7 @@ private void addShortcutToHomeScreen(Bitmap bitmap) {
if(!scManager.getPinnedShortcuts().stream().anyMatch(s -> s.getId().equals(newScId))) {
ShortcutManagerCompat.requestPinShortcut(requireActivity(), pinShortcutInfo, null);
} else {
- Utility.showToast(requireActivity(), getString(R.string.shortcut_already_exists));
+ NotificationUtils.showToast(requireActivity(), getString(R.string.shortcut_already_exists));
}
}
@@ -368,14 +370,18 @@ private void addShortcutToHomeScreen(Bitmap bitmap) {
private void prepareFailedUI() {
showFailedMessage();
- uiTitle.setText(webapp.getTitle());
+ if(webapp.getTitle() != null && !webapp.getTitle().equals("")) {
+ uiTitle.setText(webapp.getTitle());
+ }
+
uiTitle.requestFocus();
uiProgressBar.setVisibility(View.GONE);
uiFavicon.setVisibility(View.VISIBLE);
}
private void showFailedMessage() {
- Utility.showToast(requireActivity(), getString(R.string.icon_fetch_failed_line1, webapp.getTitle()) + getString(R.string.icon_fetch_failed_line2) + getString(R.string.icon_fetch_failed_line3));
+ String title = webapp != null && webapp.getTitle() != null ? webapp.getTitle() : "";
+ NotificationUtils.showToast(requireActivity(), getString(R.string.icon_fetch_failed_line1, title) + getString(R.string.icon_fetch_failed_line2) + getString(R.string.icon_fetch_failed_line3));
}
private void setShortcutTitle(String shortcut_title) {
diff --git a/app/src/main/java/com/cylonid/nativealpha/WebAppSettingsActivity.java b/app/src/main/java/com/cylonid/nativealpha/WebAppSettingsActivity.java
deleted file mode 100644
index 13b997e5..00000000
--- a/app/src/main/java/com/cylonid/nativealpha/WebAppSettingsActivity.java
+++ /dev/null
@@ -1,171 +0,0 @@
-package com.cylonid.nativealpha;
-
-import android.annotation.SuppressLint;
-import android.app.ActivityManager;
-import android.app.TimePickerDialog;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.text.Html;
-import android.text.method.LinkMovementMethod;
-import android.view.View;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.databinding.DataBindingUtil;
-
-import com.cylonid.nativealpha.databinding.WebappSettingsBinding;
-import com.cylonid.nativealpha.model.DataManager;
-import com.cylonid.nativealpha.model.WebApp;
-import com.cylonid.nativealpha.util.App;
-import com.cylonid.nativealpha.util.Const;
-import com.cylonid.nativealpha.util.Utility;
-
-import java.util.Calendar;
-
-public class WebAppSettingsActivity extends AppCompatActivity {
-
- int webappID = -1;
- WebApp webapp;
- boolean isGlobalWebApp;
-
- @SuppressLint({"SetJavaScriptEnabled", "ClickableViewAccessibility"})
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- WebappSettingsBinding binding = DataBindingUtil.setContentView(this, R.layout.webapp_settings);
- TextView txt = findViewById(R.id.txthintUserAgent);
- txt.setText(Html.fromHtml(getString(R.string.hint_user_agent), Html.FROM_HTML_MODE_LEGACY));
- txt.setMovementMethod(LinkMovementMethod.getInstance());
-
- webappID = getIntent().getIntExtra(Const.INTENT_WEBAPPID, -1);
- Utility.Assert(webappID != -1, "WebApp ID could not be retrieved.");
- isGlobalWebApp = webappID == DataManager.getInstance().getSettings().getGlobalWebApp().getID();
-
- final View inflated_view = binding.getRoot();
-
- if (isGlobalWebApp) {
- webapp = DataManager.getInstance().getSettings().getGlobalWebApp();
- prepareGlobalWebAppScreen();
- }
- else
- webapp = DataManager.getInstance().getWebAppIgnoringGlobalOverride(webappID, true);
-
- if (webapp == null) {
- finish();
- }
- else {
- final WebApp modified_webapp = new WebApp(webapp);
- binding.setWebapp(modified_webapp);
- binding.setActivity(WebAppSettingsActivity.this);
-
- final Button btnCreateShortcut = inflated_view.findViewById(R.id.btnRecreateShortcut);
-
- btnCreateShortcut.setOnClickListener(view -> {
- ShortcutDialogFragment frag = ShortcutDialogFragment.newInstance(webapp);
- frag.show(getSupportFragmentManager(), "SCFetcher-" + webapp.getID());
-
- });
- Button btnSave = findViewById(R.id.btnSave);
- Button btnCancel = findViewById(R.id.btnCancel);
-
- btnSave.setOnClickListener(v -> {
- ActivityManager activityManager =
- (ActivityManager) App.getAppContext().getSystemService(Context.ACTIVITY_SERVICE);
-
- //Global web app => close all webview activities, save to global settings
- if (isGlobalWebApp) {
- for (ActivityManager.AppTask task : activityManager.getAppTasks()) {
- int id = task.getTaskInfo().baseIntent.getIntExtra(Const.INTENT_WEBAPPID, -1);
- if (id != -1)
- task.finishAndRemoveTask();
- }
- for (ActivityManager.RunningAppProcessInfo processInfo : activityManager.getRunningAppProcesses()) {
- if (processInfo.processName.contains("web_sandbox")) {
- android.os.Process.killProcess(processInfo.pid);
- }
- }
- DataManager.getInstance().getSettings().setGlobalWebApp(modified_webapp);
- DataManager.getInstance().saveGlobalSettings();
- }
- //Normal web app => only close that specific web app, save to webapp arraylist
- else {
- for (ActivityManager.AppTask task : activityManager.getAppTasks()) {
- int id = task.getTaskInfo().baseIntent.getIntExtra(Const.INTENT_WEBAPPID, -1);
- if (id == webappID)
- task.finishAndRemoveTask();
- }
- for (ActivityManager.RunningAppProcessInfo processInfo : activityManager.getRunningAppProcesses()) {
- if (processInfo.processName.contains("web_sandbox_" + modified_webapp.getContainerId())) {
- android.os.Process.killProcess(processInfo.pid);
- }
- }
- DataManager.getInstance().replaceWebApp(modified_webapp);
- }
-
- Intent i = new Intent(WebAppSettingsActivity.this, MainActivity.class);
- i.putExtra(Const.INTENT_WEBAPP_CHANGED, true);
- finish();
- startActivity(i);
- });
-
- btnCancel.setOnClickListener(v -> finish());
- EditText txtBeginDarkMode = inflated_view.findViewById(R.id.textDarkModeBegin);
- EditText txtEndDarkMode = inflated_view.findViewById(R.id.textDarkModeEnd);
-
- txtBeginDarkMode.setOnClickListener(view -> showTimePicker(txtBeginDarkMode));
- txtEndDarkMode.setOnClickListener(view -> showTimePicker(txtEndDarkMode));
-
- webapp.onSwitchExpertSettingsChanged(inflated_view.findViewById(R.id.switchExpertSettings), webapp.isShowExpertSettings());
- webapp.onSwitchOverrideGlobalSettingsChanged(findViewById(R.id.switchOverrideGlobal), webapp.isOverrideGlobalSettings());
- setPlusSettings(inflated_view);
- }
- }
-
- private void setPlusSettings(View v) {
- LinearLayout secDarkMode = v.findViewById(R.id.sectionDarkmode);
- LinearLayout secSandbox= v.findViewById(R.id.sectionSandbox);
- LinearLayout secKiosk = v.findViewById(R.id.sectionKioskMode);
- LinearLayout secAccessRestriction = v.findViewById(R.id.sectionAccessRestriction);
- if (!BuildConfig.FLAVOR.equals("extended")) {
- secDarkMode.setVisibility(View.GONE);
- secSandbox.setVisibility(View.GONE);
- secKiosk.setVisibility(View.GONE);
- secAccessRestriction.setVisibility(View.GONE);
- }
- }
-
-
- private void showTimePicker(EditText txtField) {
- Calendar c = Utility.convertStringToCalendar(txtField.getText().toString());
- TimePickerDialog timePickerDialog = new TimePickerDialog(WebAppSettingsActivity.this, R.style.CustomDatePickerDialog, (timePicker, selectedHour, selectedMinute) -> {
- Calendar datetime = Calendar.getInstance();
- datetime.set(Calendar.HOUR_OF_DAY, selectedHour);
- datetime.set(Calendar.MINUTE, selectedMinute);
- txtField.setText(Utility.getHourMinFormat().format(datetime.getTime()));
- }, c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE), true);
- timePickerDialog.show();
-
- }
-
- private void prepareGlobalWebAppScreen() {
- findViewById(R.id.btnRecreateShortcut).setVisibility(View.GONE);
- findViewById(R.id.labelWebAppName).setVisibility(View.GONE);
- findViewById(R.id.txtWebAppName).setVisibility(View.GONE);
- findViewById(R.id.switchOverrideGlobal).setVisibility(View.GONE);
- findViewById(R.id.sectionSSL).setVisibility(View.GONE);
- findViewById(R.id.sectionSandbox).setVisibility(View.GONE);
- findViewById(R.id.labelTitle).setVisibility(View.GONE);
- findViewById(R.id.labelEditableBaseUrl).setVisibility(View.GONE);
- findViewById(R.id.textBaseUrl).setVisibility(View.GONE);
-
- TextView page_title = findViewById(R.id.labelPageTitle);
- page_title.setText(getString(R.string.global_web_app_settings));
-
- }
-}
-
-
diff --git a/app/src/main/java/com/cylonid/nativealpha/WebAppSettingsActivity.kt b/app/src/main/java/com/cylonid/nativealpha/WebAppSettingsActivity.kt
new file mode 100644
index 00000000..fd1a15ea
--- /dev/null
+++ b/app/src/main/java/com/cylonid/nativealpha/WebAppSettingsActivity.kt
@@ -0,0 +1,184 @@
+package com.cylonid.nativealpha
+
+import android.annotation.SuppressLint
+import android.app.ActivityManager
+import android.app.TimePickerDialog
+import android.content.Intent
+import android.os.Bundle
+import android.os.Process
+import android.text.Html
+import android.text.method.LinkMovementMethod
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.EditText
+import android.widget.TimePicker
+import com.cylonid.nativealpha.activities.ToolbarBaseActivity
+import com.cylonid.nativealpha.databinding.WebappSettingsBinding
+import com.cylonid.nativealpha.model.DataManager
+import com.cylonid.nativealpha.model.WebApp
+import com.cylonid.nativealpha.util.Const
+import com.cylonid.nativealpha.util.DateUtils.convertStringToCalendar
+import com.cylonid.nativealpha.util.DateUtils.getHourMinFormat
+import com.cylonid.nativealpha.util.ProcessUtils.closeAllWebAppsAndProcesses
+import com.cylonid.nativealpha.util.Utility
+import java.util.Calendar
+
+class WebAppSettingsActivity : ToolbarBaseActivity() {
+ var webappID: Int = -1
+ var webapp: WebApp? = null
+ private var isGlobalWebApp: Boolean = false
+
+ @SuppressLint("SetJavaScriptEnabled", "ClickableViewAccessibility")
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setToolbarTitle(getString(R.string.web_app_settings))
+
+ webappID = intent.getIntExtra(Const.INTENT_WEBAPPID, -1)
+ Utility.Assert(webappID != -1, "WebApp ID could not be retrieved.")
+ isGlobalWebApp = webappID == DataManager.getInstance().settings.globalWebApp.ID
+
+ if (isGlobalWebApp) {
+ webapp = DataManager.getInstance().settings.globalWebApp
+ prepareGlobalWebAppScreen()
+ } else webapp = DataManager.getInstance().getWebAppIgnoringGlobalOverride(webappID, true)
+
+ if (webapp == null) {
+ finish()
+ return
+ }
+ val modifiedWebapp = WebApp(webapp!!)
+ binding.webapp = modifiedWebapp
+ binding.activity = this@WebAppSettingsActivity
+
+ setupSaveAndCancel(modifiedWebapp)
+ setupDarkModeElements()
+ setupPlusSettings()
+ setupDesktopUserAgentHint()
+ setupShortcutButton()
+ setupSwitchListeners(webapp!!)
+
+ }
+
+ override fun inflateBinding(layoutInflater: LayoutInflater): WebappSettingsBinding {
+ return WebappSettingsBinding.inflate(layoutInflater)
+ }
+
+ private fun setupSwitchListeners(webapp: WebApp) {
+ webapp.onSwitchExpertSettingsChanged(
+ binding.switchExpertSettings,
+ webapp.isShowExpertSettings
+ )
+ webapp.onSwitchOverrideGlobalSettingsChanged(
+ binding.switchOverrideGlobal,
+ webapp.isOverrideGlobalSettings
+ )
+ }
+
+ private fun setupSaveAndCancel(modifiedWebapp: WebApp) {
+ binding.btnSave.setOnClickListener {
+ val activityManager =
+ getSystemService(ACTIVITY_SERVICE) as ActivityManager
+ // Global web app => close all webview activities, save to global settings
+ if (isGlobalWebApp) {
+ closeAllWebAppsAndProcesses(
+ activityManager
+ )
+ DataManager.getInstance().settings.globalWebApp = modifiedWebapp
+ DataManager.getInstance().saveGlobalSettings()
+ } else {
+ for (task in activityManager.appTasks) {
+ val id = task.taskInfo.baseIntent.getIntExtra(
+ Const.INTENT_WEBAPPID,
+ -1
+ )
+ if (id == webappID) task.finishAndRemoveTask()
+ }
+ for (processInfo in activityManager.runningAppProcesses) {
+ if (processInfo.processName.contains("web_sandbox_" + modifiedWebapp.containerId)) {
+ Process.killProcess(processInfo.pid)
+ }
+ }
+ DataManager.getInstance().replaceWebApp(modifiedWebapp)
+ }
+
+ val i = Intent(this@WebAppSettingsActivity, MainActivity::class.java)
+ i.putExtra(Const.INTENT_WEBAPP_CHANGED, true)
+ finish()
+ startActivity(i)
+ }
+
+ binding.btnCancel.setOnClickListener { finish() }
+ }
+
+ private fun setupDarkModeElements() {
+ val txtBeginDarkMode = binding.textDarkModeBegin
+ val txtEndDarkMode = binding.textDarkModeEnd
+
+ txtBeginDarkMode.setOnClickListener {
+ showTimePicker(
+ txtBeginDarkMode
+ )
+ }
+ txtEndDarkMode.setOnClickListener {
+ showTimePicker(
+ txtEndDarkMode
+ )
+ }
+
+ }
+
+ private fun setupDesktopUserAgentHint() {
+ val txt = binding.txthintUserAgent
+ txt.text = Html.fromHtml(getString(R.string.hint_user_agent), Html.FROM_HTML_MODE_LEGACY)
+ txt.movementMethod = LinkMovementMethod.getInstance()
+ }
+
+ private fun setupShortcutButton() {
+ binding.btnRecreateShortcut.setOnClickListener {
+ val frag = ShortcutDialogFragment.newInstance(webapp)
+ frag.show(supportFragmentManager, "SCFetcher-" + webapp!!.ID)
+ }
+ }
+
+ private fun setupPlusSettings() {
+ if (!BuildConfig.FLAVOR.contains("extended")) {
+ binding.sectionDarkmode.visibility = View.GONE
+ binding.sectionSandbox.visibility = View.GONE
+ binding.sectionKioskMode.visibility = View.GONE
+ binding.sectionAccessRestriction.visibility = View.GONE
+ }
+ }
+
+ private fun showTimePicker(txtField: EditText) {
+ val c = convertStringToCalendar(txtField.text.toString())
+ val timePickerDialog = TimePickerDialog(
+ this@WebAppSettingsActivity, R.style.AppTheme,
+ { timePicker: TimePicker?, selectedHour: Int, selectedMinute: Int ->
+ val datetime = Calendar.getInstance()
+ datetime[Calendar.HOUR_OF_DAY] = selectedHour
+ datetime[Calendar.MINUTE] = selectedMinute
+ txtField.setText(getHourMinFormat().format(datetime.time))
+ }, c!![Calendar.HOUR_OF_DAY], c[Calendar.MINUTE], true
+ )
+ timePickerDialog.show()
+ }
+
+ private fun prepareGlobalWebAppScreen() {
+ binding.btnRecreateShortcut.visibility = View.GONE
+ binding.labelWebAppName.visibility = View.GONE
+ binding.txtWebAppName.visibility = View.GONE
+ binding.switchOverrideGlobal.visibility = View.GONE
+ binding.sectionSSL.visibility = View.GONE
+ binding.sectionSandbox.visibility = View.GONE
+ binding.labelTitle.visibility = View.GONE
+ binding.labelEditableBaseUrl.visibility = View.GONE
+ binding.textBaseUrl.visibility = View.GONE
+
+ binding.globalSettingsInfoText.visibility = View.VISIBLE
+
+ setToolbarTitle(getString(R.string.global_web_app_settings))
+ }
+}
+
+
diff --git a/app/src/main/java/com/cylonid/nativealpha/WebViewActivity.java b/app/src/main/java/com/cylonid/nativealpha/WebViewActivity.java
index 759bba52..ad2aa915 100644
--- a/app/src/main/java/com/cylonid/nativealpha/WebViewActivity.java
+++ b/app/src/main/java/com/cylonid/nativealpha/WebViewActivity.java
@@ -6,13 +6,11 @@
import android.app.DownloadManager;
import android.content.ClipData;
import android.content.ClipboardManager;
-import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.net.http.SslError;
import android.os.Build;
@@ -23,8 +21,7 @@
import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
import android.util.Log;
-import android.view.Gravity;
-import android.view.Menu;
+import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
@@ -33,55 +30,65 @@
import android.view.WindowManager;
import android.webkit.CookieManager;
import android.webkit.GeolocationPermissions;
+import android.webkit.HttpAuthHandler;
import android.webkit.PermissionRequest;
import android.webkit.SslErrorHandler;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
-import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.FrameLayout;
import android.widget.PopupMenu;
import android.widget.ProgressBar;
-import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.app.ActivityCompat;
import androidx.core.app.ShareCompat;
import androidx.core.content.ContextCompat;
-import androidx.core.view.MenuCompat;
import androidx.webkit.WebSettingsCompat;
import androidx.webkit.WebViewFeature;
+import com.cylonid.nativealpha.databinding.DialogHttpAuthBinding;
+import com.cylonid.nativealpha.helper.AdblockLifecycleHelper;
+import com.cylonid.nativealpha.helper.AdblockProviderApiHelper;
import com.cylonid.nativealpha.helper.BiometricPromptHelper;
import com.cylonid.nativealpha.helper.IconPopupMenuHelper;
+import com.cylonid.nativealpha.model.AdblockConfig;
import com.cylonid.nativealpha.model.DataManager;
import com.cylonid.nativealpha.model.SandboxManager;
import com.cylonid.nativealpha.model.WebApp;
import com.cylonid.nativealpha.util.Const;
+import com.cylonid.nativealpha.util.DateUtils;
import com.cylonid.nativealpha.util.EntryPointUtils;
import com.cylonid.nativealpha.util.LocaleUtils;
+import com.cylonid.nativealpha.util.NotificationUtils;
import com.cylonid.nativealpha.util.Utility;
import com.cylonid.nativealpha.util.WebViewLauncher;
+import com.google.android.material.color.MaterialColors;
import com.google.android.material.snackbar.Snackbar;
-import com.jakewharton.processphoenix.ProcessPhoenix;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
+import java.util.Objects;
import java.util.stream.Stream;
+import io.github.edsuns.adfilter.AdFilter;
+import io.github.edsuns.adfilter.Filter;
import pub.devrel.easypermissions.EasyPermissions;
+
import static com.cylonid.nativealpha.util.Const.CODE_OPEN_FILE;
public class WebViewActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks {
@@ -107,12 +114,21 @@ public class WebViewActivity extends AppCompatActivity implements EasyPermission
private boolean fallbackToDefaultLongClickBehaviour = false;
private PopupMenu mPopupMenu = null;
+ private AdFilter adFilter;
+
+ private AdblockProviderApiHelper adblockProviderApiHelper;
+ private AdblockLifecycleHelper adblockLifecycleHelper;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+
+ adblockLifecycleHelper = new AdblockLifecycleHelper(this);
+ adblockLifecycleHelper.trySyncOperation(() -> adFilter = AdFilter.Companion.get(getApplicationContext()));
+
+ adblockProviderApiHelper = new AdblockProviderApiHelper(adFilter);
webappID = getIntent().getIntExtra(Const.INTENT_WEBAPPID, -1);
EntryPointUtils.entryPointReached(this);
-
webapp = DataManager.getInstance().getWebApp(webappID);
if (webapp == null) {
// Toast is shown in getWebApp method
@@ -128,29 +144,28 @@ protected void onCreate(Bundle savedInstanceState) {
@SuppressLint("ClickableViewAccessibility")
private void setupWebView() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- String processName = Application.getProcessName();
- String packageName = this.getPackageName();
+ String processName = Application.getProcessName();
+ String packageName = this.getPackageName();
+
+ boolean hasSandboxing = SandboxManager.getInstance() != null;
+ // Sandboxed Web App is openend in main process using an old shortcut
+ if (packageName.equals(processName) && webapp.isUseContainer() && hasSandboxing) {
+ WebViewLauncher.startWebViewInNewProcess(webapp, this);
+ }
- // Sandboxed Web App is openend in main process using an old shortcut
- if(packageName.equals(processName) && webapp.isUseContainer()) {
+ if (!packageName.equals(processName) && hasSandboxing) {
+ if (SandboxManager.getInstance().isSandboxUsedByAnotherApp(webapp)) {
+ SandboxManager.getInstance().unregisterWebAppFromSandbox(webapp.getContainerId());
WebViewLauncher.startWebViewInNewProcess(webapp, this);
}
-
- if (!packageName.equals(processName) && SandboxManager.getInstance() != null) {
- if (SandboxManager.getInstance().isSandboxUsedByAnotherApp(webapp)) {
- SandboxManager.getInstance().unregisterWebAppFromSandbox(webapp.getContainerId());
- WebViewLauncher.startWebViewInNewProcess(webapp, this);
- }
- try {
- SandboxManager.getInstance().registerWebAppToSandbox(webapp);
- WebView.setDataDirectorySuffix(webapp.getContainerId() + webapp.getAlphanumericBaseUrl() + "_" + webapp.getID());
- } catch (IllegalStateException e) {
- e.printStackTrace();
- }
+ try {
+ SandboxManager.getInstance().registerWebAppToSandbox(webapp);
+ WebView.setDataDirectorySuffix(webapp.getContainerId() + webapp.getAlphanumericBaseUrl() + "_" + webapp.getID());
+ } catch (IllegalStateException e) {
+ e.printStackTrace();
}
-
}
+
setContentView(R.layout.full_webview);
if(webapp.isKeepAwake()) {
@@ -162,26 +177,43 @@ private void setupWebView() {
wv = findViewById(R.id.webview);
progressBar = findViewById(R.id.progressBar);
- if (webapp.isUseAdblock()) {
+ List adblockConfigs = DataManager.getInstance().getSettings().getGlobalWebApp().getAdBlockSettings();
+ if (webapp.isUseAdblock() && !adblockConfigs.isEmpty()) {
wv.setVisibility(View.GONE);
wv = findViewById(R.id.adblockwebview);
wv.setVisibility(View.VISIBLE);
+
+ adFilter.setupWebView(wv);
+ adblockLifecycleHelper.beforeAdblockOperation(() -> adblockProviderApiHelper.synchronizeAdblockProviderWithSettings(adblockConfigs));
+
+ adFilter.getViewModel().getOnDirty().observe(this, none -> wv.clearCache(false)
+ );
+
+ adFilter.getViewModel().getEnabledFilterCount().observe(this, count -> {
+ if (count == adblockConfigs.size()) {
+ adblockLifecycleHelper.afterAdblockOperation();
+ }
+ });
}
+
String fieldName = Stream.of(WebViewActivity.class.getDeclaredFields()).filter(f -> f.getType() == WebView.class).findFirst().orElseThrow(null).getName();
String uaString = wv.getSettings().getUserAgentString().replace("; " + fieldName, "");
wv.getSettings().setUserAgentString(uaString);
if (webapp.isUseCustomUserAgent()) {
- wv.getSettings().setUserAgentString(webapp.getUserAgent().replace("\0", "").replace("\n", "").replace("\r", ""));
+ if(webapp.getUserAgent() != null && !webapp.getUserAgent().equals("")) {
+ wv.getSettings().setUserAgentString(webapp.getUserAgent().replace("\0", "").replace("\n", "").replace("\r", ""));
+ }
}
if (webapp.isShowFullscreen()) {
this.hideSystemBars();
- } else {
+ } else if(DataManager.getInstance().getSettings().getAlwaysShowSoftwareButtons()) {
this.showSystemBars();
}
wv.setWebViewClient(new CustomBrowser());
wv.getSettings().setSafeBrowsingEnabled(false);
wv.getSettings().setDomStorageEnabled(true);
+ wv.getSettings().setDatabaseEnabled(true);
wv.getSettings().setAllowFileAccess(true);
wv.getSettings().setBlockNetworkLoads(false);
// wv.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
@@ -217,6 +249,7 @@ private void setupWebView() {
loadURL(wv, url);
wv.setWebChromeClient(new CustomWebChromeClient());
wv.setOnLongClickListener(view -> {
+ if(webapp.getAlwaysUseFallbackContextMenu()) return false;
if(fallbackToDefaultLongClickBehaviour) {
fallbackToDefaultLongClickBehaviour = false;
return false;
@@ -225,6 +258,7 @@ private void setupWebView() {
return true;
});
+
wv.setDownloadListener((dl_url, userAgent, contentDisposition, mimeType, contentLength) -> {
if (mimeType.equals("application/pdf")) {
@@ -232,39 +266,55 @@ private void setupWebView() {
i.setData(Uri.parse(dl_url));
startActivity(i);
} else {
- DownloadManager.Request request = new DownloadManager.Request(
- Uri.parse(dl_url));
- String file_name = Utility.getFileNameFromDownload(dl_url, contentDisposition, mimeType);
-
- request.setMimeType(mimeType);
- request.addRequestHeader("cookie", CookieManager.getInstance().getCookie(dl_url));
- request.addRequestHeader("User-Agent", userAgent);
- request.setTitle(file_name);
- request.allowScanningByMediaScanner();
- request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
- request.setDestinationInExternalPublicDir(
- Environment.DIRECTORY_DOWNLOADS, file_name);
-
- DownloadManager dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
-
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
- String[] perms = {Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE};
- if (!EasyPermissions.hasPermissions(WebViewActivity.this, perms)) {
- dl_request = request;
- EasyPermissions.requestPermissions(WebViewActivity.this, getString(R.string.permission_storage_rationale), Const.PERMISSION_RC_STORAGE, perms);
- } else {
- if (dm != null) {
- dm.enqueue(request);
- Utility.showInfoSnackbar(this, getString(R.string.file_download), Snackbar.LENGTH_SHORT);
+ if(dl_url != null && !dl_url.equals("")) {
+ if(dl_url.startsWith("blob:")) {
+ dl_url = dl_url.replace("blob:", "");
+ try {
+ dl_url = URLDecoder.decode(dl_url, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ e.printStackTrace();
}
}
- }
- //No storage permission needed for Android 10+
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- if (dm != null) {
- dm.enqueue(request);
- Utility.showInfoSnackbar(this, getString(R.string.file_download), Snackbar.LENGTH_SHORT);
+ DownloadManager.Request request = null;
+ try {
+ request = new DownloadManager.Request(
+ Uri.parse(dl_url));
}
+ catch(Exception e) {
+ NotificationUtils.showInfoSnackbar(this, getString(R.string.file_download), Snackbar.LENGTH_SHORT);
+ }
+ String file_name = Utility.getFileNameFromDownload(dl_url, contentDisposition, mimeType);
+
+ request.setMimeType(mimeType);
+ request.addRequestHeader("cookie", CookieManager.getInstance().getCookie(dl_url));
+ request.addRequestHeader("User-Agent", userAgent);
+ request.setTitle(file_name);
+ request.allowScanningByMediaScanner();
+ request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
+ request.setDestinationInExternalPublicDir(
+ Environment.DIRECTORY_DOWNLOADS, file_name);
+
+ DownloadManager dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
+ String[] perms = {Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE};
+ if (!EasyPermissions.hasPermissions(WebViewActivity.this, perms)) {
+ dl_request = request;
+ EasyPermissions.requestPermissions(WebViewActivity.this, getString(R.string.permission_storage_rationale), Const.PERMISSION_RC_STORAGE, perms);
+ } else {
+ if (dm != null) {
+ dm.enqueue(request);
+ NotificationUtils.showInfoSnackbar(this, getString(R.string.file_download), Snackbar.LENGTH_SHORT);
+ }
+ }
+ }
+ //No storage permission needed for Android 10+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ if (dm != null) {
+ dm.enqueue(request);
+ NotificationUtils.showInfoSnackbar(this, getString(R.string.file_download), Snackbar.LENGTH_SHORT);
+ }
+ }
}
}
@@ -332,30 +382,50 @@ public boolean onTouch(View v, MotionEvent event) {
});
}
+ @SuppressLint("RequiresFeature")
private void setDarkModeIfNeeded() {
+ if (!BuildConfig.FLAVOR.contains("extended")) {
+ return;
+ }
+
boolean needsForcedDarkMode = webapp.isUseTimespanDarkMode() &&
- Utility.isInInterval(Utility.convertStringToCalendar(webapp.getTimespanDarkModeBegin()), Calendar.getInstance(), Utility.convertStringToCalendar(webapp.getTimespanDarkModeEnd()))
+ DateUtils.isInInterval(DateUtils.convertStringToCalendar(webapp.getTimespanDarkModeBegin()), Calendar.getInstance(), DateUtils.convertStringToCalendar(webapp.getTimespanDarkModeEnd()))
|| (!webapp.isUseTimespanDarkMode() && webapp.isForceDarkMode());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- if (Utility.isNightMode(this) || needsForcedDarkMode) {
- wv.getSettings().setForceDark(WebSettings.FORCE_DARK_ON);
- } else {
- wv.getSettings().setForceDark(WebSettings.FORCE_DARK_OFF);
- }
+ boolean isForceDarkSupported = WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK);
+ boolean isForceDarkStrategySupported = WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK_STRATEGY);
+ boolean isAlgorithmicDarkeningSupported = WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING);
if (needsForcedDarkMode) {
- if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK_STRATEGY)) {
+ wv.setBackgroundColor(Color.BLACK);
+ wv.setForceDarkAllowed(true);
+ getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_YES);
+ if (isForceDarkSupported) {
+ WebSettingsCompat.setForceDark(wv.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
+ }
+ if (isForceDarkStrategySupported) {
WebSettingsCompat.setForceDarkStrategy(wv.getSettings(), WebSettingsCompat.DARK_STRATEGY_PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING);
}
- wv.setBackgroundColor(Color.BLACK);
+ if (isAlgorithmicDarkeningSupported) {
+ WebSettingsCompat.setAlgorithmicDarkeningAllowed(wv.getSettings(), true);
+ }
} else {
- if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK_STRATEGY)) {
+ getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
+ wv.setBackgroundColor(Color.WHITE);
+
+ if (isForceDarkSupported) {
+ WebSettingsCompat.setForceDark(wv.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
+ }
+ if (isForceDarkStrategySupported) {
WebSettingsCompat.setForceDarkStrategy(wv.getSettings(), WebSettingsCompat.DARK_STRATEGY_WEB_THEME_DARKENING_ONLY);
}
- wv.setBackgroundColor(Color.WHITE);
+ if (isAlgorithmicDarkeningSupported) {
+ WebSettingsCompat.setAlgorithmicDarkeningAllowed(wv.getSettings(), false);
+ }
}
}
+
}
@SuppressLint("NonConstantResourceId")
@@ -364,13 +434,30 @@ private void showWebViewPopupMenu() {
mPopupMenu = IconPopupMenuHelper.getMenu(center, R.menu.wv_context_menu, WebViewActivity.this);
String currentUrl = wv.getUrl();
- String title = currentUrl.length() < 32 ? currentUrl : currentUrl.substring(0, 32) + "…";
- SpannableString spanString = new SpannableString(title);
- spanString.setSpan(new ForegroundColorSpan(Color.BLACK), 0, spanString.length(), 0); //fix the color to white
- spanString.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 0, spanString.length(), 0);
- mPopupMenu.getMenu().getItem(0).setTitle(spanString);
- if(wv.canGoForward()) mPopupMenu.getMenu().getItem(2).setVisible(true);
+ String title = "";
+ if (currentUrl != null) {
+ title = currentUrl.length() < 32 ? currentUrl : currentUrl.substring(0, 32) + "…";
+ }
+ SpannableString spanStringWebAppTitle = new SpannableString(title);
+
+ // The item is disabled because it has no click action, but we want to override the disabled style (text color)
+ int colorOnSurface = MaterialColors.getColor(center, R.attr.colorOnSurface, Color.BLACK);
+ ForegroundColorSpan foregroundColorSpan = new ForegroundColorSpan(colorOnSurface);
+ spanStringWebAppTitle.setSpan(foregroundColorSpan, 0, spanStringWebAppTitle.length(), 0);
+ spanStringWebAppTitle.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 0, spanStringWebAppTitle.length(), 0);
+ mPopupMenu.getMenu().getItem(0).setTitle(spanStringWebAppTitle);
+
+ for (int i = 0; i < mPopupMenu.getMenu().size(); i++) {
+ MenuItem item = mPopupMenu.getMenu().getItem(i);
+ SpannableString spanString = new SpannableString(item.getTitle());
+ spanString.setSpan(foregroundColorSpan, 0, spanString.length(),0);
+ item.setTitle(spanString);
+ }
+ if(wv.canGoForward()) mPopupMenu.getMenu().getItem(2).setVisible(true);
+ if(BuildConfig.DEBUG) {
+ mPopupMenu.getMenu().getItem(6).setVisible(true);
+ }
mPopupMenu.setOnMenuItemClickListener(menuItem -> {
switch(menuItem.getItemId()) {
case R.id.cmItemForward:
@@ -397,13 +484,21 @@ private void showWebViewPopupMenu() {
case R.id.cmItemCloseWebApp:
finishAndRemoveTask();
return true;
- case R.id.cmSelectText:
+ case R.id.cmFallbackContextmenuTemp:
fallbackToDefaultLongClickBehaviour = true;
return true;
case R.id.cmMainMenu:
Intent intent = new Intent(this, MainActivity.class);
startActivity(intent);
return true;
+ case R.id.cmShowAdblockProviders:
+ StringBuilder message = new StringBuilder();
+ for(Map.Entry entry : Objects.requireNonNull(AdFilter.Companion.get().getViewModel().getFilters().getValue()).entrySet()) {
+ Filter filter = entry.getValue();
+ message.append(filter.getUrl()).append(" has downloaded: ").append(filter.hasDownloaded()).append("\n\n");
+ }
+ NotificationUtils.showToast(this, message.toString());
+ return true;
}
return false;
@@ -469,7 +564,7 @@ protected void onResume() {
protected void onPause() {
super.onPause();
- wv.evaluateJavascript("document.querySelectorAll('audio').forEach(x => x.pause());", null);
+ wv.evaluateJavascript("document.querySelectorAll('audio').forEach(x => x.pause());document.querySelectorAll('video').forEach(x => x.pause());", null);
wv.onPause();
wv.pauseTimers();
if(mPopupMenu != null) mPopupMenu.dismiss();
@@ -498,6 +593,7 @@ public WebView getWebView() {
private Map initCustomHeaders(boolean save_data) {
Map extraHeaders = new HashMap<>();
extraHeaders.put("DNT", "1");
+ extraHeaders.put("X-REQUESTED-WITH", "");
if (save_data)
extraHeaders.put("Save-Data", "on");
return Collections.unmodifiableMap(extraHeaders);
@@ -598,7 +694,7 @@ public void onPermissionsGranted(int requestCode, @NonNull List list) {
DownloadManager dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
if (dm != null) {
dm.enqueue(dl_request);
- Utility.showInfoSnackbar(this, getString(R.string.file_download), Snackbar.LENGTH_SHORT);
+ NotificationUtils.showInfoSnackbar(this, getString(R.string.file_download), Snackbar.LENGTH_SHORT);
}
dl_request = null;
@@ -683,7 +779,7 @@ public boolean onShowFileChooser(
Intent intent = fileChooserParams.createIntent();
startActivityForResult(intent, CODE_OPEN_FILE);
} catch (Exception e) {
- Utility.showInfoSnackbar(WebViewActivity.this, getString(R.string.no_filemanager), Snackbar.LENGTH_LONG);
+ NotificationUtils.showInfoSnackbar(WebViewActivity.this, getString(R.string.no_filemanager), Snackbar.LENGTH_LONG);
e.printStackTrace();
}
return true;
@@ -769,8 +865,32 @@ public void onGeolocationPermissionsShowPrompt(final String origin,
}
}
+ private void showHttpAuthDialog(final HttpAuthHandler handler, String host, String realm) {
+ DialogHttpAuthBinding localBinding = DialogHttpAuthBinding.inflate(LayoutInflater.from(this));
+ new AlertDialog.Builder(this)
+ .setView(localBinding.getRoot())
+ .setTitle(getString(R.string.http_auth_title))
+ .setMessage(getString(R.string.enter_http_auth_credentials, realm, host))
+ .setPositiveButton(getString(R.string.ok), (dialog, whichButton) -> {
+ String username = localBinding.username.getText().toString();
+ String password = localBinding.password.getText().toString();
+
+ handler.proceed(username, password);
+
+ })
+ .setNegativeButton(getString(R.string.cancel), (dialog, whichButton) -> handler.cancel())
+ .show();
+ }
+
private class CustomBrowser extends WebViewClient {
+ private AdFilter adFilter = AdFilter.Companion.get();
+
+ @Override
+ public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
+ showHttpAuthDialog(handler, host, realm);
+ }
+
@Override
public void onPageFinished(WebView view, String url) {
if(url.equals("about:blank")) {
@@ -781,16 +901,28 @@ public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
}
+ @Override
+ public void onPageStarted(WebView view, String url, Bitmap favicon) {
+ adFilter.performScript(view, url);
+ super.onPageStarted(view, url, favicon);
+ }
+
@Nullable
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
if(urlOnFirstPageload.equals("")) urlOnFirstPageload = request.getUrl().toString();
+
+ if(webapp.isUseAdblock()) {
+ return (adFilter.shouldIntercept(view, request)).getResourceResponse();
+ }
if (webapp.isBlockThirdPartyRequests()) {
Uri uri = request.getUrl();
Uri webapp_uri = Uri.parse(webapp.getBaseUrl());
- if (!uri.getHost().endsWith(webapp_uri.getHost())) {
- return null;
+ if(uri.getHost() != null) {
+ if (!uri.getHost().endsWith(webapp_uri.getHost())) {
+ return new WebResourceResponse("text/plain", "utf-8", null);
+ }
}
}
return super.shouldInterceptRequest(view, request);
@@ -840,8 +972,14 @@ public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslE
@Override
public void onLoadResource(WebView view, String url) {
super.onLoadResource(view, url);
- if (DataManager.getInstance().getWebApp(webappID).isRequestDesktop())
- view.evaluateJavascript("document.querySelector('meta[name=\"viewport\"]').setAttribute('content', 'width=1024px, initial-scale=' + (document.documentElement.clientWidth / 1024));", null);
+
+ if (DataManager.getInstance().getWebApp(webappID).isRequestDesktop())
+ view.evaluateJavascript("""
+ var needsForcedWidth = document.documentElement.clientWidth < 1200;
+ if(needsForcedWidth) {
+ document.querySelector('meta[name=\"viewport\"]').setAttribute('content', 'width=1200px, initial-scale=' + (document.documentElement.clientWidth / 1200));
+ }
+ """, null);
view.evaluateJavascript("document.addEventListener( \"visibilitychange\" , (event) => { event.stopImmediatePropagation(); } );", null);
}
diff --git a/app/src/main/java/com/cylonid/nativealpha/model/DataManager.java b/app/src/main/java/com/cylonid/nativealpha/model/DataManager.java
index 9ef0ad26..0d7ff6ad 100644
--- a/app/src/main/java/com/cylonid/nativealpha/model/DataManager.java
+++ b/app/src/main/java/com/cylonid/nativealpha/model/DataManager.java
@@ -9,8 +9,12 @@
import android.widget.Toast;
import com.cylonid.nativealpha.R;
+import com.cylonid.nativealpha.model.deserializer.GlobalSettingsDeserializer;
+import com.cylonid.nativealpha.model.deserializer.WebAppDeserializer;
import com.cylonid.nativealpha.util.App;
+import com.cylonid.nativealpha.util.Const;
import com.cylonid.nativealpha.util.InvalidChecksumException;
+import com.cylonid.nativealpha.util.ShortcutIconUtils;
import com.cylonid.nativealpha.util.Utility;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
@@ -24,6 +28,7 @@
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;
@@ -38,6 +43,8 @@ public class DataManager {
private static final String SHARED_PREF_KEY = "WEBSITEDATA";
private static final String GENERAL_INFO = "com.cylonid.nativealpha.GENERAL_INFO";
public static final String EULA_ACCEPTED = "eulaAccepted";
+
+ public static final String ADBLOCK_CRASH = "adblockCrash";
public static final String LAST_SHOWN_UPDATE = "lastShownUpdate";
public static final String DATA_FORMAT = "dataFormat";
@@ -101,22 +108,20 @@ public void saveWebAppData() {
editor.apply();
}
- public void setDataFormat(int dataFormat) {
- getGeneralInfo().edit().putInt(DATA_FORMAT, dataFormat).apply();
- }
- public int getDataFormat() {
- return getGeneralInfo().getInt(DATA_FORMAT, LEGACY_DATA_FORMAT);
- }
public boolean getEulaData() {
return getGeneralInfo().getBoolean(EULA_ACCEPTED, false);
}
+ public boolean getHasAdblockCrashed() { return getGeneralInfo().getBoolean(ADBLOCK_CRASH, false);}
+
public int getLastShownUpdate() {
return getGeneralInfo().getInt(LAST_SHOWN_UPDATE, 0);
}
+ public void setHasAdblockCrashed(boolean newValue) { getGeneralInfo().edit().putBoolean(ADBLOCK_CRASH, newValue).apply(); }
+
public void setEulaData(boolean newValue) {
getGeneralInfo().edit().putBoolean(EULA_ACCEPTED, newValue).apply();
}
@@ -143,7 +148,7 @@ private void checkIfWebAppIdsCollide(ArrayList oldWebApps, ArrayList() {}.getType());
- }
- }
- else
loadGlobalSettingsLegacy();
+ }
+ //Global settings
+ if (appdata.contains(shared_pref_globalsettings)) {
+ GsonBuilder gsonBuilder = new GsonBuilder();
+ gsonBuilder.registerTypeAdapter(WebApp.class, new WebAppDeserializer());
+ gsonBuilder.registerTypeAdapter(GlobalSettings.class, new GlobalSettingsDeserializer());
+ Gson gson = gsonBuilder.create();
+ String json = appdata.getString(shared_pref_globalsettings, "");
+ int oldDataFormat = DataVersionConverter.getDataFormat(json);
+ String currentDataFormattedJson = this.checkDataFormat(oldDataFormat, json);
+ settings = gson.fromJson(currentDataFormattedJson, GlobalSettings.class);
+ assertGlobalWebappData();
+ if(oldDataFormat != DataVersionConverter.getDataFormat(currentDataFormattedJson)) this.saveGlobalSettings();
+ }
+
}
public void loadGlobalSettingsLegacy() {
@@ -208,30 +217,19 @@ public void saveGlobalSettings() {
editor.apply();
}
-
-// public void initDummyData()
-// {
-// loadAppData();
-// WebApp d1 = new WebApp("orf.at");
-// WebApp d2 = new WebApp("diepresse.com");
-// WebApp d3 = new WebApp("oebb.at");
-//
-// addWebsite(d1);
-// addWebsite(d2);
-// addWebsite(d3);
-//
-// }
-
public void addWebsite(WebApp new_site) {
websites.add(new_site);
- Utility.Assert(new_site.getBaseUrl().equals(websites.get(new_site.getID()).getBaseUrl()), "WebApp ID and array position out of sync.");
saveWebAppData();
}
public int getIncrementedID() {
- max_assigned_ID++;
- return max_assigned_ID;
+ return getWebsites().size();
}
+
+ public int getIncrementedOrder() {
+ return getActiveWebsitesCount() + 1;
+ }
+
public ArrayList getWebsites() {
Utility.Assert(websites != null, "Websites not loaded");
return websites;
@@ -239,10 +237,12 @@ public ArrayList getWebsites() {
public ArrayList getActiveWebsites() {
ArrayList active_webapps = new ArrayList<>();
+
for (WebApp webapp : websites) {
if (webapp.isActiveEntry())
active_webapps.add(webapp);
}
+ active_webapps.sort(Comparator.comparingInt(WebApp::getOrder));
return active_webapps;
}
@@ -349,7 +349,6 @@ private String checkDataFormat(int dataFormat, String jsonInput) {
switch(dataFormat) {
case LEGACY_DATA_FORMAT:
String convertedInput = DataVersionConverter.convertToDataFormat(jsonInput, DataVersionConverter.getLegacyTo1300Map());
- this.setDataFormat(1300);
return convertedInput;
default:
case 1300: // Current data format => corresponding to app release version
@@ -379,19 +378,16 @@ public WebApp getPredecessor(int i) {
}
while (!websites.get(neighbor).isActiveEntry());
return websites.get(neighbor);
+ }
-// if (i != (websites.size() - 1)) {
-// return websites.get(i + 1);
-// }
-// else
-// return websites.get(0);
-
-//
-// if (i != 0) {
-// return websites.get(i - 1);
-// }
-// else
-// return websites.get(websites.size() - 1);
+ private void assertGlobalWebappData() {
+ boolean override = settings.getGlobalWebApp().isOverrideGlobalSettings();
+ int container = settings.getGlobalWebApp().getContainerId();
+ if(!override || container != Const.NO_CONTAINER) {
+ settings.getGlobalWebApp().setOverrideGlobalSettings(true);
+ settings.getGlobalWebApp().setContainerId(Const.NO_CONTAINER);
+ this.saveGlobalSettings();
+ }
}
diff --git a/app/src/main/java/com/cylonid/nativealpha/model/DataVersionConverter.java b/app/src/main/java/com/cylonid/nativealpha/model/DataVersionConverter.java
index 95b3c07f..0348a08c 100644
--- a/app/src/main/java/com/cylonid/nativealpha/model/DataVersionConverter.java
+++ b/app/src/main/java/com/cylonid/nativealpha/model/DataVersionConverter.java
@@ -15,8 +15,8 @@ public static String convertToDataFormat(String input, Map map)
}
public static int getDataFormat(String input) {
- if(input.contains(DataVersionConverter.formatAsJsonKey("base_url"))) return 1000;
- if(input.contains(DataVersionConverter.formatAsJsonKey("baseUrl"))) return 1300;
+ if(input.contains(DataVersionConverter.formatAsJsonKey("allow_js"))) return 1000;
+ if(input.contains(DataVersionConverter.formatAsJsonKey("isAllowJs"))) return 1300;
return 0;
}
diff --git a/app/src/main/java/com/cylonid/nativealpha/model/GlobalSettings.java b/app/src/main/java/com/cylonid/nativealpha/model/GlobalSettings.java
deleted file mode 100644
index 92cbbf30..00000000
--- a/app/src/main/java/com/cylonid/nativealpha/model/GlobalSettings.java
+++ /dev/null
@@ -1,97 +0,0 @@
-package com.cylonid.nativealpha.model;
-
-public class GlobalSettings {
-
- private boolean clear_cache;
- private boolean clear_cookies;
- private boolean two_finger_multitouch;
- private boolean three_finger_multitouch;
- private boolean show_progressbar;
- private boolean multitouch_reload;
- private int theme_id;
- private WebApp global_web_app;
-
- public GlobalSettings(GlobalSettings other) {
- this.clear_cache = other.clear_cache;
- this.clear_cookies = other.clear_cookies;
- this.two_finger_multitouch = other.two_finger_multitouch;
- this.three_finger_multitouch = other.three_finger_multitouch;
- this.theme_id = other.theme_id;
- this.multitouch_reload = other.multitouch_reload;
- this.show_progressbar = other.show_progressbar;
- this.global_web_app = other.global_web_app;
- }
-
- public GlobalSettings() {
- clear_cache = false;
- clear_cookies = false;
- two_finger_multitouch = true;
- three_finger_multitouch = false;
- multitouch_reload = true;
- theme_id = 0;
- show_progressbar = false;
- global_web_app = new WebApp("about:blank", Integer.MAX_VALUE);
- }
-
- public boolean isTwoFingerMultitouch() {
- return two_finger_multitouch;
- }
-
- public void setTwoFingerMultitouch(boolean twoFingerMultitouch) {
- this.two_finger_multitouch = twoFingerMultitouch;
- }
-
- public boolean isThreeFingerMultitouch() {
- return three_finger_multitouch;
- }
-
- public void setThreeFingerMultitouch(boolean threeFingerMultitouch) {
- this.three_finger_multitouch = threeFingerMultitouch;
- }
-
- public boolean isClearCache() {
- return clear_cache;
- }
-
- public void setClearCache(boolean clearCache) {
- this.clear_cache = clearCache;
- }
-
- public void setClearCookies(boolean clear_cookies) {
- this.clear_cookies = clear_cookies;
- }
-
- public int getThemeId() {
- return theme_id;
- }
-
- public void setThemeId(int theme_id) {
- this.theme_id = theme_id;
- }
-
- public boolean isMultitouchReload() {
- return multitouch_reload;
- }
-
- public void setMultitouchReload(boolean multitouch_reload) {
- this.multitouch_reload = multitouch_reload;
- }
-
- public boolean isShowProgressbar() {
- return show_progressbar;
- }
-
- public void setShowProgressbar(boolean show_progressbar) {
- this.show_progressbar = show_progressbar;
- }
-
- public WebApp getGlobalWebApp() {
- return global_web_app;
- }
-
- public void setGlobalWebApp(WebApp globalWebApp) {
- this.global_web_app = globalWebApp;
- }
-
-
-}
diff --git a/app/src/main/java/com/cylonid/nativealpha/model/GlobalSettings.kt b/app/src/main/java/com/cylonid/nativealpha/model/GlobalSettings.kt
new file mode 100644
index 00000000..1b3f0c2f
--- /dev/null
+++ b/app/src/main/java/com/cylonid/nativealpha/model/GlobalSettings.kt
@@ -0,0 +1,21 @@
+package com.cylonid.nativealpha.model
+
+import com.cylonid.nativealpha.util.Const
+
+
+data class GlobalSettings(
+ var isClearCache: Boolean = false,
+ var isTwoFingerMultitouch: Boolean = true,
+ var isThreeFingerMultitouch: Boolean = false,
+ var isShowProgressbar: Boolean = false,
+ var isMultitouchReload: Boolean = true,
+ var themeId: Int = 0,
+ var globalWebApp: WebApp = WebApp("about:blank", Int.MAX_VALUE, Const.getDefaultAdBlockConfig()),
+ var alwaysShowSoftwareButtons: Boolean = false,
+ var clear_cookies: Boolean = false
+) {
+
+ fun setClearCookies(clear_cookies: Boolean) {
+ this.clear_cookies = clear_cookies
+ }
+}
diff --git a/app/src/main/java/com/cylonid/nativealpha/model/GlobalSettingsInstanceCreator.java b/app/src/main/java/com/cylonid/nativealpha/model/GlobalSettingsInstanceCreator.java
deleted file mode 100644
index 42473c5a..00000000
--- a/app/src/main/java/com/cylonid/nativealpha/model/GlobalSettingsInstanceCreator.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.cylonid.nativealpha.model;
-
-import com.google.gson.InstanceCreator;
-
-import java.lang.reflect.Type;
-
-public class GlobalSettingsInstanceCreator implements InstanceCreator
-{
- @Override
- public GlobalSettings createInstance(Type type)
- {
- return new GlobalSettings();
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/cylonid/nativealpha/model/SandboxManager.java b/app/src/main/java/com/cylonid/nativealpha/model/SandboxManager.java
index b9d57a2f..b9a2305c 100644
--- a/app/src/main/java/com/cylonid/nativealpha/model/SandboxManager.java
+++ b/app/src/main/java/com/cylonid/nativealpha/model/SandboxManager.java
@@ -21,7 +21,7 @@ private SandboxManager()
}
public static SandboxManager getInstance() {
- if (BuildConfig.FLAVOR.equals("extended")) {
+ if (BuildConfig.FLAVOR.contains("extended")) {
instance = instance == null ? new SandboxManager() : instance;
return instance;
}
diff --git a/app/src/main/java/com/cylonid/nativealpha/util/App.java b/app/src/main/java/com/cylonid/nativealpha/util/App.java
index e8e1ee55..a548a6e3 100644
--- a/app/src/main/java/com/cylonid/nativealpha/util/App.java
+++ b/app/src/main/java/com/cylonid/nativealpha/util/App.java
@@ -4,6 +4,9 @@
import android.app.Application;
import android.content.Context;
+import androidx.work.Configuration;
+import androidx.work.WorkManager;
+
public class App extends Application {
@SuppressLint("StaticFieldLeak") //We are using app context which is never deleted during runtime, so this is not a leak per se.
@@ -12,7 +15,13 @@ public class App extends Application {
public void onCreate() {
super.onCreate();
+
App.context = getApplicationContext();
+ if(!WorkManager.isInitialized()) {
+ WorkManager.initialize(this, new Configuration.Builder().build());
+ }
+
+
}
public static Context getAppContext() {
diff --git a/app/src/main/java/com/cylonid/nativealpha/util/Const.java b/app/src/main/java/com/cylonid/nativealpha/util/Const.java
index a9f3d772..4b41a68d 100644
--- a/app/src/main/java/com/cylonid/nativealpha/util/Const.java
+++ b/app/src/main/java/com/cylonid/nativealpha/util/Const.java
@@ -1,7 +1,11 @@
package com.cylonid.nativealpha.util;
+import com.cylonid.nativealpha.model.AdblockConfig;
+
+import java.util.ArrayList;
+
public class Const {
- public static final String DESKTOP_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:98.0) Gecko/20100101 Firefox/98.0";
+ public static final String DESKTOP_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0";
public static final String INTENT_WEBAPPID = "webappID";
public static final String INTENT_BACKUP_RESTORED = "backup_restored";
@@ -10,10 +14,6 @@ public class Const {
public static final int NO_CONTAINER = -1;
- public static final int RESULT_IDX_FAVICON = 0;
- public static final int RESULT_IDX_TITLE = 1;
- public static final int RESULT_IDX_NEW_BASEURL = 2;
-
public static final int PERMISSION_RC_LOCATION = 123;
public static final int PERMISSION_RC_STORAGE = 132;
public static final int PERMISSION_CAMERA = 100;
@@ -23,6 +23,12 @@ public class Const {
public static final int CODE_WRITE_FILE = 4096;
public static final int FAVICON_MIN_WIDTH = 96;
+
+ public static ArrayList getDefaultAdBlockConfig() {
+ ArrayList list = new ArrayList<>();
+ list.add(new AdblockConfig("Fanboy Ultimate List", "https://fanboy.co.nz/r/fanboy-ultimate.txt"));
+ return list;
+ }
}
diff --git a/app/src/main/java/com/cylonid/nativealpha/util/Utility.java b/app/src/main/java/com/cylonid/nativealpha/util/Utility.java
index 4c087e68..73c0b618 100644
--- a/app/src/main/java/com/cylonid/nativealpha/util/Utility.java
+++ b/app/src/main/java/com/cylonid/nativealpha/util/Utility.java
@@ -1,89 +1,13 @@
package com.cylonid.nativealpha.util;
-import android.annotation.SuppressLint;
-import android.app.Activity;
-import android.app.ActivityManager;
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ShortcutInfo;
-import android.content.pm.ShortcutManager;
-import android.content.res.Configuration;
-import android.graphics.Color;
-import android.net.Uri;
-import android.os.Build;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.Gravity;
+
import android.view.View;
import android.view.ViewGroup;
import android.webkit.URLUtil;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import androidx.annotation.AttrRes;
-import androidx.annotation.ColorInt;
-import androidx.annotation.NonNull;
-import androidx.annotation.StringRes;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.appcompat.app.AppCompatDelegate;
-import androidx.appcompat.widget.Toolbar;
-import androidx.core.content.res.ResourcesCompat;
-
-import com.cylonid.nativealpha.BuildConfig;
-import com.cylonid.nativealpha.R;
-import com.cylonid.nativealpha.WebViewActivity;
-import com.cylonid.nativealpha.model.DataManager;
-import com.cylonid.nativealpha.model.WebApp;
-import com.google.android.material.snackbar.Snackbar;
-
-import java.io.File;
-import java.io.FileWriter;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.List;
-import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class Utility {
- private static final String TAG = "XXX";
-
- public static void killWebSandbox(int id) {
- ActivityManager activityManager =
- (ActivityManager) App.getAppContext().getSystemService(Context.ACTIVITY_SERVICE);
-
- for (ActivityManager.RunningAppProcessInfo processInfo : activityManager.getRunningAppProcesses()) {
- if (processInfo.processName.contains("web_sandbox_" + id)) {
- android.os.Process.killProcess(processInfo.pid);
- }
- }
- }
-
- public static void deleteShortcuts(List removableWebAppIds) {
- ShortcutManager manager = App.getAppContext().getSystemService(ShortcutManager.class);
- for (ShortcutInfo info : manager.getPinnedShortcuts()) {
- int id = info.getIntent().getIntExtra(Const.INTENT_WEBAPPID, -1);
- if (removableWebAppIds.contains(id)) {
- manager.disableShortcuts(Arrays.asList(info.getId()), App.getAppContext().getString(R.string.webapp_already_deleted));
- }
- }
- }
-
- public static void showToast(Activity a, String text) {
- showToast(a, text, Toast.LENGTH_LONG);
- }
-
- public static void showToast(Activity a, String text, int toastDisplayDuration) {
- Toast toast = Toast.makeText(a, text, toastDisplayDuration);
- toast.setGravity(Gravity.TOP, 0, 100);
- toast.show();
- }
public static void setViewAndChildrenEnabled(View view, boolean enabled) {
@@ -104,56 +28,6 @@ public static void setViewAndChildrenEnabled(View view, boolean enabled) {
}
}
- public static Long getTimeInSeconds()
- {
- return System.currentTimeMillis() / 1000;
- }
-
- @SuppressLint("SimpleDateFormat")
- public static SimpleDateFormat getHourMinFormat() {
- return new SimpleDateFormat("HH:mm");
- }
- @SuppressLint("SimpleDateFormat")
- public static SimpleDateFormat getDayHourMinuteSecondsFormat() {
- return new SimpleDateFormat( "EEE, d MMM yyyy HH:mm:ss Z");
- }
-
-
-
- public static Calendar convertStringToCalendar(String str) {
- Calendar c = Calendar.getInstance();
- try {
- c.setTime(Objects.requireNonNull(getHourMinFormat().parse(str)));
- } catch (Exception e) {
- e.printStackTrace();
- }
- return c;
- }
-
- public static boolean isInInterval(Calendar low, Calendar time, Calendar high) {
- //Bring timestamp with day_current + HH:mm => day_unixZero + HH:mm by parsing it again...
- Calendar middle = Calendar.getInstance();
- try {
- middle.setTime(Objects.requireNonNull(getHourMinFormat().parse(Utility.getHourMinFormat().format(time.getTime()))));
- } catch (ParseException e) {
- e.printStackTrace();
- }
-
- //CASE: If the end of our timespan is after midnight, add one day to the end date to get a proper span.
- if (high.before(low)) {
- high.add(Calendar.DATE, 1);
- if (middle.before(low)) {
- middle.add(Calendar.DATE, 1);
- }
- }
- return middle.after(low) && middle.before(high);
- }
-// System.out.println("Low: " + Utility.getDayHourMinuteSecondsFormat().format(low.getTime()));
-// System.out.println("Middle: " + Utility.getDayHourMinuteSecondsFormat().format(middle.getTime()));
-// System.out.println("High: " + Utility.getDayHourMinuteSecondsFormat().format(high.getTime()));
-// System.out.println("Is Before high: " + (middle.before(high)));
-// System.out.println("Is after low: " + (middle.after(low)));
-
public static void Assert(boolean condition, String message) {
@@ -162,88 +36,6 @@ public static void Assert(boolean condition, String message) {
}
}
- public static Integer getWidthFromIcon(String size_string) {
- int x_index = size_string.indexOf("x");
- if (x_index == -1)
- x_index = size_string.indexOf("×");
-
- if (x_index == -1)
- return 1;
- String width = size_string.substring(0, x_index);
-
- return Integer.parseInt(width);
- }
-
-
- public static void applyUITheme() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- int id = DataManager.getInstance().getSettings().getThemeId();
- switch (id) {
- case 0:
- AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
- break;
- case 1:
- AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
- break;
- case 2:
- AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
- break;
- }
- }
- }
-
- public static boolean isNightMode(Context context) {
- int nightModeFlags = context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
- return nightModeFlags == Configuration.UI_MODE_NIGHT_YES;
- }
-
- public static void writeFileOnInternalStorage(Context mcoContext, String sFileName, String sBody){
-
- try {
- File gpxfile = new File(mcoContext.getExternalFilesDir(null), sFileName);
- FileWriter writer = new FileWriter(gpxfile);
- writer.append(sBody);
- writer.flush();
- writer.close();
- } catch (Exception e){
- e.printStackTrace();
- }
- }
-
- public static void showInfoSnackbar(AppCompatActivity activity, String msg, int duration) {
-
- Snackbar snackbar = Snackbar.make(activity.findViewById(android.R.id.content), msg, duration);
-
- snackbar.setAction(App.getAppContext().getString(android.R.string.ok), v -> snackbar.dismiss());
-
- View snackBarView = snackbar.getView();
- LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
- LinearLayout.LayoutParams.MATCH_PARENT,
- LinearLayout.LayoutParams.WRAP_CONTENT);
-
- params.setMargins(0, 30, 0, 20);
-
-
- snackBarView.setLayoutParams(params);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
- snackBarView.setForceDarkAllowed(false);
-
- TextView tv = snackBarView.findViewById(com.google.android.material.R.id.snackbar_text);
- tv.setMaxLines(10);
- snackbar.setBackgroundTint(ResourcesCompat.getColor(App.getAppContext().getResources(), R.color.snackbar_background, null));
- snackbar.setTextColor(Color.BLACK);
- snackbar.show();
-
- }
-
- public static boolean URLEqual(String left, String right) {
- if (left == null || right == null)
- return false;
- String stripped_left = left.replace("/", "").replace("www.", "");
- String stripped_right = right.replace("/", "").replace("www.", "");
- return stripped_left.equals(stripped_right);
- }
-
public static String getFileNameFromDownload(String url, String content_disposition, String mime_type) {
String file_name = null;
if (content_disposition != null && !content_disposition.equals("")) {
@@ -258,28 +50,4 @@ public static String getFileNameFromDownload(String url, String content_disposit
return file_name;
}
- @ColorInt
- public static int getThemeColor
- (
- @NonNull final Context context,
- @AttrRes final int attributeColor
- )
- {
- final TypedValue value = new TypedValue();
- context.getTheme ().resolveAttribute (attributeColor, value, true);
- return value.data;
- }
-
- public static String getProcessName(Context context) {
- if (context == null) return null;
- ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
- for (ActivityManager.RunningAppProcessInfo processInfo : manager.getRunningAppProcesses()) {
- if (processInfo.pid == android.os.Process.myPid()) {
- return processInfo.processName;
- }
- }
- return null;
- }
-
-
}
diff --git a/app/src/main/java/com/cylonid/nativealpha/util/WebViewLauncher.kt b/app/src/main/java/com/cylonid/nativealpha/util/WebViewLauncher.kt
index c8105365..ec0bc83c 100644
--- a/app/src/main/java/com/cylonid/nativealpha/util/WebViewLauncher.kt
+++ b/app/src/main/java/com/cylonid/nativealpha/util/WebViewLauncher.kt
@@ -1,5 +1,6 @@
package com.cylonid.nativealpha.util
+import android.app.Activity
import android.content.Context
import com.cylonid.nativealpha.model.WebApp
import androidx.appcompat.app.AppCompatActivity
@@ -16,7 +17,7 @@ object WebViewLauncher {
try {
c.startActivity(createWebViewIntent(webapp, c))
} catch (e: NullPointerException) {
- Utility.showInfoSnackbar(
+ NotificationUtils.showInfoSnackbar(
c as AppCompatActivity,
c.getString(R.string.webview_activity_launch_failed),
Snackbar.LENGTH_LONG
@@ -26,13 +27,13 @@ object WebViewLauncher {
}
@JvmStatic
- fun startWebViewInNewProcess(webapp: WebApp, c: Context) {
+ fun startWebViewInNewProcess(webapp: WebApp, a: Activity) {
try {
- ProcessPhoenix.triggerRebirth(c, createWebViewIntent(webapp, c))
+ ProcessPhoenix.triggerRebirth(a, createWebViewIntent(webapp, a))
} catch (e: NullPointerException) {
- Utility.showInfoSnackbar(
- c as AppCompatActivity,
- c.getString(R.string.webview_activity_launch_failed),
+ NotificationUtils.showInfoSnackbar(
+ a,
+ a.getString(R.string.webview_activity_launch_failed),
Snackbar.LENGTH_LONG
)
e.printStackTrace()
diff --git a/app/src/main/kotlin/com/cylonid/nativealpha/activities/AdblockConfigActivity.kt b/app/src/main/kotlin/com/cylonid/nativealpha/activities/AdblockConfigActivity.kt
new file mode 100644
index 00000000..fdcfc249
--- /dev/null
+++ b/app/src/main/kotlin/com/cylonid/nativealpha/activities/AdblockConfigActivity.kt
@@ -0,0 +1,98 @@
+package com.cylonid.nativealpha.activities
+
+import android.app.ActivityManager
+import android.content.Context
+import android.content.DialogInterface
+import android.os.Bundle
+import android.text.Editable
+import android.text.TextWatcher
+import android.view.LayoutInflater
+import androidx.appcompat.app.AlertDialog
+import androidx.appcompat.app.AppCompatActivity
+import com.cylonid.nativealpha.R
+import com.cylonid.nativealpha.databinding.AdblockConfigActivityBinding
+import com.cylonid.nativealpha.databinding.AddAdblockConfigDialogBinding
+import com.cylonid.nativealpha.fragments.adblocklist.AdblockListFragment
+import com.cylonid.nativealpha.model.AdblockConfig
+import com.cylonid.nativealpha.model.DataManager
+import com.cylonid.nativealpha.util.Const
+import com.cylonid.nativealpha.util.NotificationUtils
+import com.cylonid.nativealpha.util.ProcessUtils
+
+
+class AdblockConfigActivity : ToolbarBaseActivity() {
+ private lateinit var adblockListFragment: AdblockListFragment
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ binding.adblockFab.setOnClickListener { showAddAdblockDialog() }
+
+ binding.btnRestoreDefault.setOnClickListener {
+ DataManager.getInstance().apply {
+ settings.globalWebApp.adBlockSettings = Const.getDefaultAdBlockConfig()
+ saveGlobalSettings()
+ }
+ ProcessUtils.closeAllWebAppsAndProcesses(getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager)
+ updateAdblockList()
+ }
+
+ setToolbarTitle(getString(R.string.adblock_config))
+
+ adblockListFragment =
+ supportFragmentManager.findFragmentById(R.id.adblock_fragment_container_view) as AdblockListFragment
+ }
+
+ override fun inflateBinding(layoutInflater: LayoutInflater): AdblockConfigActivityBinding {
+ return AdblockConfigActivityBinding.inflate(layoutInflater)
+ }
+
+ private fun updateAdblockList() {
+ adblockListFragment.updateAdblockList()
+ }
+
+ private fun showAddAdblockDialog() {
+ val localBinding = AddAdblockConfigDialogBinding.inflate(layoutInflater)
+ val dialog = AlertDialog.Builder(this)
+ .setView(localBinding.root)
+ .setTitle(getString(R.string.add_a_new_adblock_provider))
+ .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
+
+ val url = localBinding.addAdblockUrl.text.toString().trim()
+
+ val formattedUrl =
+ if (url.startsWith("https://") || url.startsWith("http://")) url else "https://$url"
+
+ val urlAlreadyExists = DataManager.getInstance().settings.globalWebApp.adBlockSettings.any { it.value == formattedUrl}
+ if(urlAlreadyExists) {
+ NotificationUtils.showToast(this, getString(R.string.entry_already_exists))
+ return@setPositiveButton
+ }
+
+ DataManager.getInstance().apply {
+ val label =
+ if (localBinding.addAdblockLabel.text.isNotEmpty()) localBinding.addAdblockLabel.text.toString() else url
+ settings.globalWebApp.adBlockSettings += AdblockConfig(label, formattedUrl)
+ saveGlobalSettings()
+ }
+ updateAdblockList()
+ }
+ .setNegativeButton(android.R.string.cancel, null)
+ .create()
+ dialog.show()
+
+ val okButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE)
+ okButton.isEnabled = false
+ localBinding.addAdblockUrl.addTextChangedListener(object : TextWatcher {
+ override fun afterTextChanged(s: Editable?) {
+ okButton.isEnabled = !s.isNullOrBlank()
+ }
+
+ override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
+ override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
+ })
+
+ }
+}
+
+
diff --git a/app/src/main/kotlin/com/cylonid/nativealpha/activities/NewsActivity.kt b/app/src/main/kotlin/com/cylonid/nativealpha/activities/NewsActivity.kt
index d58b7f08..3f645301 100644
--- a/app/src/main/kotlin/com/cylonid/nativealpha/activities/NewsActivity.kt
+++ b/app/src/main/kotlin/com/cylonid/nativealpha/activities/NewsActivity.kt
@@ -1,138 +1,78 @@
package com.cylonid.nativealpha.activities;
+import android.annotation.SuppressLint
import android.content.Intent
-import android.graphics.drawable.Drawable
import android.os.Bundle
-import android.util.Log
import android.view.MotionEvent
import android.view.View
-import android.view.ViewTreeObserver
-import android.view.ViewTreeObserver.OnGlobalLayoutListener
+import android.webkit.JavascriptInterface
+import android.webkit.WebChromeClient
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
-import android.widget.Toast
-import androidx.annotation.ColorInt
+
+import androidx.activity.addCallback
import androidx.appcompat.app.AppCompatActivity
-import androidx.core.content.ContextCompat
import com.cylonid.nativealpha.BuildConfig
-import com.cylonid.nativealpha.R
+import com.cylonid.nativealpha.databinding.NewsActivityBinding
import com.cylonid.nativealpha.model.DataManager
import com.cylonid.nativealpha.util.LocaleUtils
-import com.cylonid.nativealpha.util.Utility
-import kotlinx.android.synthetic.main.news_activity.*
-import kotlin.properties.Delegates
-
-
-class NewsActivity : AppCompatActivity(), View.OnTouchListener, ViewTreeObserver.OnScrollChangedListener {
-
- var btnDefaultBackgroundColor: Drawable? = null
- var btnDefaultTextColor: Int = android.R.color.black
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.news_activity)
-
- initializeUI()
- setButtonState()
- }
- override fun onBackPressed() {}
+class NewsActivity : AppCompatActivity(), View.OnTouchListener {
- private fun disableAcceptButton() {
- btnNewsConfirm.isActivated = false
- btnDefaultTextColor = btnNewsConfirm.currentTextColor
- btnDefaultBackgroundColor = btnNewsConfirm.background
+ private lateinit var binding: NewsActivityBinding
- btnNewsConfirm.setBackgroundColor(
- ContextCompat.getColor(
- baseContext,
- R.color.disabled_background_color
- )
- )
- btnNewsConfirm.setTextColor(
- ContextCompat.getColor(
- baseContext,
- R.color.disabled_text_color
- )
- )
- btnNewsConfirm.setOnClickListener {
- Utility.showToast(
- this,
- getString(R.string.scroll_to_bottom),
- Toast.LENGTH_SHORT
- )
+ inner class WebAppInterface {
+ @JavascriptInterface
+ fun onOkButtonPressed() {
+ DataManager.getInstance().eulaData = true
+ DataManager.getInstance().lastShownUpdate = BuildConfig.VERSION_CODE
+ finish()
}
}
- private fun enableAcceptButton() {
- btnNewsConfirm.isActivated = true
- btnNewsConfirm.setTextColor(btnDefaultTextColor)
- btnNewsConfirm.background = btnDefaultBackgroundColor
+ @SuppressLint("SetJavaScriptEnabled")
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = NewsActivityBinding.inflate(layoutInflater)
+ val view = binding.root
+ setContentView(view)
- btnNewsConfirm.setOnClickListener { confirm() }
- }
+ binding.newsContent.settings.javaScriptEnabled = true
+ binding.newsContent.isLongClickable = false
+ binding.newsContent.webChromeClient = WebChromeClient()
- private fun initializeUI() {
+ onBackPressedDispatcher.addCallback(this) {}
setText()
- btnNewsConfirm.setOnClickListener {
- confirm()
- }
- }
-
- private fun setButtonState() {
- val vto: ViewTreeObserver = news_scrollchild.viewTreeObserver
- vto.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
- override fun onGlobalLayout() {
- val height: Int = news_scrollchild.measuredHeight
- if (height > 0) {
- news_scrollchild.viewTreeObserver.removeOnGlobalLayoutListener(this)
- if (news_scrollview.canScrollVertically(1) || news_scrollview.canScrollVertically(-1)) {
- news_scrollview.setOnTouchListener(this@NewsActivity)
- news_scrollview.viewTreeObserver.addOnScrollChangedListener(this@NewsActivity)
- disableAcceptButton()
- }
- }
- }
- })
}
private fun setText() {
val fileId = intent.extras?.getString("text") ?: "latestUpdate"
- news_content.loadUrl("file:///android_asset/news/" + fileId + "_" + LocaleUtils.fileEnding +".html")
- if(DataManager.getInstance().eulaData) {
- btnNewsConfirm.isEnabled = true
- news_content.settings.javaScriptEnabled = true
- news_content.webViewClient = NewsWebViewClient()
- }
- }
+ binding.newsContent.loadUrl("file:///android_asset/news/" + fileId + "_" + LocaleUtils.fileEnding + ".html")
+ binding.newsContent.addJavascriptInterface(WebAppInterface(), "NAlpha")
+ val hideEula = DataManager.getInstance().eulaData;
- private fun confirm() {
- DataManager.getInstance().eulaData = true
- DataManager.getInstance().lastShownUpdate = BuildConfig.VERSION_CODE
- finish()
+ binding.newsContent.webViewClient = NewsWebViewClient(
+ hideEula = hideEula,
+ showLiberaPay = BuildConfig.FLAVOR == "extendedGithub"
+ )
}
override fun onTouch(p0: View?, p1: MotionEvent?): Boolean {
return false
}
- override fun onScrollChanged() {
- val view = news_scrollview.getChildAt(news_scrollview.childCount - 1)
- val bottomDetector: Int = view.bottom - (news_scrollview.height + news_scrollview.scrollY)
-
- if (bottomDetector < 30) {
- enableAcceptButton()
- }
- }
}
-private class NewsWebViewClient : WebViewClient() {
+private class NewsWebViewClient(val hideEula: Boolean, val showLiberaPay: Boolean) :
+ WebViewClient() {
override fun onPageFinished(view: WebView, url: String) {
- view.evaluateJavascript("hideById('eula')", null)
- view.settings.javaScriptEnabled = false
+ if (hideEula) view.evaluateJavascript("hideById('eula')", null)
+ if (showLiberaPay) {
+ view.evaluateJavascript("showById('nonGp')", null)
+ }
}
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
diff --git a/app/src/main/kotlin/com/cylonid/nativealpha/activities/ToolbarBaseActivity.kt b/app/src/main/kotlin/com/cylonid/nativealpha/activities/ToolbarBaseActivity.kt
new file mode 100644
index 00000000..2c7d2f3b
--- /dev/null
+++ b/app/src/main/kotlin/com/cylonid/nativealpha/activities/ToolbarBaseActivity.kt
@@ -0,0 +1,48 @@
+package com.cylonid.nativealpha.activities
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import androidx.activity.addCallback
+import androidx.appcompat.app.AppCompatActivity
+import androidx.viewbinding.ViewBinding
+import com.cylonid.nativealpha.databinding.ActivityToolbarBaseBinding
+
+abstract class ToolbarBaseActivity : AppCompatActivity() {
+
+ private lateinit var _binding: VB
+ protected val binding get() = _binding
+
+ private var onNavigationClickListener: (() -> Unit)? = null
+
+ abstract fun inflateBinding(layoutInflater: LayoutInflater): VB
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ val baseBinding = ActivityToolbarBaseBinding.inflate(layoutInflater)
+ setContentView(baseBinding.root)
+
+ _binding = inflateBinding(layoutInflater)
+ baseBinding.activityContent.addView(_binding.root)
+
+ val toolbar = baseBinding.toolbar.topAppBar
+ setSupportActionBar(toolbar)
+ supportActionBar?.setDisplayHomeAsUpEnabled(true)
+
+ onBackPressedDispatcher.addCallback(this) {
+ finish()
+ }
+
+ toolbar.setNavigationOnClickListener {
+ onNavigationClickListener?.invoke() ?: onBackPressedDispatcher.onBackPressed()
+ }
+ }
+
+ fun setToolbarTitle(title: String) {
+ supportActionBar?.title = title
+ }
+
+ fun setNavigationClickListener(listener: () -> Unit) {
+ onNavigationClickListener = listener
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/com/cylonid/nativealpha/fragments/adblocklist/AdblockListAdapter.kt b/app/src/main/kotlin/com/cylonid/nativealpha/fragments/adblocklist/AdblockListAdapter.kt
new file mode 100644
index 00000000..6dc147b8
--- /dev/null
+++ b/app/src/main/kotlin/com/cylonid/nativealpha/fragments/adblocklist/AdblockListAdapter.kt
@@ -0,0 +1,39 @@
+package com.cylonid.nativealpha.fragments.adblocklist
+
+import android.app.Activity
+
+import android.view.View
+import android.widget.TextView
+import androidx.annotation.StringRes
+import com.cylonid.nativealpha.R
+import com.cylonid.nativealpha.model.AdblockConfig
+import com.cylonid.nativealpha.model.DataManager
+import com.ernestoyaquello.dragdropswiperecyclerview.DragDropSwipeAdapter
+
+class AdblockListAdapter(dataSet: List)
+ : DragDropSwipeAdapter(dataSet) {
+
+ class ViewHolder(webAppLayout: View) : DragDropSwipeAdapter.ViewHolder(webAppLayout) {
+
+ val titleView: TextView = itemView.findViewById(R.id.adblock_title)
+ val subtitleView: TextView = itemView.findViewById(R.id.adblock_subtitle)
+ }
+
+ override fun getViewHolder(itemView: View) = ViewHolder(itemView)
+ override fun onBindViewHolder(item: AdblockConfig, viewHolder: ViewHolder, position: Int) {
+ viewHolder.titleView.text = item.label
+ viewHolder.subtitleView.text = item.value
+
+ }
+
+ override fun canBeDragged(item: AdblockConfig, viewHolder: ViewHolder, position: Int): Boolean {
+ return false
+ }
+
+ fun updateAdblockList() {
+ dataSet = DataManager.getInstance().settings.globalWebApp.adBlockSettings
+ }
+
+ override fun getViewToTouchToStartDraggingItem(item: AdblockConfig, viewHolder: ViewHolder, position: Int) = viewHolder.titleView
+
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/com/cylonid/nativealpha/fragments/adblocklist/AdblockListFragment.kt b/app/src/main/kotlin/com/cylonid/nativealpha/fragments/adblocklist/AdblockListFragment.kt
new file mode 100644
index 00000000..63044169
--- /dev/null
+++ b/app/src/main/kotlin/com/cylonid/nativealpha/fragments/adblocklist/AdblockListFragment.kt
@@ -0,0 +1,89 @@
+package com.cylonid.nativealpha.fragments.adblocklist
+
+import android.os.Bundle
+import android.view.View
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentActivity
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.cylonid.nativealpha.R
+import com.cylonid.nativealpha.model.AdblockConfig
+import com.cylonid.nativealpha.model.DataManager
+import com.ernestoyaquello.dragdropswiperecyclerview.DragDropSwipeRecyclerView
+import com.ernestoyaquello.dragdropswiperecyclerview.listener.OnItemSwipeListener
+import com.google.android.material.floatingactionbutton.FloatingActionButton
+import com.google.android.material.snackbar.Snackbar
+
+class AdblockListFragment : Fragment(R.layout.fragment_adblock_list) {
+ private lateinit var adapter: AdblockListAdapter
+ private lateinit var list: DragDropSwipeRecyclerView
+
+ private var fab: FloatingActionButton? = null
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ val globalWebApp = DataManager.getInstance().settings.globalWebApp
+ adapter = AdblockListAdapter(globalWebApp.adBlockSettings)
+
+ fab = requireActivity().findViewById(R.id.adblock_fab)
+ checkFabEnabledStateIfNecessary()
+
+ list = view.findViewById(R.id.adblock_list)
+ list.layoutManager = LinearLayoutManager(requiredActivity())
+ list.adapter = adapter
+ list.swipeListener = onItemSwipeListener
+ list.orientation =
+ DragDropSwipeRecyclerView.ListOrientation.VERTICAL_LIST_WITH_VERTICAL_DRAGGING
+ list.disableSwipeDirection(DragDropSwipeRecyclerView.ListOrientation.DirectionFlag.RIGHT)
+
+ }
+
+
+ fun updateAdblockList() {
+ adapter.updateAdblockList()
+ checkFabEnabledStateIfNecessary()
+ }
+
+ private fun checkFabEnabledStateIfNecessary() {
+ if(fab != null && adapter.itemCount >= 8) {
+ fab?.isEnabled = false
+ } else {
+ fab?.isEnabled = true
+ }
+ }
+
+ private fun requiredActivity(): FragmentActivity {
+ return requireNotNull(activity) { "AdblockListFragment is not attached to an activity." }
+ }
+
+ private val onItemSwipeListener = object : OnItemSwipeListener {
+
+ override fun onItemSwiped(
+ position: Int,
+ direction: OnItemSwipeListener.SwipeDirection,
+ item: AdblockConfig
+ ): Boolean {
+
+ DataManager.getInstance().apply {
+ settings.globalWebApp.adBlockSettings.removeAt(position)
+ saveGlobalSettings()
+ }
+ updateAdblockList()
+ checkFabEnabledStateIfNecessary()
+
+ val itemSwipedSnackBar =
+ view?.let { Snackbar.make(it, getString(R.string.x_was_removed, item.label), Snackbar.LENGTH_SHORT) }
+ itemSwipedSnackBar?.setAction(getString(R.string.undo).uppercase()) {
+ DataManager.getInstance().apply {
+ settings.globalWebApp.adBlockSettings.add(position, item)
+ saveGlobalSettings()
+ }
+ updateAdblockList()
+ }
+ itemSwipedSnackBar?.show()
+
+ return true
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/com/cylonid/nativealpha/fragments/webapplist/WebAppListAdapter.kt b/app/src/main/kotlin/com/cylonid/nativealpha/fragments/webapplist/WebAppListAdapter.kt
new file mode 100644
index 00000000..e257a52d
--- /dev/null
+++ b/app/src/main/kotlin/com/cylonid/nativealpha/fragments/webapplist/WebAppListAdapter.kt
@@ -0,0 +1,55 @@
+package com.cylonid.nativealpha.fragments.webapplist
+
+import android.app.Activity
+import android.content.DialogInterface
+import android.content.Intent
+import android.transition.Visibility
+import android.view.View
+import android.widget.ImageButton
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.annotation.StringRes
+import androidx.appcompat.app.AlertDialog
+import androidx.core.content.ContextCompat.startActivity
+import com.cylonid.nativealpha.R
+import com.cylonid.nativealpha.WebAppSettingsActivity
+import com.cylonid.nativealpha.model.DataManager
+import com.cylonid.nativealpha.model.WebApp
+import com.cylonid.nativealpha.util.Const
+import com.cylonid.nativealpha.util.WebViewLauncher.startWebView
+import com.ernestoyaquello.dragdropswiperecyclerview.DragDropSwipeAdapter
+import com.google.android.material.internal.VisibilityAwareImageButton
+import java.util.ArrayList
+
+class WebAppListAdapter(dataSet: List = emptyList(), private val activityOfFragment: Activity)
+ : DragDropSwipeAdapter(dataSet) {
+
+ class ViewHolder(webAppLayout: View) : DragDropSwipeAdapter.ViewHolder(webAppLayout) {
+ val dragAnchor : ImageView = itemView.findViewById(R.id.dragAnchor)
+ val titleView: TextView = itemView.findViewById(R.id.btnWebAppTitle)
+
+ }
+
+ override fun getViewHolder(itemView: View) = ViewHolder(itemView)
+ override fun onBindViewHolder(item: WebApp, viewHolder: ViewHolder, position: Int) {
+ viewHolder.titleView.text = item.title
+ viewHolder.titleView.setOnClickListener {
+ openWebView(
+ item
+ )
+ }
+ }
+
+ fun updateWebAppList() {
+ dataSet = DataManager.getInstance().activeWebsites
+ }
+
+
+ private fun openWebView(webapp: WebApp) {
+ startWebView(webapp, activityOfFragment)
+ }
+
+
+ override fun getViewToTouchToStartDraggingItem(item: WebApp, viewHolder: ViewHolder, position: Int) = viewHolder.dragAnchor
+
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/com/cylonid/nativealpha/fragments/webapplist/WebAppListFragment.kt b/app/src/main/kotlin/com/cylonid/nativealpha/fragments/webapplist/WebAppListFragment.kt
new file mode 100644
index 00000000..f2b74f6d
--- /dev/null
+++ b/app/src/main/kotlin/com/cylonid/nativealpha/fragments/webapplist/WebAppListFragment.kt
@@ -0,0 +1,104 @@
+package com.cylonid.nativealpha.fragments.webapplist
+
+import android.content.DialogInterface
+import android.content.Intent
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import android.view.View
+import androidx.appcompat.app.AlertDialog
+import androidx.core.content.ContextCompat
+import androidx.fragment.app.FragmentActivity
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.cylonid.nativealpha.R
+import com.cylonid.nativealpha.WebAppSettingsActivity
+import com.cylonid.nativealpha.model.AdblockConfig
+import com.cylonid.nativealpha.model.DataManager
+import com.cylonid.nativealpha.model.WebApp
+import com.cylonid.nativealpha.util.Const
+import com.ernestoyaquello.dragdropswiperecyclerview.DragDropSwipeRecyclerView
+import com.ernestoyaquello.dragdropswiperecyclerview.listener.OnItemDragListener
+import com.ernestoyaquello.dragdropswiperecyclerview.listener.OnItemSwipeListener
+import com.google.android.material.snackbar.Snackbar
+
+class WebAppListFragment : Fragment(R.layout.fragment_web_app_list) {
+ private lateinit var adapter: WebAppListAdapter
+
+ private lateinit var list: DragDropSwipeRecyclerView
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ adapter = WebAppListAdapter(DataManager.getInstance().activeWebsites, requiredActivity())
+
+ list = view.findViewById(R.id.web_app_list)
+ list.layoutManager = LinearLayoutManager(requiredActivity())
+ list.adapter = adapter
+ list.orientation = DragDropSwipeRecyclerView.ListOrientation.VERTICAL_LIST_WITH_VERTICAL_DRAGGING
+ list.dragListener = onItemDragListener
+ list.swipeListener = onItemSwipeListener
+ }
+
+ fun updateWebAppList() {
+ adapter.updateWebAppList()
+ }
+
+ private fun requiredActivity(): FragmentActivity {
+ return requireNotNull(activity) { "WebAppListFragment is not attached to an activity." }
+ }
+
+ private val onItemSwipeListener = object : OnItemSwipeListener {
+ override fun onItemSwiped(
+ position: Int,
+ direction: OnItemSwipeListener.SwipeDirection,
+ item: WebApp
+ ): Boolean {
+ if(direction == OnItemSwipeListener.SwipeDirection.RIGHT_TO_LEFT) {
+ item.markInactive(requiredActivity())
+ saveCurrentDisplayedOrderOfWebAppsToDisk()
+
+ val itemSwipedSnackBar =
+ view?.let { Snackbar.make(it, getString(R.string.x_was_removed, item.title), Snackbar.LENGTH_SHORT) }
+ itemSwipedSnackBar?.setAction(getString(R.string.undo).uppercase()) {
+ item.isActiveEntry = true
+ DataManager.getInstance().saveWebAppData()
+ updateWebAppList()
+ }
+ itemSwipedSnackBar?.show()
+ }
+ if(direction == OnItemSwipeListener.SwipeDirection.LEFT_TO_RIGHT) {
+ val intent = Intent(
+ activity,
+ WebAppSettingsActivity::class.java
+ )
+ intent.putExtra(Const.INTENT_WEBAPPID, item.ID)
+ intent.setAction(Intent.ACTION_VIEW)
+ context?.let { ContextCompat.startActivity(it, intent, null) }
+ return true
+ }
+ return false
+ }
+ }
+
+ private val onItemDragListener = object : OnItemDragListener {
+
+ override fun onItemDropped(initialPosition: Int, finalPosition: Int, item: WebApp) {
+ saveCurrentDisplayedOrderOfWebAppsToDisk()
+
+ }
+
+ override fun onItemDragged(previousPosition: Int, newPosition: Int, item: WebApp) {
+ }
+ }
+
+ private fun saveCurrentDisplayedOrderOfWebAppsToDisk() {
+ for ((i, webapp) in adapter.dataSet.withIndex()) {
+ // Do not use "i" as index here, since adapter.dataSet includes only active website.
+ // The DataManager's websites array contains both active and inactive websites.
+ DataManager.getInstance().websites[webapp.ID].order = i
+ }
+ DataManager.getInstance().saveWebAppData()
+ }
+ companion object {
+ fun newInstance() = WebAppListFragment()
+ }
+}
diff --git a/app/src/main/kotlin/com/cylonid/nativealpha/helper/AdblockLifecycleHelper.kt b/app/src/main/kotlin/com/cylonid/nativealpha/helper/AdblockLifecycleHelper.kt
new file mode 100644
index 00000000..5d52ea2c
--- /dev/null
+++ b/app/src/main/kotlin/com/cylonid/nativealpha/helper/AdblockLifecycleHelper.kt
@@ -0,0 +1,51 @@
+package com.cylonid.nativealpha.helper
+
+
+import android.app.Activity
+import android.content.DialogInterface
+import androidx.appcompat.app.AlertDialog
+import com.cylonid.nativealpha.R
+import com.cylonid.nativealpha.model.DataManager
+
+class AdblockLifecycleHelper(private val activity: Activity) {
+
+ fun trySyncOperation(callback: AdblockLifecycleCallback) {
+ beforeAdblockOperation(callback)
+ afterAdblockOperation()
+ }
+
+ fun beforeAdblockOperation(callback: AdblockLifecycleCallback) {
+ if(DataManager.getInstance().hasAdblockCrashed) {
+ showWarningDialog()
+ DataManager.getInstance().hasAdblockCrashed = false
+ return
+ }
+ DataManager.getInstance().hasAdblockCrashed = true
+ callback.execute()
+ }
+
+ fun afterAdblockOperation() {
+ DataManager.getInstance().hasAdblockCrashed = false
+ }
+
+ private fun showWarningDialog() {
+ AlertDialog.Builder(activity)
+ .setMessage(activity.getString(R.string.adblock_warning_text))
+ .setCancelable(false)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setTitle(activity.getString(R.string.warning))
+ .setPositiveButton(activity.getString(R.string.ok)) { _: DialogInterface?, _: Int ->
+ DataManager.getInstance().apply {
+ settings.globalWebApp.adBlockSettings.clear()
+ saveGlobalSettings()
+ }
+ }
+ .setNegativeButton(activity.getString(R.string.cancel)) { _: DialogInterface?, _: Int -> }
+ .create().show()
+ }
+
+ fun interface AdblockLifecycleCallback {
+ fun execute()
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/com/cylonid/nativealpha/helper/AdblockProviderApiHelper.kt b/app/src/main/kotlin/com/cylonid/nativealpha/helper/AdblockProviderApiHelper.kt
new file mode 100644
index 00000000..9dfdc449
--- /dev/null
+++ b/app/src/main/kotlin/com/cylonid/nativealpha/helper/AdblockProviderApiHelper.kt
@@ -0,0 +1,37 @@
+package com.cylonid.nativealpha.helper
+
+import com.cylonid.nativealpha.model.AdblockConfig
+import com.cylonid.nativealpha.util.DateUtils
+import io.github.edsuns.adfilter.AdFilter
+import io.github.edsuns.adfilter.Filter
+
+internal class AdblockProviderApiHelper(private val adFilterProvider: AdFilter) {
+
+ fun synchronizeAdblockProviderWithSettings(settings: List) {
+ val map = transformToMapWithUrlKey(adFilterProvider.viewModel.filters.value ?: emptyMap())
+ for (config: AdblockConfig in settings) {
+ var setFilter = map[config.value]
+ if (setFilter == null) {
+ setFilter = adFilterProvider.viewModel.addFilter(config.label, config.value)
+ adFilterProvider.viewModel.download(setFilter.id)
+ }
+ if (DateUtils.isOlderThanDays(setFilter.updateTime, 10)) {
+ adFilterProvider.viewModel.download(setFilter.id)
+ }
+ }
+ for ((_, filter) in map) {
+ val existingConfig = settings.find { it.value == filter.url }
+ if(existingConfig == null) {
+ adFilterProvider.viewModel.removeFilter(filter.id)
+ }
+ }
+ }
+
+ private fun transformToMapWithUrlKey(originalMap: Map): Map {
+ val urlBasedMap: HashMap = HashMap()
+ for ((_, value) in originalMap) {
+ urlBasedMap[value.url] = value
+ }
+ return urlBasedMap
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/com/cylonid/nativealpha/helper/BiometricPromptHelper.kt b/app/src/main/kotlin/com/cylonid/nativealpha/helper/BiometricPromptHelper.kt
index 4b6fed6d..cd766466 100644
--- a/app/src/main/kotlin/com/cylonid/nativealpha/helper/BiometricPromptHelper.kt
+++ b/app/src/main/kotlin/com/cylonid/nativealpha/helper/BiometricPromptHelper.kt
@@ -7,6 +7,7 @@ import androidx.fragment.app.FragmentActivity
import androidx.core.content.ContextCompat
import androidx.biometric.BiometricPrompt.PromptInfo
import com.cylonid.nativealpha.R
+import com.cylonid.nativealpha.util.NotificationUtils
import com.cylonid.nativealpha.util.Utility
import com.google.android.material.snackbar.Snackbar
@@ -49,10 +50,10 @@ internal class BiometricPromptHelper(private val activity: FragmentActivity) {
isSupported = true
}
BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> {
- Utility.showInfoSnackbar(activity as AppCompatActivity?, activity.getString(R.string.no_biometric_keys_enrolled), Snackbar.LENGTH_LONG);
+ NotificationUtils.showInfoSnackbar(activity, activity.getString(R.string.no_biometric_keys_enrolled), Snackbar.LENGTH_LONG);
}
else -> {
- Utility.showInfoSnackbar(activity as AppCompatActivity?, activity.getString(R.string.no_biometric_devices), Snackbar.LENGTH_LONG);
+ NotificationUtils.showInfoSnackbar(activity, activity.getString(R.string.no_biometric_devices), Snackbar.LENGTH_LONG);
}
}
return isSupported
diff --git a/app/src/main/kotlin/com/cylonid/nativealpha/helper/IconPopupMenuHelper.kt b/app/src/main/kotlin/com/cylonid/nativealpha/helper/IconPopupMenuHelper.kt
index e07bf966..c4a488ee 100644
--- a/app/src/main/kotlin/com/cylonid/nativealpha/helper/IconPopupMenuHelper.kt
+++ b/app/src/main/kotlin/com/cylonid/nativealpha/helper/IconPopupMenuHelper.kt
@@ -1,6 +1,7 @@
package com.cylonid.nativealpha.helper
import android.content.Context
+import android.os.Build
import android.view.Gravity
import android.view.View
import android.widget.PopupMenu
@@ -11,7 +12,9 @@ object IconPopupMenuHelper {
@JvmStatic
fun getMenu(v: View, @MenuRes menuRes: Int, c: Context): PopupMenu {
val popup = PopupMenu(c, v, Gravity.END)
- popup.setForceShowIcon(true)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ popup.setForceShowIcon(true)
+ }
popup.menuInflater.inflate(menuRes, popup.menu)
return popup
diff --git a/app/src/main/kotlin/com/cylonid/nativealpha/model/WebApp.kt b/app/src/main/kotlin/com/cylonid/nativealpha/model/WebApp.kt
index d43684c6..5e9072e2 100644
--- a/app/src/main/kotlin/com/cylonid/nativealpha/model/WebApp.kt
+++ b/app/src/main/kotlin/com/cylonid/nativealpha/model/WebApp.kt
@@ -1,18 +1,21 @@
package com.cylonid.nativealpha.model
+import android.app.Activity
import android.view.View
import android.widget.*
+import androidx.appcompat.widget.SwitchCompat
import androidx.fragment.app.FragmentActivity
import com.cylonid.nativealpha.R
import com.cylonid.nativealpha.WebAppSettingsActivity
import com.cylonid.nativealpha.helper.BiometricPromptHelper
import com.cylonid.nativealpha.util.Const
+import com.cylonid.nativealpha.util.ShortcutIconUtils
import com.cylonid.nativealpha.util.Utility
import java.util.*
-class WebApp {
- val ID: Int
- var baseUrl: String
+data class AdblockConfig(val label: String, val value: String)
+
+data class WebApp(var baseUrl: String, val ID: Int) {
var title: String
var isActiveEntry = true
var isOverrideGlobalSettings = true
@@ -51,24 +54,33 @@ class WebApp {
var isEnableZooming = false
var isBiometricProtection = false
var isAllowMediaPlaybackInBackground = false
+ var order = 0
+ var alwaysUseFallbackContextMenu = false
+ var adBlockSettings = mutableListOf()
- constructor(url: String, id: Int) {
- title = url.replace("http://", "").replace("https://", "").replace("www.", "")
- baseUrl = url
- this.ID = id
+ init {
+ title = baseUrl.replace("http://", "").replace("https://", "").replace("www.", "")
initDefaultSettings()
}
- constructor(other: WebApp) {
+ constructor(baseUrl: String, ID: Int, order: Int): this(baseUrl, ID) {
+ this.order = order
+ }
+
+ constructor(baseUrl: String, ID: Int, adBlockSettings: MutableList): this(baseUrl, ID) {
+ this.adBlockSettings = adBlockSettings
+ }
+
+ constructor(other: WebApp) : this(other.baseUrl, other.ID) {
title = other.title
- ID = other.ID
- baseUrl = other.baseUrl
isOverrideGlobalSettings = other.isOverrideGlobalSettings
containerId = other.containerId
isUseContainer = other.isUseContainer
copySettings(other)
}
+
+
//This part of the copy ctor should be callable independently from actual object construction to copy values of the global web app template
fun copySettings(other: WebApp) {
isOpenUrlExternal = other.isOpenUrlExternal
@@ -104,6 +116,9 @@ class WebApp {
isEnableZooming = other.isEnableZooming
isBiometricProtection = other.isBiometricProtection
isAllowMediaPlaybackInBackground = other.isAllowMediaPlaybackInBackground
+ order = other.order
+ alwaysUseFallbackContextMenu = other.alwaysUseFallbackContextMenu
+ adBlockSettings = other.adBlockSettings
}
private fun initDefaultSettings() {
@@ -121,9 +136,12 @@ class WebApp {
isOverrideGlobalSettings = false
}
- fun markInactive() {
+ fun markInactive(activity: Activity) {
isActiveEntry = false
- Utility.deleteShortcuts(Arrays.asList(ID))
+ ShortcutIconUtils.deleteShortcuts(
+ listOf(ID),
+ activity
+ )
}
@@ -131,18 +149,18 @@ class WebApp {
get() = baseUrl.replace("\\P{Alnum}".toRegex(), "").replace("https", "").replace("http", "").replace("www", "")
fun onSwitchCookiesChanged(mSwitch: CompoundButton, isChecked: Boolean) {
- val switchThirdPCookies = mSwitch.rootView.findViewById(R.id.switch3PCookies)
+ val switchThirdPCookies = mSwitch.rootView.findViewById(R.id.switch3PCookies)
if (isChecked) switchThirdPCookies.isEnabled = true else {
switchThirdPCookies.isEnabled = false
switchThirdPCookies.isChecked = false
}
}
- private fun disableSwitchBiometricAccessChangeListener(switchBiometricAccess: Switch) {
+ private fun disableSwitchBiometricAccessChangeListener(switchBiometricAccess: SwitchCompat) {
switchBiometricAccess.setOnCheckedChangeListener(null)
}
- private fun enableSwitchBiometricAccessChangeListener(switchBiometricAccess: Switch,
+ private fun enableSwitchBiometricAccessChangeListener(switchBiometricAccess: SwitchCompat,
activity: WebAppSettingsActivity) {
switchBiometricAccess.setOnCheckedChangeListener { switch, checked ->
onSwitchBiometricAccessChanged(
@@ -154,7 +172,7 @@ class WebApp {
}
private fun setSwitchBiometricAccessSilently(newValue: Boolean,
- switchBiometricAccess: Switch,
+ switchBiometricAccess: SwitchCompat,
activity: WebAppSettingsActivity) {
disableSwitchBiometricAccessChangeListener(switchBiometricAccess)
switchBiometricAccess.isChecked = newValue
@@ -167,7 +185,7 @@ class WebApp {
activity: WebAppSettingsActivity
) {
val switchBiometricAccess =
- mSwitch.rootView.findViewById(R.id.switchBiometricAccess)
+ mSwitch.rootView.findViewById(R.id.switchBiometricAccess)
// reset to value before user toggled, actual setting of value is done by prompt success callback
setSwitchBiometricAccessSilently(!switchBiometricAccess.isChecked, switchBiometricAccess, activity)
@@ -191,8 +209,8 @@ class WebApp {
}
fun onSwitchJsChanged(mSwitch: CompoundButton, isChecked: Boolean) {
- val switchDesktopVersion = mSwitch.rootView.findViewById(R.id.switchDesktopSite)
- val switchAdblock = mSwitch.rootView.findViewById(R.id.switchAdblock)
+ val switchDesktopVersion = mSwitch.rootView.findViewById(R.id.switchDesktopSite)
+ val switchAdblock = mSwitch.rootView.findViewById(R.id.switchAdblock)
if (isChecked) {
switchDesktopVersion.isEnabled = true
switchAdblock.isEnabled = true
@@ -205,7 +223,7 @@ class WebApp {
}
fun onSwitchForceDarkChanged(mSwitch: CompoundButton, isChecked: Boolean) {
- val switchLimit = mSwitch.rootView.findViewById(R.id.switchTimeSpanDarkMode)
+ val switchLimit = mSwitch.rootView.findViewById(R.id.switchTimeSpanDarkMode)
val txtBegin = mSwitch.rootView.findViewById(R.id.textDarkModeBegin)
val txtEnd = mSwitch.rootView.findViewById(R.id.textDarkModeEnd)
if (isChecked) {
@@ -240,7 +258,7 @@ class WebApp {
fun onSwitchUserAgentChanged(mSwitch: CompoundButton, isChecked: Boolean) {
val txt = mSwitch.rootView.findViewById(R.id.textUserAgent)
- val switchDesktopVersion = mSwitch.rootView.findViewById(R.id.switchDesktopSite)
+ val switchDesktopVersion = mSwitch.rootView.findViewById(R.id.switchDesktopSite)
if (isChecked) {
switchDesktopVersion.isChecked = false
switchDesktopVersion.isEnabled = false
diff --git a/app/src/main/kotlin/com/cylonid/nativealpha/model/WebAppInstanceCreator.kt b/app/src/main/kotlin/com/cylonid/nativealpha/model/WebAppInstanceCreator.kt
deleted file mode 100644
index 7db5849a..00000000
--- a/app/src/main/kotlin/com/cylonid/nativealpha/model/WebAppInstanceCreator.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.cylonid.nativealpha.model
-
-import com.google.gson.InstanceCreator
-import java.lang.reflect.Type
-
-class WebAppInstanceCreator : InstanceCreator {
- override fun createInstance(type: Type): WebApp {
- return WebApp("", Int.MAX_VALUE)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/kotlin/com/cylonid/nativealpha/model/deserializer/GlobalSettingsDeserializer.kt b/app/src/main/kotlin/com/cylonid/nativealpha/model/deserializer/GlobalSettingsDeserializer.kt
new file mode 100644
index 00000000..c2194cfc
--- /dev/null
+++ b/app/src/main/kotlin/com/cylonid/nativealpha/model/deserializer/GlobalSettingsDeserializer.kt
@@ -0,0 +1,33 @@
+package com.cylonid.nativealpha.model.deserializer
+
+import android.util.Log
+import com.cylonid.nativealpha.model.GlobalSettings
+import com.cylonid.nativealpha.model.WebApp
+import com.cylonid.nativealpha.util.Const
+import com.google.gson.Gson
+import com.google.gson.JsonDeserializationContext
+import com.google.gson.JsonDeserializer
+import com.google.gson.JsonElement
+import java.lang.NullPointerException
+import java.lang.reflect.Type
+
+class GlobalSettingsDeserializer : JsonDeserializer {
+ override fun deserialize(
+ json: JsonElement,
+ typeOfT: Type,
+ context: JsonDeserializationContext
+ ): GlobalSettings {
+ try {
+ val obj = json.asJsonObject
+ val globalWebApp =
+ context.deserialize(obj.get("globalWebApp"), WebApp::class.java)
+ val settings = Gson().fromJson(obj, GlobalSettings::class.java)
+ settings.globalWebApp = globalWebApp
+
+ return settings
+ }
+ catch(e: NullPointerException) {
+ return GlobalSettings()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/com/cylonid/nativealpha/model/deserializer/WebAppDeserializer.kt b/app/src/main/kotlin/com/cylonid/nativealpha/model/deserializer/WebAppDeserializer.kt
new file mode 100644
index 00000000..a939d2ab
--- /dev/null
+++ b/app/src/main/kotlin/com/cylonid/nativealpha/model/deserializer/WebAppDeserializer.kt
@@ -0,0 +1,56 @@
+package com.cylonid.nativealpha.model.deserializer
+
+import com.cylonid.nativealpha.model.AdblockConfig
+import com.cylonid.nativealpha.model.WebApp
+import com.cylonid.nativealpha.util.Const
+import com.google.gson.Gson
+import com.google.gson.JsonDeserializationContext
+import com.google.gson.JsonDeserializer
+import com.google.gson.JsonElement
+import com.google.gson.JsonObject
+import java.lang.reflect.Type
+
+class WebAppDeserializer : JsonDeserializer {
+ override fun deserialize(
+ json: JsonElement,
+ typeOfT: Type,
+ context: JsonDeserializationContext
+ ): WebApp {
+ val obj = json.asJsonObject
+ val webapp = Gson().fromJson(obj, WebApp::class.java)
+ patchDataVersion1500(webapp, obj)
+ return webapp
+ }
+
+ /**
+ * With release v1.5.0 (code 1500), we added the array "adBlockSettings" to WebApp.
+ If the value in JSON string is null, we set an empty array for usual web apps and the default adblock provider for the global web app.
+ */
+ private fun patchDataVersion1500(webapp: WebApp, obj: JsonObject) {
+ var adblockKeyPresent = false
+
+ val parsedAdblockSettings =
+ if (obj.has("adBlockSettings") && obj.get("adBlockSettings").isJsonArray) {
+ val array = obj.getAsJsonArray("adBlockSettings")
+ adblockKeyPresent = true
+ array.mapNotNull { item ->
+ try {
+ val configObj = item.asJsonObject
+ val label = configObj.get("label")?.asString ?: return@mapNotNull null
+ val value = configObj.get("value")?.asString ?: return@mapNotNull null
+ AdblockConfig(label, value)
+ } catch (e: Exception) {
+ null // Skip malformed entries
+ }
+ }
+ } else {
+ emptyList()
+ }
+
+ webapp.adBlockSettings = parsedAdblockSettings.toMutableList()
+ if (webapp.ID == Int.MAX_VALUE && !adblockKeyPresent) {
+ webapp.adBlockSettings = Const.getDefaultAdBlockConfig()
+ }
+ webapp.adBlockSettings = webapp.adBlockSettings.take(8).toMutableList()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/com/cylonid/nativealpha/util/ColorUtils.kt b/app/src/main/kotlin/com/cylonid/nativealpha/util/ColorUtils.kt
new file mode 100644
index 00000000..a37bcf14
--- /dev/null
+++ b/app/src/main/kotlin/com/cylonid/nativealpha/util/ColorUtils.kt
@@ -0,0 +1,27 @@
+package com.cylonid.nativealpha.util
+
+import android.content.Context
+import android.util.TypedValue
+import androidx.annotation.AttrRes
+import androidx.annotation.ColorRes
+
+object ColorUtils {
+
+ @JvmStatic
+ @ColorRes
+ fun getColorResFromThemeAttr(context: Context, @AttrRes resId: Int, @ColorRes fallback: Int): Int {
+ val typedValue = TypedValue()
+ val theme = context.theme
+ var colorResId = fallback
+
+ val success = theme.resolveAttribute(
+ resId,
+ typedValue,
+ true
+ )
+ if (success) {
+ colorResId = typedValue.resourceId
+ }
+ return colorResId
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/com/cylonid/nativealpha/util/DateUtils.kt b/app/src/main/kotlin/com/cylonid/nativealpha/util/DateUtils.kt
new file mode 100644
index 00000000..224d646c
--- /dev/null
+++ b/app/src/main/kotlin/com/cylonid/nativealpha/util/DateUtils.kt
@@ -0,0 +1,65 @@
+package com.cylonid.nativealpha.util
+
+import android.annotation.SuppressLint
+import java.text.SimpleDateFormat
+import java.util.Calendar
+
+import java.util.Locale
+import java.util.Objects.requireNonNull
+
+object DateUtils {
+
+ @JvmStatic
+ fun getTimeInSeconds(): Long {
+ return System.currentTimeMillis() / 1000
+ }
+
+ @JvmStatic
+ @SuppressLint("SimpleDateFormat")
+ fun getHourMinFormat(): SimpleDateFormat {
+ return SimpleDateFormat("HH:mm", Locale.getDefault())
+ }
+
+ @JvmStatic
+ @SuppressLint("SimpleDateFormat")
+ fun getDayHourMinuteSecondsFormat(): SimpleDateFormat {
+ return SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z", Locale.getDefault())
+ }
+
+ @JvmStatic
+ fun convertStringToCalendar(str: String?): Calendar? {
+ if (str.isNullOrBlank()) return null
+ return try {
+ val parsedDate = getHourMinFormat().parse(str)
+ Calendar.getInstance().also { it.time = parsedDate!! }
+ } catch (e: Exception) {
+ null
+ }
+ }
+
+ @JvmStatic
+ fun isInInterval(low: Calendar, time: Calendar, high: Calendar): Boolean {
+ // Bring timestamp with day_current + HH:mm => day_unixZero + HH:mm by parsing it again...
+ val middle = Calendar.getInstance()
+ middle.time = requireNonNull(
+ getHourMinFormat().parse(
+ getHourMinFormat().format(time.time)
+ )
+ )
+
+ // CASE: If the end of our timespan is after midnight, add one day to the end date to get a proper span.
+ if (high.before(low)) {
+ high.add(Calendar.DATE, 1)
+ if (middle.before(low)) {
+ middle.add(Calendar.DATE, 1)
+ }
+ }
+ return middle.after(low) && middle.before(high)
+ }
+
+ @JvmStatic
+ fun isOlderThanDays(timestamp: Long, days: Int, targetTime: Long = System.currentTimeMillis()): Boolean {
+ val daysInMillis = days * 24L * 60 * 60 * 1000
+ return (targetTime - timestamp) > daysInMillis
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/com/cylonid/nativealpha/util/EntryPointUtils.kt b/app/src/main/kotlin/com/cylonid/nativealpha/util/EntryPointUtils.kt
index b14926fb..760cb7db 100644
--- a/app/src/main/kotlin/com/cylonid/nativealpha/util/EntryPointUtils.kt
+++ b/app/src/main/kotlin/com/cylonid/nativealpha/util/EntryPointUtils.kt
@@ -9,7 +9,7 @@ import com.cylonid.nativealpha.activities.NewsActivity
object EntryPointUtils {
@JvmStatic
fun entryPointReached(a: Activity) {
- if (DataManager.getInstance().lastShownUpdate != BuildConfig.VERSION_CODE) {
+ if (kotlin.math.abs(DataManager.getInstance().lastShownUpdate - BuildConfig.VERSION_CODE) > 50 ) {
a.startActivity(Intent(a, NewsActivity::class.java))
}
DataManager.getInstance().loadAppData()
diff --git a/app/src/main/kotlin/com/cylonid/nativealpha/util/NotificationUtils.kt b/app/src/main/kotlin/com/cylonid/nativealpha/util/NotificationUtils.kt
new file mode 100644
index 00000000..be04d834
--- /dev/null
+++ b/app/src/main/kotlin/com/cylonid/nativealpha/util/NotificationUtils.kt
@@ -0,0 +1,42 @@
+package com.cylonid.nativealpha.util
+
+import android.app.Activity
+import android.view.Gravity
+import android.widget.TextView
+import android.widget.Toast
+import com.cylonid.nativealpha.R
+import com.google.android.material.snackbar.Snackbar
+
+object NotificationUtils {
+
+ @JvmStatic
+ fun showToast(a: Activity, text: String) {
+ showToast(a, text, Toast.LENGTH_LONG)
+ }
+
+ @JvmStatic
+ fun showToast(a: Activity, text: String, toastDisplayDuration: Int) {
+ val toast = Toast.makeText(a, text, toastDisplayDuration)
+ toast.setGravity(Gravity.TOP, 0, 100)
+ toast.show()
+ }
+
+ @JvmStatic
+ fun showInfoSnackbar(activity: Activity, msg: String, duration: Int) {
+ val snackbar = Snackbar.make(
+ activity.findViewById(android.R.id.content),
+ msg, duration
+ )
+
+ snackbar.setAction(
+ activity.getString(R.string.ok)
+ ) { snackbar.dismiss() }
+
+
+ val tv = snackbar.view.findViewById(com.google.android.material.R.id.snackbar_text).findViewById(com.google.android.material.R.id.snackbar_text)
+ tv.maxLines = 10
+ snackbar.show()
+ }
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/com/cylonid/nativealpha/util/ProcessUtils.kt b/app/src/main/kotlin/com/cylonid/nativealpha/util/ProcessUtils.kt
new file mode 100644
index 00000000..7f5bdb7d
--- /dev/null
+++ b/app/src/main/kotlin/com/cylonid/nativealpha/util/ProcessUtils.kt
@@ -0,0 +1,28 @@
+package com.cylonid.nativealpha.util
+
+import android.app.ActivityManager
+import android.os.Process
+
+object ProcessUtils {
+ @JvmStatic
+ fun closeAllWebAppsAndProcesses(activityManager: ActivityManager) {
+ for (task in activityManager.appTasks) {
+ val id = task.taskInfo.baseIntent.getIntExtra(Const.INTENT_WEBAPPID, -1)
+ if (id != -1) task.finishAndRemoveTask()
+ }
+ for (processInfo in activityManager.runningAppProcesses) {
+ if (processInfo.processName.contains("web_sandbox")) {
+ Process.killProcess(processInfo.pid)
+ }
+ }
+ }
+
+
+ fun killWebSandbox(id: Int, activityManager: ActivityManager) {
+ for (processInfo in activityManager.runningAppProcesses) {
+ if (processInfo.processName.contains("web_sandbox_$id")) {
+ Process.killProcess(processInfo.pid)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/com/cylonid/nativealpha/util/ShortcutIconUtils.kt b/app/src/main/kotlin/com/cylonid/nativealpha/util/ShortcutIconUtils.kt
new file mode 100644
index 00000000..5264af79
--- /dev/null
+++ b/app/src/main/kotlin/com/cylonid/nativealpha/util/ShortcutIconUtils.kt
@@ -0,0 +1,37 @@
+package com.cylonid.nativealpha.util
+
+import android.content.Context
+import android.content.pm.ShortcutManager
+import com.cylonid.nativealpha.R
+
+
+object ShortcutIconUtils {
+ @JvmStatic
+ fun deleteShortcuts(removableWebAppIds: List, context: Context) {
+ val manager = context.getSystemService(
+ ShortcutManager::class.java
+ )
+ for (info in manager.pinnedShortcuts) {
+ val id = info.intent!!
+ .getIntExtra(Const.INTENT_WEBAPPID, -1)
+ if (removableWebAppIds.contains(id)) {
+ manager.disableShortcuts(
+ listOf(info.id),
+ context.getString(R.string.webapp_already_deleted)
+ )
+ }
+ }
+ }
+
+ @JvmStatic
+ fun getWidthFromIcon(sizeString: String): Int {
+ var xIndex = sizeString.indexOf("x")
+ if (xIndex == -1) xIndex = sizeString.indexOf("×")
+ if (xIndex == -1) xIndex = sizeString.indexOf("*")
+
+ if (xIndex == -1) return 1
+ val width = sizeString.substring(0, xIndex)
+
+ return width.toInt()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/baseline_drag_indicator_24.xml b/app/src/main/res/drawable/baseline_drag_indicator_24.xml
new file mode 100644
index 00000000..edd016e0
--- /dev/null
+++ b/app/src/main/res/drawable/baseline_drag_indicator_24.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/baseline_menu_open_24.xml b/app/src/main/res/drawable/baseline_menu_open_24.xml
new file mode 100644
index 00000000..c19daa90
--- /dev/null
+++ b/app/src/main/res/drawable/baseline_menu_open_24.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/launch_screen.xml b/app/src/main/res/drawable/launch_screen.xml
index 70e2b721..cacd1a67 100644
--- a/app/src/main/res/drawable/launch_screen.xml
+++ b/app/src/main/res/drawable/launch_screen.xml
@@ -1,7 +1,7 @@
-
+
diff --git a/app/src/main/res/drawable/liberapay_logo.xml b/app/src/main/res/drawable/liberapay_logo.xml
new file mode 100644
index 00000000..ec9df19c
--- /dev/null
+++ b/app/src/main/res/drawable/liberapay_logo.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/web_app_list_separator.xml b/app/src/main/res/drawable/web_app_list_separator.xml
new file mode 100644
index 00000000..b204c05e
--- /dev/null
+++ b/app/src/main/res/drawable/web_app_list_separator.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index f3aa3fbe..b04ab3e3 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -8,15 +8,15 @@
+ android:layout_height="wrap_content">
+ android:background="@color/colorSignature" />
diff --git a/app/src/main/res/layout/activity_toolbar_base.xml b/app/src/main/res/layout/activity_toolbar_base.xml
new file mode 100644
index 00000000..51af1390
--- /dev/null
+++ b/app/src/main/res/layout/activity_toolbar_base.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/adblock_config_activity.xml b/app/src/main/res/layout/adblock_config_activity.xml
new file mode 100644
index 00000000..977da4a4
--- /dev/null
+++ b/app/src/main/res/layout/adblock_config_activity.xml
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/adblock_list_item.xml b/app/src/main/res/layout/adblock_list_item.xml
new file mode 100644
index 00000000..7e84208b
--- /dev/null
+++ b/app/src/main/res/layout/adblock_list_item.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/add_adblock_config_dialog.xml b/app/src/main/res/layout/add_adblock_config_dialog.xml
new file mode 100644
index 00000000..b5ae003d
--- /dev/null
+++ b/app/src/main/res/layout/add_adblock_config_dialog.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/add_website_dialogue.xml b/app/src/main/res/layout/add_website_dialogue.xml
index 1fd380d6..8ef6ca85 100644
--- a/app/src/main/res/layout/add_website_dialogue.xml
+++ b/app/src/main/res/layout/add_website_dialogue.xml
@@ -1,6 +1,6 @@
@@ -9,20 +9,12 @@
android:inputType="textUri"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="16dp"
- android:layout_marginLeft="4dp"
- android:layout_marginRight="4dp"
- android:layout_marginBottom="4dp"
android:hint="@string/url" />
-
diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml
index e1c84812..97114a29 100644
--- a/app/src/main/res/layout/content_main.xml
+++ b/app/src/main/res/layout/content_main.xml
@@ -6,55 +6,11 @@
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:name="com.cylonid.nativealpha.fragments.webapplist.WebAppListFragment" />
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/context_menu_title.xml b/app/src/main/res/layout/context_menu_title.xml
index 6d2ff99b..31b7504b 100644
--- a/app/src/main/res/layout/context_menu_title.xml
+++ b/app/src/main/res/layout/context_menu_title.xml
@@ -10,11 +10,7 @@
android:layout_weight="1"
android:gravity="center"
android:text=""
- android:forceDarkAllowed="false"
- android:background="@android:color/black"
android:maxLines="1"
- android:textColor="@android:color/white"
android:ellipsize="end"
- android:padding="8dp"
- android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
+ android:padding="8dp" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_http_auth.xml b/app/src/main/res/layout/dialog_http_auth.xml
new file mode 100644
index 00000000..189e9a2a
--- /dev/null
+++ b/app/src/main/res/layout/dialog_http_auth.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_adblock_list.xml b/app/src/main/res/layout/fragment_adblock_list.xml
new file mode 100644
index 00000000..f256e2ca
--- /dev/null
+++ b/app/src/main/res/layout/fragment_adblock_list.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_web_app_list.xml b/app/src/main/res/layout/fragment_web_app_list.xml
new file mode 100644
index 00000000..f8b639cf
--- /dev/null
+++ b/app/src/main/res/layout/fragment_web_app_list.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/full_webview.xml b/app/src/main/res/layout/full_webview.xml
index 73593574..417ca403 100644
--- a/app/src/main/res/layout/full_webview.xml
+++ b/app/src/main/res/layout/full_webview.xml
@@ -15,10 +15,11 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="true"
+ android:forceDarkAllowed="false"
android:focusableInTouchMode="true"
tools:ignore="MissingConstraints" />
-
+
+ android:scrollbarStyle="outsideOverlay">
-
-
-
-
-
-
-
-
+ android:text="@string/adblock_config" />
-
-
-
-
-
+
+
+
@@ -175,6 +166,7 @@
diff --git a/app/src/main/res/layout/news_activity.xml b/app/src/main/res/layout/news_activity.xml
index 57a439d1..bfabaa85 100644
--- a/app/src/main/res/layout/news_activity.xml
+++ b/app/src/main/res/layout/news_activity.xml
@@ -4,8 +4,7 @@
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="@android:color/white"
- android:forceDarkAllowed="false"
+ android:layout_margin="@dimen/margin_activity"
android:id="@+id/news_main">
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/shortcut_dialog.xml b/app/src/main/res/layout/shortcut_dialog.xml
index 9748b42f..2571cd09 100644
--- a/app/src/main/res/layout/shortcut_dialog.xml
+++ b/app/src/main/res/layout/shortcut_dialog.xml
@@ -1,6 +1,6 @@
diff --git a/app/src/main/res/layout/toolbar_back_action.xml b/app/src/main/res/layout/toolbar_back_action.xml
new file mode 100644
index 00000000..84639b76
--- /dev/null
+++ b/app/src/main/res/layout/toolbar_back_action.xml
@@ -0,0 +1,11 @@
+
+
diff --git a/app/src/main/res/layout/web_app_list_item.xml b/app/src/main/res/layout/web_app_list_item.xml
new file mode 100644
index 00000000..03b3ef1f
--- /dev/null
+++ b/app/src/main/res/layout/web_app_list_item.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/webapp_settings.xml b/app/src/main/res/layout/webapp_settings.xml
index e0b9182d..ef1a7c58 100644
--- a/app/src/main/res/layout/webapp_settings.xml
+++ b/app/src/main/res/layout/webapp_settings.xml
@@ -25,13 +25,6 @@
android:orientation="vertical"
android:weightSum="100">
-
-
+ android:scrollbarStyle="outsideOverlay">
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:text="@string/webapp_interval_for_reload" />
-
-
-
-
-
-
+
+
-
-
+ android:backgroundTint="?attr/colorError" />
-
+
diff --git a/app/src/main/res/menu/wv_context_menu.xml b/app/src/main/res/menu/wv_context_menu.xml
index ddb41086..7db52ecc 100644
--- a/app/src/main/res/menu/wv_context_menu.xml
+++ b/app/src/main/res/menu/wv_context_menu.xml
@@ -2,6 +2,7 @@
@@ -12,35 +13,51 @@
+
+
-
-
+
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index f5b8ad21..995b26fa 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -113,7 +113,7 @@
Allgemeines
Backup
Google Safe Browsing aktivieren
- Alle Drittanbieter-Anfragen blockieren
+ Drittanbieter-Anfragen blockieren
Sandbox
Sandbox nicht verwenden.
Alle Sandboxen sind in Verwendung.
@@ -158,4 +158,23 @@
Web-App konnte nicht gestartet werden.
Bitte geben Sie eine URL an.
Bitte scrollen Sie bis zum Ende.
+ Download fehlgeschlagen.
+ Software-Navigationsbuttons immer anzeigen
+ Einmalig Standard-Kontextmenü nutzen
+ Standard-Kontextmenü nutzen
+ Adblocker-Einstellungen
+ Die hier angelegten Einträge dienen als Quellen für den Werbeblocker. Die Blockregeln werden von der angegebenen URL heruntergeladen und regelmäßig aktualisiert. Bitte beachten Sie, dass zu viele hinzugefügte Quellen sich negativ auf die Performance auswirken können. Sie können bis zu 8 Einträge erstellen.
+ Neuen Adblock-Anbieter hinzufügen
+ Sind Sie sicher, dass Sie diesen Eintrag entfernen möchten?
+ Rückgängig
+ %1$s wurde entfernt.
+ Standard wiederherstellen
+ Anmelden (HTTP Auth)
+ Benutzername
+ Passwort
+ Geben Sie die Zugangsdaten für %1$s bei %2$s ein:
+ Dieser Eintrag existiert bereits.
+ Unterstütze mich auf Liberapay
+ Ihre aktuellen Adblocker-Einstellungen führen möglicherweise zu Problemen oder Abstürzen. Leider funktionieren nicht alle Filterlisten auf allen Geräten.\n\nEs wird empfohlen, die Filterlisten zu leeren und testweise neu zu befüllen.
+ Warnung
\ No newline at end of file
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
new file mode 100644
index 00000000..a2f7b7a5
--- /dev/null
+++ b/app/src/main/res/values-es/strings.xml
@@ -0,0 +1,185 @@
+
+ Native Alpha
+ Configuraciones
+
+ Modo de interfaz
+ Ajustes generales
+ Estas configuraciones son utilizadas por todas las Aplicaciones Web, excepto aquellas que anulan explícitamente las configuraciones generales.
+ Eliminar cookies después del uso
+ Utiliza el deslizamiento de tres dedos ⇄ para cambiar entre las Aplicaciones Web (experimental)
+ Seleccione el modo de interfaz
+ Guardar
+ Cargando el icono y el título del sitio web...
+ Abrir enlaces externos en la aplicación del navegador
+ Permitir Javascript
+ Acceptar cookies
+ Acceptar cookies de terceros
+ Eliminar el cache después del uso
+ Activar el bloqueo de anuncios
+ Tiempo de espera en min.
+ Volver a crear el acceso directo
+ Crear un acceso directo en la pantalla de inicio
+ URL
+ Utilice el deslizamiento de dos dedos ⇄ para la navegación hacia delante y hacia atrás del navegador
+
+ - Por defecto del sistema
+ - Modo Claro
+ - Modo Oscuro
+
+ No hemos podido recuperar un icono para %1$s.\nPosibles razones:\n
+ • No hay conexión a Internet\n
+ • El sitio web no tiene un icono adecuado almacenado.
+ ¿Está seguro de que quiere eliminar este sitio web?
+ Por favor, introduzca una dirección web válida.
+ Editar la configuración de la aplicación web
+ Añadir aplicación web
+ Bienvenido.\nAñade tu primera aplicación web:
+ Restaurar la última página abierta
+ Solicitar el sitio web en versión de escritorio
+ Icono de acceso directo
+ Título de acceso directo
+
+ Acuerdo de licencia de usuario final
+ El software se suministra "tal cual", sin garantía de ningún tipo, expresa o implícita, incluidas, pero sin limitarse a ellas, las garantías de comerciabilidad, idoneidad para un fin determinado y no infracción. En ningún caso los autores o los titulares de los derechos de autor serán responsables de ninguna reclamación, daño u otra responsabilidad, ya sea en una acción contractual, extracontractual o de otro tipo, que surja de, o esté relacionada con el software o el uso u otras operaciones con el software.
+ Mostrar las bibliotecas utilizadas
+ Este software se publica bajo la Licencia Pública General GNU - Versión 3.\nhttps://www.gnu.org/licenses/gpl-3.0.txt
+ Acerca de
+ Licencia
+ Bibliotecas de código abierto
+ Configuración de la aplicación web
+ No cargar imágenes
+ Enviar petición de \"Guardar datos\"
+ Esta página tiene un problema con su certificado SSL.
+ La autoridad de certificación no es de confianza.
+ El certificado ha caducado.
+ El certificado no coincide con el nombre del host.
+ El certificado aún no es válido.
+ No se recomienda cargar la página.
+ Advertencia: Posible amenaza a la seguridad
+ Cargar de todos modos
+ No hay cifrado HTTPS
+ Permitir HTTP en esta aplicación web
+ Esta página utiliza una conexión HTTP no cifrada. No introduzca ningún dato sensible.
+ Permitir HTTP
+ Seguridad & Privacidad
+ Cookies & Anuncios
+ Ahorro de datos
+ Varios
+ " ☕ Cómprame un café en PayPal"
+ Restaurar la página
+ La copia de seguridad no se ha podido restaurar.
+ Copia de seguridad con %1$d aplicación(es) web ha sido restaurada con éxito.
+ No se ha podido exportar la copia de seguridad.
+ La copia de seguridad se ha creado con éxito.
+ La aplicación web a la que intenta acceder ya no está disponible.
+ La copia de seguridad contenía
+ ¿Quieres volver a crear accesos directos para todas las aplicaciones web?
+ Tenga en cuenta que tendrá que confirmar manualmente la adición de cada acceso directo.
+ Por favor, instala un gestor de archivos.
+
+
+ Usa dos dedos ↓ para recargar la página
+ Permitir el acceso a la ubicación
+ Permitir contenidos DRM
+ Solicitud de permiso de ubicación
+ ¿Desea conceder a esta Aplicación Web acceso a su ubicación? Puedes revocar esta decisión en cualquier momento en el menú de configuración de la Aplicación Web.
+ Contenido DRM
+ ¿Quiere permitir que esta aplicación web cargue contenidos protegidos por DRM?
+ Si desea que las aplicaciones web puedan recuperar su ubicación, puede conceder a Native Alpha el permiso para acceder a la ubicación. Puedes decidir conceder el permiso para cada Aplicación Web individualmente.
+ Si quieres que las Aplicaciones Web de Native Alpha sean capaces de descargar archivos, tienes que aceptar la solicitud de permiso de almacenamiento.
+ El archivo se está descargando...
+ Descarga fallida.
+ Seleccione un icono personalizado
+ Mostrar la barra de progreso durante la carga de la página
+ Agente de usuario
+ Utilizar un agente de usuario personalizado
+ Inserte el agente de usuario personalizado aquí...
+ Recargar automáticamente la página
+ Intervalo en segundos
+ Actualizar la página regularmente
+ Consulta WhatIsMyBrowser.com]]> para una lista de agentes de usuario recientes.
+
+ Etiqueta
+ La importación de la copia de seguridad ha finalizado
+ Es necesario recargar la pantalla para aplicar el tema de la interfaz de usuario restaurado.
+ ¿Desea restaurar el acceso directo para %1$s]]>?
+ Forzar el modo oscuro
+ Final
+ Inicio
+ Limitar el modo oscuro a un intervalo de tiempo
+ Modo oscuro
+ Ajustes expertos
+ Mostrar la configuración de expertos
+ Errores SSL
+ Utilice esta opción sólo para los sitios web autoalojados que no sean de producción
+ Ignorar los errores de SSL
+ Esta aplicación web ya no está disponible. Puede eliminar este acceso directo.
+ Anular la configuración general
+ Configuración general de la aplicación web
+ General
+ Copia de seguridad
+ Utilizar la navegación segura de Google
+ Bloquear solicitudes de terceros
+ Sandbox
+ No usar la sandbox.
+ Todas las sandboxes están en uso actualmente.
+ Habilitar Sandbox
+ Pantalla completa (modo inmersivo)
+ No se ha podido cargar la página.
+ No se ha podido cargar el icono.
+ Política de privacidad
+ Modo quiosco
+ Mantener la pantalla despierta
+ Acceso a la cámara
+ ¿Desea conceder a esta Aplicación Web acceso a su cámara? Puedes revocar esta decisión en cualquier momento en el menú de configuración de la Aplicación Web.
+ Permitir el acceso a la cámara
+ Permitir el acceso al micrófono
+ Acceso al micrófono
+ ¿Desea conceder a esta Aplicación Web acceso a su micrófono? Puedes revocar esta decisión en cualquier momento en el menú de configuración de la Aplicación Web.
+ Native Alpha +
+ Ya existe un acceso directo con este nombre. Por favor, elija otro nombre o elimine el acceso directo existente.
+ Volver
+ Avanzar
+ Copiar URL
+ Compartir
+ Recargar
+ Activar la función de Zoom
+ Restricción de acceso
+ Restricción de acceso
+ ¿Desactivar la restricción de acceso?
+ Activar la restricción de acceso
+ ¿Permitir la restricción de acceso?
+ Permitir la reproducción de medios en segundo plano
+ URL inicial
+ Más
+ Cerrar Aplicación Web
+ Seleccionar texto
+ Ir al menú principal
+ Cancelar
+ De Acuerdo
+ Aceptar
+ Debes aceptar el EULA para continuar.
+ Su dispositivo no admite la autenticación biométrica.
+ Su dispositivo admite la autenticación biométrica, pero aún no ha registrado ningún método.
+ La Aplicación Web no pudo ser iniciada.
+ Por favor, introduzca una URL.
+ Por favor, desplácese hasta el final.
+ Mostrar siempre los botones de navegación del software
+ Utilizar el menú contextual estándar
+ Utilizar permanentemente el menú contextual estándar
+ Configuración de Adblock
+ Las entradas creadas aquí sirven como fuentes para el bloqueador de anuncios. Las reglas de bloqueo se descargan desde la URL especificada y se mantienen actualizadas. Tenga en cuenta que agregar demasiadas fuentes puede afectar negativamente el rendimiento. Puede crear hasta 8 entradas.
+ Añadir un nuevo proveedor de Adblock
+ ¿Está seguro de que desea eliminar este elemento?
+ Deshacer
+ Se ha eliminado %1$s.
+ Restaurar configuración predeterminada
+ Iniciar sesión (HTTP Auth)
+ Nombre de usuario
+ Contraseña
+ Introduce las credenciales para %1$s en %2$s:
+ Esta entrada ya existe.
+ Apóyame en Liberapay
+ La configuración actual del bloqueador de anuncios puede causar problemas o fallos. Lamentablemente, no todas las listas de filtros funcionan en todos los dispositivos.\n\nSe recomienda vaciar las listas de filtros y volver a llenarlas como prueba.
+ Advertencia
+
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
new file mode 100644
index 00000000..bf4e3bc0
--- /dev/null
+++ b/app/src/main/res/values-it/strings.xml
@@ -0,0 +1,181 @@
+
+
+ Native Alpha
+ Impostazioni
+
+ Modalità UI
+ Impostazioni generali
+ Queste impostazioni sono utilizzate da tutte le Web App, tranne quelle che sovrascrivono esplicitamente le impostazioni globali.
+ Cancella i cookie dopo l\'uso
+ Usa lo swipe con tre dita ⇄ per passare da una Web App all\'altra (sperimentale)
+ Seleziona la modalità UI
+ Salva
+ Caricamento icona e titolo del sito web…
+ Apri i collegamenti esterni nell\'applicazione del browser
+ Consenti Javascript
+ Accetta i cookies
+ Accetta cookies di terze parti
+ Cancella la cache dopo l\'utilizzo
+ Abilita blocco pubblicitario
+ Tempo minimo di attesa
+ Crea di nuovo scorciatoia
+ Crea una scorciatoia nella schermata iniziale
+ Usa lo swipe con due dita ⇄ per la navigazione in avanti e indietro del browser.
+
+ - Predefinito di sistema
+ - Tema Chiaro
+ - Tema Scuro
+
+ "Non è stato possibile trovare un'icona per %1$s. Possibili motivi: "
+ • Nessuna connessione di rete
+ • Il sito web non ha un\'icona adatta.
+ Sei sicuro di voler rimuovere questo sito?
+ Inserire un indirizzo web valido.
+ Modifica le impostazioni della Web App
+ Aggiungi Web App
+ Benvenuti!\nAggiungete la vostra prima Web App:
+ Ripristina l\'ultima pagina aperta
+ Richiedi il sito web in versione desktop
+ Icona Scorciatoia
+ Titolo della scorciatoia
+ Accordo di licenza con l\'utente finale
+ Il software viene fornito ”così com\'è“, senza alcun tipo di garanzia, esplicita o implicita, includendo, a titolo esemplificativo, le garanzie di commerciabilità, idoneità a uno scopo particolare e inviolabilità. In nessun caso gli autori o i detentori dei diritti d\'autore saranno responsabili per qualsiasi reclamo, danno o altra responsabilità, sia in un\'azione contrattuale, che in un\'azione illecita o altro, derivante da, in relazione o meno con il software o l\'uso o altri rapporti con il software.
+ Mostra le librerie utilizzate
+ Questo software è rilasciato sotto licenza GNU General Public License - Versione 3.\nhttps://www.gnu.org/licenses/gpl-3.0.txt
+ Info
+ Licenza
+ Librerie open source
+ Impostazioni Web App
+ Non caricare immagini
+ Invia la richiesta \"Salva dati\"
+ Questa pagina ha un problema con il suo certificato SSL.
+ L\'autorità di certificazione non è attendibile.
+ Il certificato è scaduto.
+ Il certificato non corrisponde al nome dell\'host.
+ Il certificato non è ancora valido.
+ Si sconsiglia di caricare la pagina.
+ Attenzione: Potenziale minaccia alla sicurezza
+ Carica comunque
+ Nessuna crittografia HTTPS
+ Consenti HTTP all\'interno di questa applicazione web
+ Questa pagina utilizza una connessione HTTP non criptata. Non inserire dati sensibili.
+ Consenti HTTP
+ Sicurezza & Privacy
+ Cookie & annunci
+ Risparmio dati
+ Varie
+ " ☕ Offrimi un caffè su PayPal"
+ Ripristina la pagina
+ Backup non ripristinabile.
+ Il backup di %1$d Web App è stato ripristinato con successo.
+ Backup non esportabile
+ Backup creato correttamente.
+ L\'applicazione Web che si sta cercando di aprire non è più disponibile.
+ Il backup contiene
+ Volete ricreare i collegamenti per tutte le Web App?
+ Notare che è necessario confermare manualmente l\'aggiunta di ogni collegamento.
+ Installate un gestore di file.
+
+
+ Usa due dita ↓ per ricaricare la pagina
+ Consenti l\'accesso alla posizione
+ Consenti i contenuti DRM
+ Richiesta di autorizzazione alla localizzazione
+ Si desidera concedere a questa Web App l\'accesso alla propria posizione? È possibile revocare questa decisione in qualsiasi momento nel menu Impostazioni della Web App.
+ Contenuto DRM
+ Si desidera consentire a questa Web App di caricare contenuti protetti da DRM?
+ Se si desidera che le Web App siano in grado di recuperare la propria posizione, è possibile concedere a Native Alpha l\'autorizzazione ad accedere alla posizione. Si può decidere di concedere l\'autorizzazione per ogni Web App individualmente.
+ Se si desidera che le Web App Native Alpha siano in grado di scaricare i file, è necessario accettare la richiesta di autorizzazione all\'archiviazione.
+ Scaricamento file in corso…
+ Seleziona un\'icona personalizzata
+ Mostra barra di avanzamento durante il caricamento della pagina
+ User Agent
+ User Agent personalizzato
+ Inserisci uno User Agent personalizzato
+ Ricarica automaticamente la pagina
+ Intervallo in secondi
+ Aggiornare regolarmente la pagina
+ Consultare WhatIsMyBrowser.com]]> per un elenco di user agent recenti.
+ Etichetta
+ Importazione backup terminata
+ È necessario ricaricare la schermata per applicare il tema ripristinato della UI.
+ Si vuole ripristinare il collegamento per %1$s]]>?
+ Forza Tema scuro
+ Fine
+ Inizio
+ Limita il tema scuro all\'intervallo di tempo
+ Tema Scuro
+ Impostazioni Avanzate
+ Mostra impostazioni avanzate
+ Errori SSL
+ Utilizza questa opzione solo per i siti web non di produzione ospitati autonomamente.
+ Ignorare errori SSL
+ Questa Web App non è più disponibile. È possibile eliminare questo collegamento.
+ Sovrascrivi impostazioni globali
+ Impostazioni Globali Web App
+ Generale
+ Backup
+ Usa Navigazione Sicura Google
+ Blocca le richieste di terze parti
+ Sandbox
+ Non usare la sandbox
+ Tutte le sandbox sono attualmente in uso.
+ Abilita Sandbox
+ Schermo intero (modalità immersiva)
+ La pagina non può essere caricata.
+ L\'icona non può essere caricata.
+ Informativa sulla privacy
+ Modalità Kiosk
+ Mantieni schermo acceso
+ Accesso alla fotocamera
+ Si desidera concedere a questa Web App l\'accesso alla fotocamera? È possibile revocare questa decisione in qualsiasi momento nel menu Impostazioni della Web App.
+ Consenti accesso alla fotocamera
+ Consenti accesso al microfono
+ Accesso al microfono
+ Si desidera concedere a questa Web App l\'accesso al proprio microfono? È possibile revocare questa decisione in qualsiasi momento nel menu Impostazioni della Web App.
+ Esiste già un collegamento con questo nome. Scegliere un altro nome o elimina il collegamento esistente.
+ Indietro
+ Avanti
+ Copia indirizzo
+ Condividi
+ Ricarica
+ Abilita ingrandimento con due dita
+ Restrizione di accesso
+ Restrizione di accesso
+ Disattivare la restrizione di accesso?
+ Abilita restrizione di accesso
+ Abilitare la restrizione di accesso?
+ Consentire la riproduzione dei media in sottofondo
+ Indirizzo iniziale
+ Altro
+ Chiudi Web App
+ Seleziona testo
+ Al menu principale
+ Cancella
+ OK
+ Accetta
+ Per continuare è necessario accettare l\'EULA.
+ Il dispositivo non supporta l\'autenticazione biometrica.
+ Il dispositivo supporta l\'autenticazione biometrica, ma non è stato ancora registrato alcun metodo.
+ Impossibile avviare la Web App.
+ Inserire un indirizzo
+ Scorri fino in fondo.
+ Mostra sempre i pulsanti di navigazione del software
+ Utilizzare temporaneamente il menu contestuale standard
+ Utilizzare il menu contestuale standard
+ Configurazione Adblock
+ Le voci create qui fungono da fonti per il blocco degli annunci. Le regole di blocco vengono scaricate dall\'URL specificato e aggiornate regolarmente. Si prega di notare che l\'aggiunta di troppe fonti potrebbe influire negativamente sulle prestazioni. È possibile creare fino a 8 voci.
+ Aggiungi un nuovo provider Adblock
+ Sei sicuro di voler eliminare questo elemento?
+ Annulla
+ %1$s è stato rimosso.
+ Ripristina impostazioni predefinite
+ Accedi (HTTP Auth)
+ Nome utente
+ Password
+ Insericsci le credenziali per %1$s su %2$s:
+ Questa voce esiste già.
+ Sostienimi su Liberapay
+ Le impostazioni attuali dell\'ad blocker potrebbero causare problemi o arresti anomali. Purtroppo non tutte le liste di filtri funzionano su tutti i dispositivi.\n\nSi consiglia di svuotare le liste di filtri e riempirle nuovamente per testare.
+ Avviso
+
diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml
new file mode 100644
index 00000000..d2e19a25
--- /dev/null
+++ b/app/src/main/res/values-night/colors.xml
@@ -0,0 +1,143 @@
+
+ #F0BE6D
+ #432C00
+ #604100
+ #FFDEAC
+ #DBC3A1
+ #3D2E16
+ #55442A
+ #F8DFBB
+ #B5CEA4
+ #213618
+ #374C2C
+ #D1EABF
+ #FFB4AB
+ #690005
+ #93000A
+ #FFDAD6
+ #17130B
+ #ECE1D4
+ #17130B
+ #ECE1D4
+ #4E4539
+ #D2C4B4
+ #9B8F80
+ #4E4539
+ #000000
+ #ECE1D4
+ #362F27
+ #7D570D
+ #FFDEAC
+ #281900
+ #F0BE6D
+ #604100
+ #F8DFBB
+ #261904
+ #DBC3A1
+ #55442A
+ #D1EABF
+ #0D2005
+ #B5CEA4
+ #374C2C
+ #17130B
+ #3F382F
+ #120D07
+ #201B13
+ #241F17
+ #2F2921
+ #3A342B
+ #FFD697
+ #352200
+ #B5893E
+ #000000
+ #F2D9B5
+ #31230C
+ #A38E6E
+ #000000
+ #CAE4B9
+ #172A0E
+ #809871
+ #000000
+ #FFD2CC
+ #540003
+ #FF5449
+ #000000
+ #17130B
+ #ECE1D4
+ #17130B
+ #FFFFFF
+ #4E4539
+ #E8DAC9
+ #BDB0A0
+ #9A8F7F
+ #000000
+ #ECE1D4
+ #2F2921
+ #614200
+ #FFDEAC
+ #1A0F00
+ #F0BE6D
+ #4A3100
+ #F8DFBB
+ #1A0F00
+ #DBC3A1
+ #43341B
+ #D1EABF
+ #041500
+ #B5CEA4
+ #273B1D
+ #17130B
+ #4A433A
+ #0B0703
+ #221D15
+ #2D271F
+ #383229
+ #433D34
+ #FFEDD7
+ #000000
+ #ECBB6A
+ #130900
+ #FFEDD7
+ #000000
+ #D7BF9D
+ #130900
+ #DEF8CC
+ #000000
+ #B1CAA0
+ #020F00
+ #FFECE9
+ #000000
+ #FFAEA4
+ #220001
+ #17130B
+ #ECE1D4
+ #17130B
+ #FFFFFF
+ #4E4539
+ #FFFFFF
+ #FDEEDC
+ #CEC1B0
+ #000000
+ #ECE1D4
+ #000000
+ #614200
+ #FFDEAC
+ #000000
+ #F0BE6D
+ #1A0F00
+ #F8DFBB
+ #000000
+ #DBC3A1
+ #1A0F00
+ #D1EABF
+ #000000
+ #B5CEA4
+ #041500
+ #17130B
+ #564F45
+ #000000
+ #241F17
+ #362F27
+ #413A32
+ #4D463C
+
diff --git a/app/src/main/res/values-night/theme_overlays.xml b/app/src/main/res/values-night/theme_overlays.xml
new file mode 100644
index 00000000..02adac70
--- /dev/null
+++ b/app/src/main/res/values-night/theme_overlays.xml
@@ -0,0 +1,98 @@
+
+
+
+
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
new file mode 100644
index 00000000..4ab6251c
--- /dev/null
+++ b/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,53 @@
+
+
+
+
diff --git a/app/src/main/res/values-v29/styles.xml b/app/src/main/res/values-v29/styles.xml
deleted file mode 100644
index 38368274..00000000
--- a/app/src/main/res/values-v29/styles.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index ab2c785a..1cbe270a 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -1,15 +1,157 @@
-
-
- #b71c1c
- #7f0000
- #373737
+
+ #904A43
+ #FFFFFF
+ #FFDAD6
+ #73332D
+ #775652
+ #FFFFFF
+ #FFDAD6
+ #5D3F3C
+ #715B2E
+ #FFFFFF
+ #FDDFA6
+ #584419
+ #BA1A1A
+ #FFFFFF
+ #FFDAD6
+ #93000A
+ #FFF8F7
+ #231918
+ #FFF8F7
+ #231918
+ #F5DDDA
+ #534341
+ #857371
+ #D8C2BF
+ #000000
+ #392E2D
+ #FFEDEA
+ #FFB4AB
+ #FFDAD6
+ #3B0907
+ #FFB4AB
+ #73332D
+ #FFDAD6
+ #2C1513
+ #E7BDB8
+ #5D3F3C
+ #FDDFA6
+ #261900
+ #E0C38C
+ #584419
+ #E8D6D4
+ #FFF8F7
+ #FFFFFF
+ #FFF0EE
+ #FCEAE7
+ #F6E4E2
+ #F1DEDC
+ #5E231E
+ #FFFFFF
+ #A25851
+ #FFFFFF
+ #4B2F2C
+ #FFFFFF
+ #876561
+ #FFFFFF
+ #453309
+ #FFFFFF
+ #816A3B
+ #FFFFFF
+ #740006
+ #FFFFFF
+ #CF2C27
+ #FFFFFF
+ #FFF8F7
+ #231918
+ #FFF8F7
+ #180F0E
+ #F5DDDA
+ #413331
+ #5F4F4D
+ #7B6967
+ #000000
+ #392E2D
+ #FFEDEA
+ #FFB4AB
+ #A25851
+ #FFFFFF
+ #84413A
+ #FFFFFF
+ #876561
+ #FFFFFF
+ #6D4D49
+ #FFFFFF
+ #816A3B
+ #FFFFFF
+ #675225
+ #FFFFFF
+ #D4C3C0
+ #FFF8F7
+ #FFFFFF
+ #FFF0EE
+ #F6E4E2
+ #EBD9D7
+ #DFCECB
+ #511A15
+ #FFFFFF
+ #763630
+ #FFFFFF
+ #3F2522
+ #FFFFFF
+ #60423E
+ #FFFFFF
+ #3A2902
+ #FFFFFF
+ #5A461B
+ #FFFFFF
+ #600004
+ #FFFFFF
+ #98000A
+ #FFFFFF
+ #FFF8F7
+ #231918
+ #FFF8F7
+ #000000
+ #F5DDDA
+ #000000
+ #362927
+ #554544
+ #000000
+ #392E2D
+ #FFFFFF
+ #FFB4AB
+ #763630
+ #FFFFFF
+ #59201B
+ #FFFFFF
+ #60423E
+ #FFFFFF
+ #472C28
+ #FFFFFF
+ #5A461B
+ #FFFFFF
+ #413006
+ #FFFFFF
+ #C6B5B3
+ #FFF8F7
+ #FFFFFF
+ #FFEDEA
+ #F1DEDC
+ #E2D0CE
+ #D4C3C0
+
+ #b71c1c
+ #7f0000
- #b71c1c
#07000000
#CCCCCC
#10181818
#aaa
+ #8A8A8A
-
+ #4DFF53
+ #EF6C00
+ #FD2C2C
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 308f5c1c..f76fc7e7 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -6,4 +6,6 @@
20dp
10dp
-10dp
+ 20dp
+ 10dp
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 48917e0f..a06750fb 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -88,6 +88,7 @@
If you want Web Apps to be able to retrieve your location, you can Native Alpha grant the permission to access the location. You can decide to grant the permission for every Web App individually.
If you want Native Alpha Web Apps to be able to download files, you have to accept the storage permission request.
File is being downloaded…
+ Could not download file.
Select custom icon
Show progress bar during page load
User Agent
@@ -118,7 +119,7 @@
General
Backup
Use Google Safe Browsing
- Block all third-party requests
+ Block third-party requests
Sandbox
Do not use sandbox.
All sandboxes are currently in use.
@@ -163,6 +164,23 @@
Web App could not be started.
Please enter an URL.
Please scroll to the bottom.
-
+ Always show software navigation buttons
+ Temporarily use standard context menu
+ Use standard context menu
+ Adblock settings
+ The entries created here serve as sources for the ad blocker. The blocking rules are downloaded from the specified URL and kept up to date. Please note that adding too many sources may negatively impact performance. You can create up to 8 entries.
+ Add a new adblock provider
+ Are you sure you want to delete this item?
+ Undo
+ %1$s was removed.
+ Restore default
+ Log in (HTTP Auth)
+ User name
+ Password
+ Enter credentials for %1$s at %2$s:
+ This entry already exists.
+ Support me on Liberapay
+ Your current ad blocker settings may cause problems or crashes. Unfortunately, not all filter lists work on every device.\n\nIt is recommended to clear the filter lists and repopulate them as a test.
+ Warning
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
deleted file mode 100644
index e8a4c732..00000000
--- a/app/src/main/res/values/styles.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/values/theme_overlays.xml b/app/src/main/res/values/theme_overlays.xml
new file mode 100644
index 00000000..e81f18fb
--- /dev/null
+++ b/app/src/main/res/values/theme_overlays.xml
@@ -0,0 +1,98 @@
+
+
+
+
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
new file mode 100644
index 00000000..0699d2cd
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/test/java/com/cylonid/nativealpha/DateUtilsTest.kt b/app/src/test/java/com/cylonid/nativealpha/DateUtilsTest.kt
new file mode 100644
index 00000000..e759bedc
--- /dev/null
+++ b/app/src/test/java/com/cylonid/nativealpha/DateUtilsTest.kt
@@ -0,0 +1,96 @@
+package com.cylonid.nativealpha
+import com.cylonid.nativealpha.util.DateUtils
+
+import java.text.SimpleDateFormat
+import java.util.*
+import org.junit.Test
+import org.junit.Assert.*
+
+class DateUtilsTest {
+
+ @Test
+ fun `getTimeInSeconds returns current time in seconds`() {
+ val expected = System.currentTimeMillis() / 1000
+ val actual = DateUtils.getTimeInSeconds()
+ assertTrue(kotlin.math.abs(expected - actual) < 2)
+ }
+
+ @Test
+ fun `getHourMinFormat returns correct format`() {
+ val format = DateUtils.getHourMinFormat()
+ val calendar = Calendar.getInstance().also {
+ it.set(Calendar.HOUR_OF_DAY, 9)
+ it.set(Calendar.MINUTE, 15)
+ }
+ assertEquals("09:15", format.format(calendar.time))
+ }
+
+ @Test
+ fun `getDayHourMinuteSecondsFormat returns correct format`() {
+ val format = DateUtils.getDayHourMinuteSecondsFormat()
+ val legacyFormat = SimpleDateFormat("dd-MM-yyyy HH:mm", Locale.getDefault())
+ val parsedDate = legacyFormat.parse("08-04-2025 14:30")
+ val formatted = format.format(parsedDate!!)
+ assertTrue(formatted.contains("2025"))
+ }
+
+ @Test
+ fun `convertStringToCalendar returns correct calendar object`() {
+ val calendar = DateUtils.convertStringToCalendar("08:45")
+ assertNotNull(calendar)
+ assertEquals(8, calendar?.get(Calendar.HOUR_OF_DAY))
+ assertEquals(45, calendar?.get(Calendar.MINUTE))
+ }
+
+ @Test
+ fun `convertStringToCalendar returns null on invalid input`() {
+ val calendar = DateUtils.convertStringToCalendar("invalid")
+ assertNull(calendar)
+ }
+
+
+ @Test
+ fun `isInInterval returns true when time is within range`() {
+ val format = DateUtils.getHourMinFormat()
+
+ val low = Calendar.getInstance().also { it.time = format.parse("08:00")!! }
+ val time = Calendar.getInstance().also { it.time = format.parse("09:00")!! }
+ val high = Calendar.getInstance().also { it.time = format.parse("10:00")!! }
+
+ assertTrue(DateUtils.isInInterval(low, time, high))
+ }
+
+ @Test
+ fun `isInInterval returns false when time is outside range`() {
+ val format = DateUtils.getHourMinFormat()
+
+ val low = Calendar.getInstance().also { it.time = format.parse("08:00")!! }
+ val time = Calendar.getInstance().also { it.time = format.parse("11:00")!! }
+ val high = Calendar.getInstance().also { it.time = format.parse("10:00")!! }
+
+ assertFalse(DateUtils.isInInterval(low, time, high))
+ }
+
+ @Test
+ fun `isInInterval handles overnight intervals correctly`() {
+ val format = DateUtils.getHourMinFormat()
+
+ val low = Calendar.getInstance().also { it.time = format.parse("23:00")!! }
+ val time = Calendar.getInstance().also { it.time = format.parse("01:00")!! }
+ val high = Calendar.getInstance().also { it.time = format.parse("02:00")!! }
+
+ assertTrue(DateUtils.isInInterval(low, time, high))
+ }
+
+ @Test
+ fun `timestamp older than 14 days should return true`() {
+ val fourteenDaysAgo = System.currentTimeMillis() - (14L * 24 * 60 * 60 * 1000 + 1)
+ assertTrue(DateUtils.isOlderThanDays(fourteenDaysAgo, 14))
+ }
+
+ @Test
+ fun `timestamp less than 14 days ago should return false`() {
+ val thirteenDaysAgo = System.currentTimeMillis() - (13L * 24 * 60 * 60 * 1000)
+ assertFalse(DateUtils.isOlderThanDays(thirteenDaysAgo, 14))
+ }
+}
\ No newline at end of file
diff --git a/app/src/test/java/com/cylonid/nativealpha/UtilUnitTests.java b/app/src/test/java/com/cylonid/nativealpha/UtilUnitTests.java
index ffd5da18..c1355bc1 100644
--- a/app/src/test/java/com/cylonid/nativealpha/UtilUnitTests.java
+++ b/app/src/test/java/com/cylonid/nativealpha/UtilUnitTests.java
@@ -1,8 +1,7 @@
package com.cylonid.nativealpha;
import com.cylonid.nativealpha.model.WebApp;
-import com.cylonid.nativealpha.util.Const;
-import com.cylonid.nativealpha.util.Utility;
+import com.cylonid.nativealpha.util.ShortcutIconUtils;
import org.junit.Test;
@@ -17,7 +16,7 @@ public class UtilUnitTests {
@Test
public void getWidthFromHTMLElementString() {
- assertEquals((Integer)192, Utility.getWidthFromIcon("192x192"));
+ assertEquals(192, ShortcutIconUtils.getWidthFromIcon("192x192"));
}
public void testShortcutHelper(String base_url, final String expected, final int result_index) {
WebApp webapp = new WebApp(base_url, Integer.MAX_VALUE);
@@ -28,33 +27,33 @@ public void testShortcutHelper(String base_url, final String expected, final int
@Test
public void faviconFromWebManifest() {
- testShortcutHelper("https://xda-developers.com", "https://www.xda-developers.com/android-chrome-512x512.png", Const.RESULT_IDX_FAVICON);
+ testShortcutHelper("https://xda-developers.com", "https://static0.xdaimages.com/assets/images/favicon-240x240.43161a66.png", IconFetchResult.FAVICON.index);
}
@Test
public void faviconWithoutManifest() {
- testShortcutHelper("https://orf.at", "https://orf.at/mojo/1_4_1/storyserver//common/images/favicons/favicon-128x128.png", Const.RESULT_IDX_FAVICON);
+ testShortcutHelper("https://orf.at", "https://orf.at/mojo/1_4_1/storyserver//common/images/favicons/favicon-128x128.png", IconFetchResult.FAVICON.index);
}
@Test
public void faviconNull() {
- testShortcutHelper("https://tugraz.at", null, Const.RESULT_IDX_FAVICON);
+ testShortcutHelper("https://tugraz.at", null, IconFetchResult.FAVICON.index);
}
@Test
public void faviconNonExistingSite() {
- testShortcutHelper("https://asdfasdfasdfasdf.asdfsdaf", null, Const.RESULT_IDX_FAVICON);
+ testShortcutHelper("https://asdfasdfasdfasdf.asdfsdaf", null, IconFetchResult.FAVICON.index);
}
@Test
public void getStartUrlFromWebManifest() {
- testShortcutHelper("https://online.tugraz.at", "https://online.tugraz.at/tug_online/ee/ui/ca2/app/desktop/#/login", Const.RESULT_IDX_NEW_BASEURL);
+ testShortcutHelper("https://online.tugraz.at", "https://online.tugraz.at/tug_online/ee/ui/ca2/app/desktop/#/login?pwa=1", IconFetchResult.NEW_BASEURL.index);
}
@Test
public void getWebAppTitleFromManifest() {
- testShortcutHelper("https://online.tugraz.at", "TUGRAZonline Go", Const.RESULT_IDX_TITLE);
+ testShortcutHelper("https://online.tugraz.at", "TUGRAZonline Go", IconFetchResult.TITLE.index);
}
}
diff --git a/build.gradle b/build.gradle
index 1ff56861..dd8a51a6 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,19 +2,19 @@
buildscript {
ext {
- kotlinVersion = "1.7.0"
+ kotlinVersion = "2.1.20"
}
repositories {
google()
- jcenter()
+ mavenCentral()
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
- classpath 'com.android.tools.build:gradle:7.2.2'
+ classpath 'com.android.tools.build:gradle:8.8.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
- classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:10.0.0"
+ classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:11.6.3"
// NOTE: Do not place your application dependencies here; they belong
@@ -25,7 +25,7 @@ buildscript {
allprojects {
repositories {
google()
- jcenter()
+ mavenCentral()
maven { url 'https://jitpack.io' }
}
}
diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt
index 16979982..e4fc72d0 100644
--- a/fastlane/metadata/android/en-US/full_description.txt
+++ b/fastlane/metadata/android/en-US/full_description.txt
@@ -4,7 +4,7 @@
Offers to create home screen shortcuts and retrieves icons in suitable resolution.
arious settings (JavaScript, cookies, adblocking, location/camera/microphone access) can be set for every web app individually
Navigation with multi-touch gestures while browsing.
-Opt-in adblock using an AdBlock Plus custom webview.
+Opt-in adblock with user-selected filter lists.
Less memory footprint and no privacy-invading app permissions in comparison to native apps
Dark mode for Android 10+
diff --git a/fastlane/metadata/android/en-US/images/icon.png b/fastlane/metadata/android/en-US/images/icon.png
index 8e9a1057..f986e2df 100644
Binary files a/fastlane/metadata/android/en-US/images/icon.png and b/fastlane/metadata/android/en-US/images/icon.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png
index 29f80c70..b253ce67 100644
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png
index f82982f7..0f9277bd 100644
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png
index 4d408e3f..ed345d48 100644
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png
index 55149b16..a5cd3ea2 100644
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png differ
diff --git a/gradle.properties b/gradle.properties
index 199d16ed..46309a32 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -17,4 +17,7 @@ org.gradle.jvmargs=-Xmx1536m
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
+android.defaults.buildfeatures.buildconfig=true
+android.nonTransitiveRClass=false
+android.nonFinalResIds=false
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 653564f6..0fdd28d9 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
diff --git a/graphics/screenshots/addWebApp.png b/graphics/screenshots/addWebApp.png
index 59b7f345..0f9277bd 100755
Binary files a/graphics/screenshots/addWebApp.png and b/graphics/screenshots/addWebApp.png differ
diff --git a/graphics/screenshots/globalSettings.png b/graphics/screenshots/globalSettings.png
index 64208d84..a5cd3ea2 100644
Binary files a/graphics/screenshots/globalSettings.png and b/graphics/screenshots/globalSettings.png differ
diff --git a/graphics/screenshots/mainScreen.png b/graphics/screenshots/mainScreen.png
index 460854c7..b253ce67 100644
Binary files a/graphics/screenshots/mainScreen.png and b/graphics/screenshots/mainScreen.png differ
diff --git a/graphics/screenshots/webAppSettings.png b/graphics/screenshots/webAppSettings.png
index 9ec838f6..ed345d48 100644
Binary files a/graphics/screenshots/webAppSettings.png and b/graphics/screenshots/webAppSettings.png differ