diff --git a/.gitignore b/.gitignore index bea9a2955..0e88695d1 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,4 @@ app/src/main/assets/jsengine.bundle.js node_modules/ yarn.lock app/google-services.json +app/cliqz-config.json diff --git a/Dockerfile b/Dockerfile index 9269d52b7..d533dd81b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:16.04 +FROM ubuntu:18.04 MAINTAINER Stefano Pacifici ENV DEBIAN_FRONTEND noninteractive @@ -61,7 +61,7 @@ ENV JAVA_HOME /usr/lib/jvm/java-8-openjdk-amd64/ ENV ANDROID_HOME /home/jenkins/android_home ENV GRADLE_USER_HOME /home/jenkins/gradle_home ENV NVM_DIR /home/jenkins/nvm -ENV NODE_VERSION 8.9.3 +ENV NODE_VERSION 9.11.2 USER jenkins @@ -74,18 +74,14 @@ RUN mkdir -p $ANDROID_HOME; \ rm -r sdktools.zip; \ (while (true); do echo y; sleep 2; done) | \ tools/bin/sdkmanager \ - "build-tools;26.0.2" \ - "platforms;android-23" \ - "platforms;android-27" \ + "build-tools;28.0.3" \ + "platforms;android-28" \ "platform-tools" \ "tools" \ - "platforms;android-25" \ "extras;google;m2repository" \ "extras;android;m2repository" \ "extras;google;google_play_services"; -ENV LD_LIBRARY_PATH "/home/jenkins/android_home/emulator/lib64/qt/lib" - # Install Node.JS SHELL ["/bin/bash", "-l", "-c"] RUN curl https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | bash && \ @@ -93,14 +89,23 @@ RUN curl https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | b ENV PATH "$NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH" -#Installation of 'yarn'; 'appium' & 'wd' for Integration Tests -RUN npm install --global \ - yarn \ - appium \ - wd +#Installation of Updated 'npm' +RUN npm install --global npm #Install Ruby and Fastlane +RUN for key in 409B6B1796C275462A1703113804BB82D39DC0E3 \ + 7D2BAF1CF37B13E2069D6956105BD0E739499BDB; do \ + for server in "hkp://keys.gnupg.net" \ + "hkp://p80.pool.sks-keyservers.net:80" \ + "pgp.mit.edu" \ + "hkp://keyserver.ubuntu.com:80"; do \ + gpg2 --keyserver "${server}" --recv-keys "${key}" || echo "Trying new server..."; \ + done; \ + done +RUN curl -sSL https://get.rvm.io | bash -s stable --ruby=2.4.3 --autolibs=read-fail && \ + source /home/jenkins/.rvm/scripts/rvm \ + rvm reload && \ + gem install fastlane --version 2.126.0 -RUN gpg2 --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 && \ - curl -sSL https://get.rvm.io | bash -s stable --ruby=2.4.1 --autolibs=read-fail -RUN gem install fastlane \ No newline at end of file +#Install AWS CLI +RUN pip install awscli --upgrade --user \ No newline at end of file diff --git a/Jenkinsfile.upload b/Jenkinsfile.upload index c2a0912e9..c4e0b4003 100644 --- a/Jenkinsfile.upload +++ b/Jenkinsfile.upload @@ -1,67 +1,77 @@ #!/bin/env groovy - +@Library(['cliqz-shared-library@v1.2']) _ +properties([ + parameters([ + choice(choices: ['lumen', 'cliqz'], defaultValue: 'lumen', description: 'Which Product ?', name: 'product') + ]) +]) node('master'){ def imageName = 'android-browser' - def branchName = "${BRANCH_NAME}" - stage('Checkout'){ checkout scm } - stage('Build docker image') { docker.build(imageName, '--build-arg UID=`id -u` --build-arg GID=`id -g` .') } - docker.image(imageName).inside() { - stage('Extension') { - sh ''' - set -x - set -e - yarn - npm run bundle - ''' - } - - withEnv(["GRADLE_USER_HOME=${pwd()}/gradle_home","BN=${branchName}"]) { - withCredentials([ - file(credentialsId: '263e59fb-e9de-4e51-962c-0237c6ee167b', variable: 'CERT_PATH'), - string(credentialsId: '60354bba-8ed0-4df9-8f8e-5be7454c1680', variable: 'CERT_PASS'), - file(credentialsId: '2939d2e1-dd9a-4097-adc2-430e3d67157a', variable: 'PLAY_STORE_CERT')]) { - sh 'echo $BN' - if ("${BN}".contains("-re")) { - stage('Compile') { - sh './gradlew clean assembleStandardFatRelease' - } - }else { + try { + stage('Extension') { + sh '''#!/bin/bash -l + set -x + set -e + npm ci + npm run bundle + ''' + } + withEnv([ + "GRADLE_USER_HOME=${pwd()}/gradle_home", + "BUILD_NUMBER=${BUILD_NUMBER}" + ]) { + withCredentials([ + file(credentialsId: '263e59fb-e9de-4e51-962c-0237c6ee167b', variable: 'CERT_PATH'), + string(credentialsId: '60354bba-8ed0-4df9-8f8e-5be7454c1680', variable: 'CERT_PASS'), + file(credentialsId: '2939d2e1-dd9a-4097-adc2-430e3d67157a', variable: 'PLAY_STORE_CERT'), + file(credentialsId: 'cliqz-config.json', variable: 'CLIQZ_CONFIG_JSON'), + file(credentialsId: '6006a534-9f2e-4ba8-93f9-f0f27c0713df', variable: 'GOOGLE_SERVICES')]) { stage('Compile and Upload') { - sh '''#!/bin/bash -l - set -x - set -e - fastlane android alpha - ''' + sh '''#!/bin/bash -l + set -x + set -e + cp "$CLIQZ_CONFIG_JSON" app/cliqz-config.json + cp "$GOOGLE_SERVICES" app/google-services.json + ''' + if(params.product == "cliqz") { + sh '''#!/bin/bash -l + set -x + set -e + export APP_PACKAGE="com.cliqz.browser" + fastlane android internal_cliqz + ''' + } + if(params.product == "lumen") { + sh '''#!/bin/bash -l + set -x + set -e + export APP_PACKAGE="com.cliqz.lumen" + fastlane android internal_lumen + ''' + } + } } } - } - } - - } - if ("${branchName}".contains("-re")) { - def id = "Cliqz_Browser_android"+"${branchName}".substring("${branchName}".indexOf("v"), "${branchName}".indexOf("-")) - stage('Upload') { - withCredentials([ - [ - $class : 'UsernamePasswordMultiBinding', - credentialsId : 'f1732e5f-3e84-47ad-9286-a5fa6657ec43', - passwordVariable: 'AWS_SECRET_ACCESS_KEY', - usernameVariable: 'AWS_ACCESS_KEY_ID', - ] - ]) { - def s3Path = 's3://repository.cliqz.com/dist/android/release/apk' - def apkPath = "app/build/outputs/apk/standardFat/release/app-standard-fat-release.apk" - - sh "aws s3 cp --acl public-read --acl bucket-owner-full-control ${apkPath} ${s3Path}/${id}.apk" - sh "aws s3 cp --acl public-read --acl bucket-owner-full-control ${s3Path}/${id}.apk ${s3Path}/latest.apk" + } finally { + stage('Upload Artifacts and Clean Up') { + archiveArtifacts allowEmptyArchive: true, artifacts: 'app/build/**/*.apk' + sh'''#!/bin/bash -l + set -x + set -e + rm -f app/cliqz-config.json + rm -f app/google-services.json + rm -rf app/build + rm -rf jsengine/* + rm -rf gradle_home/ + ''' } } } -} \ No newline at end of file +} diff --git a/README.md b/README.md index b02a266ee..49f51cdf7 100644 --- a/README.md +++ b/README.md @@ -78,12 +78,12 @@ To also develop code from `browser-core`, follow these steps: ``` 3. Now, build the extension, and direct the build output to the android browser's node_modules directory. Use the `fern serve` command means that the project will be rebuild if you make code changes: ```shell - CLIQZ_OUTPUT_PATH=/path/to/android-browser/node_modules/browser-core/build/ ./fern.js serve configs/react-native.json + CLIQZ_OUTPUT_PATH=/path/to/android-browser/node_modules/browser-core/build/ ./fern.js serve configs/cliqz-android.js ``` Now the dev server will see and load the updated files outputed from the fern build when you reload the code in the app. -### Degugging +### Debugging To work with react live reloading server, the Developer Support option has to be set on ReactInstanceManager in `/app/src/main/java/com/cliqz/jsengine/Engine.java`, diff --git a/android_utils/src/main/java/com/cliqz/utils/ResourcesUtils.java b/android_utils/src/main/java/com/cliqz/utils/ResourcesUtils.java deleted file mode 100644 index 0772f52d9..000000000 --- a/android_utils/src/main/java/com/cliqz/utils/ResourcesUtils.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.cliqz.utils; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.drawable.Drawable; -import android.os.Build; -import androidx.annotation.DrawableRes; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -/** - * @author Stefano Pacifici - * @date 2016/09/13 - */ -public final class ResourcesUtils { - - private ResourcesUtils() {} // No instances - - public static Drawable getDrawable(@NonNull Context context, @DrawableRes int drawable, - @Nullable Resources.Theme theme) { - final Resources resources = context.getResources(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - return resources.getDrawable(drawable, theme); - } else { - return resources.getDrawable(drawable); - } - } -} diff --git a/android_utils/src/main/java/com/cliqz/utils/SpannableUtils.java b/android_utils/src/main/java/com/cliqz/utils/SpannableUtils.java index 50c308992..2b515f8a1 100644 --- a/android_utils/src/main/java/com/cliqz/utils/SpannableUtils.java +++ b/android_utils/src/main/java/com/cliqz/utils/SpannableUtils.java @@ -3,9 +3,6 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.drawable.Drawable; -import androidx.annotation.NonNull; -import androidx.annotation.StringRes; -import androidx.core.content.ContextCompat; import android.text.Spannable; import android.text.SpannableString; import android.text.SpannableStringBuilder; @@ -13,6 +10,10 @@ import android.text.style.ImageSpan; import android.text.style.URLSpan; +import androidx.annotation.NonNull; +import androidx.annotation.StringRes; +import androidx.appcompat.content.res.AppCompatResources; + import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -25,7 +26,7 @@ public final class SpannableUtils { private static final Pattern MARKDOWN_PATTERN = - Pattern.compile("((!?)\\[(.*?)\\]\\((.*?)\\))|()"); + Pattern.compile("((!?)\\[(.*?)]\\((.*?)\\))|()"); private SpannableUtils() { } // No instances @@ -67,7 +68,8 @@ public static Spannable markdownStringToSpannable(@NonNull Context context, if (drawableId == 0) { drawable = null; } else { - drawable = ContextCompat.getDrawable(context, drawableId); + drawable = AppCompatResources.getDrawable(context, drawableId); + assert drawable != null; drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); } region = new SpannableRegion(matcher.start(), matcher.end(), @@ -110,7 +112,7 @@ public static Spannable markdownStringToSpannable(@NonNull Context context, prev = region.regionEnd; } if (prev < in.length()) { - builder.append(in.substring(prev, in.length())); + builder.append(in.substring(prev)); } return builder; } diff --git a/app/build.gradle b/app/build.gradle index 60128206f..6969edab6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,7 +5,6 @@ apply plugin: 'kotlin-kapt' apply plugin: 'com.github.triplet.play' apply plugin: 'com.getkeepsafe.dexcount' -apply from: '../scripts/CliqzConfig.gradle' apply from: 'cliqz.gradle' def useGoogleServices = System.getenv("DISABLE_GOOGLE_SERVICES") == null @@ -18,30 +17,16 @@ android { } defaultConfig { - versionCode 106 + versionCode 1 versionName "1.8.4" multiDexEnabled true buildConfigField 'String', 'LIGHTNING_VERSION_NAME', '"4.2.3.1"' buildConfigField "int", "MINIMUM_WEBVIEW_VERSION", "55" buildConfigField 'int', "VISIBLE_TOP_SITES", '4' - - def vpnApiKey = this.hasProperty("com.cliqz.lumen.vpnApiKey") ? - this.getProperty("com.cliqz.lumen.vpnApiKey") : System.getenv("VPN_API_KEY") - if (vpnApiKey) { - buildConfigField 'String', 'VPN_API_KEY', "\"${vpnApiKey}\"" - } else { - buildConfigField 'String', 'VPN_API_KEY', '""' - } - - def revenueCatApiKey = this.hasProperty("com.cliqz.lumen.revenueCatApiKey") ? - this.getProperty("com.cliqz.lumen.revenueCatApiKey") : System.getenv("REVENUECAT_API_KEY") - if (revenueCatApiKey) { - buildConfigField 'String', 'REVENUECAT_API_KEY', "\"${revenueCatApiKey}\"" - } else { - buildConfigField 'String', 'REVENUECAT_API_KEY', '""' - } + applicationId "com.cliqz.browser" + testApplicationId "com.cliqz.browser.test" testInstrumentationRunner "com.cliqz.browser.test.CustomTestRunner" vectorDrawables.useSupportLibrary = true } @@ -53,6 +38,9 @@ android { shrinkResources false proguardFiles 'proguard-project.txt' versionNameSuffix '-debug' + dexcount { + enabled false + } } release { @@ -121,17 +109,11 @@ android { enable true reset() include "x86", "armeabi-v7a", "arm64-v8a", "x86_64" - universalApk true + universalApk false } } - - productFlavors.properties.names.each { name -> - defaultConfig { - def upperName = name.toUpperCase() - buildConfigField 'String', "FLAVOR_${upperName}", "\"${name}\"" - buildConfigField 'boolean', "IS_${upperName}", "FLAVOR.equals(FLAVOR_${upperName})" - buildConfigField 'boolean', "IS_NOT_${upperName}", "!IS_${upperName}" - } + testOptions { + animationsDisabled = true } } @@ -157,15 +139,10 @@ configurations.all { details.useTarget group: details.requested.group, name: 'android-jsc-intl', version: 'r236355' } } + force "com.google.code.findbugs:jsr305:3.0.0" } } -configurations.all { - resolutionStrategy { - force "com.google.code.findbugs:jsr305:3.0.0" - } -} - dependencies { // support libraries @@ -248,7 +225,7 @@ dependencies { lumenImplementation 'com.revenuecat.purchases:purchases:2.1.2' // vpn module - lumenImplementation 'com.cliqz:com.cliqz.vpnlib:1.0.1' + lumenImplementation 'com.cliqz:com.cliqz.vpnlib:1.0.4' // Testing-only dependencies testImplementation 'junit:junit:4.12' @@ -260,6 +237,8 @@ dependencies { androidTestImplementation 'com.google.dexmaker:dexmaker:1.2' androidTestImplementation 'com.google.dexmaker:dexmaker-mockito:1.2' + // Sentry + implementation 'io.sentry:sentry-android:1.7.21' // Forcing version androidTestImplementation 'androidx.annotation:annotation:1.0.0' androidTestImplementation 'androidx.test:runner:1.1.0' @@ -271,24 +250,11 @@ dependencies { testImplementation 'org.apache.velocity:velocity:1.7' } -def abiCodes = ["armeabi-v7a": 2, "arm64-v8a": 4, "x86": 6, "x86_64": 8] -import com.android.build.OutputFile - -preBuild.doLast { - android.applicationVariants.each { variant -> - def apiVersion = variant.productFlavors.get(0).versionCode - // set the composite code - variant.outputs.each { output -> - def abiVersion = abiCodes.get(output.getFilter(OutputFile.ABI)) ?: 1 - def generatedVersionCode = android.defaultConfig.versionCode * 100 + apiVersion * 10 + abiVersion - output.versionCodeOverride = generatedVersionCode - } - } -} - play { track = 'alpha' } if (useGoogleServices) { apply plugin: 'com.google.gms.google-services' } + +apply plugin: com.cliqz.gradle.CliqzPlugin \ No newline at end of file diff --git a/app/cliqz-config.json b/app/cliqz-config.json deleted file mode 100644 index 3b5c18728..000000000 --- a/app/cliqz-config.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - ".default": { - "amazonApplicationARN": "arn:aws:sns:us-east-1:141047255820:app/GCM/CLIQZ_Browser_for_Android", - "amazonSnsTopics": [ "arn:aws:sns:us-east-1:141047255820:mobile_news" ], - "amazonAccountID": "141047255820", - "amazonIdentityPoolID": "us-east-1:68bb8256-a533-4b93-b744-423518ef0f02", - "amazonUnauthRoleARN": "arn:aws:iam::141047255820:role/Cognito_CLIQZ_browser_androidUnauth_Role", - "amazonAuthRoleARN": "arn:aws:iam::141047255820:role/Cognito_CLIQZ_browser_androidAuth_Role" - }, - "debug": { - "amazonSnsTopics": [ "arn:aws:sns:us-east-1:141047255820:mobile_news_staging" ] - }, - "o2": { - "amazonSnsTopics": ["arn:aws:sns:us-east-1:141047255820:o2_android_browser"] - }, - "lookback": { - "lookbackSdkToken": "HWiD4ErSbeNy9JcRg" - } -} diff --git a/app/src/androidTestCliqz/java/com/cliqz/browser/main/SettingsActivityTest.java b/app/src/androidTestCliqz/java/com/cliqz/browser/main/SettingsActivityTest.java index 3433ecff7..0df6fe77b 100644 --- a/app/src/androidTestCliqz/java/com/cliqz/browser/main/SettingsActivityTest.java +++ b/app/src/androidTestCliqz/java/com/cliqz/browser/main/SettingsActivityTest.java @@ -103,7 +103,7 @@ public void setAndConfirmSearchEngine(final String engine, final String url) { onView(withText(equalToIgnoringCase("Complementary search engine"))).perform(click()); onView(withText(equalToIgnoringCase(engine))).perform(click()); onView(withText(equalToIgnoringCase("OK"))).perform(click()); - onView(withText(equalToIgnoringCase("General"))).perform(pressBack()); + onView(withText(equalToIgnoringCase("Search results for"))).perform(pressBack()); onView(withText(equalToIgnoringCase("Settings"))).perform(pressBack()); onView(withId(R.id.title_bar)).perform(click()); onView(withId(R.id.search_edit_text)).perform(typeText(query), diff --git a/app/src/cliqz/AndroidManifest.xml b/app/src/cliqz/AndroidManifest.xml new file mode 100644 index 000000000..34f69a8a3 --- /dev/null +++ b/app/src/cliqz/AndroidManifest.xml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/app/src/cliqz/java/com/cliqz/browser/app/AppComponent.java b/app/src/cliqz/java/com/cliqz/browser/app/AppComponent.java new file mode 100644 index 000000000..6c6e9f898 --- /dev/null +++ b/app/src/cliqz/java/com/cliqz/browser/app/AppComponent.java @@ -0,0 +1,14 @@ +package com.cliqz.browser.app; + +import javax.inject.Singleton; + +import dagger.Component; + +/** + * @author Ravjit Uppal + */ +@Singleton +@Component(modules = {AppModule.class}) +public interface AppComponent extends BaseAppComponent { + +} \ No newline at end of file diff --git a/app/src/cliqz/java/com/cliqz/browser/app/BrowserApp.java b/app/src/cliqz/java/com/cliqz/browser/app/BrowserApp.java index 8d638fbd2..d6d4e13a0 100644 --- a/app/src/cliqz/java/com/cliqz/browser/app/BrowserApp.java +++ b/app/src/cliqz/java/com/cliqz/browser/app/BrowserApp.java @@ -4,5 +4,8 @@ public class BrowserApp extends BaseBrowserApp { @Override public void init() { + //intialize common libraries + super.init(); + //initialize flavour specific libraries below iff any } } \ No newline at end of file diff --git a/app/src/cliqz/java/com/cliqz/browser/controlcenter/ControlCenterHelper.java b/app/src/cliqz/java/com/cliqz/browser/controlcenter/ControlCenterHelper.java index 5e763add6..70bd8e3ca 100644 --- a/app/src/cliqz/java/com/cliqz/browser/controlcenter/ControlCenterHelper.java +++ b/app/src/cliqz/java/com/cliqz/browser/controlcenter/ControlCenterHelper.java @@ -1,5 +1,6 @@ package com.cliqz.browser.controlcenter; +import android.content.Context; import android.view.View; import androidx.annotation.NonNull; @@ -7,6 +8,7 @@ import com.cliqz.browser.app.AppComponent; import com.cliqz.browser.app.BrowserApp; +import com.cliqz.browser.main.FlavoredActivityComponent; import com.cliqz.browser.main.Messages; import com.cliqz.browser.telemetry.Telemetry; import com.cliqz.nove.Bus; @@ -35,9 +37,10 @@ public class ControlCenterHelper implements ControlCenterActions { @Inject Telemetry telemetry; - public ControlCenterHelper(@NonNull FragmentManager fragmentManager) { - final AppComponent component = BrowserApp.getAppComponent(); + public ControlCenterHelper(Context context, @NonNull FragmentManager fragmentManager) { this.mFragmentManager = fragmentManager; + final FlavoredActivityComponent component = context != null ? + BrowserApp.getActivityComponent(context) : null; if (component != null) { component.inject(this); } diff --git a/app/src/cliqz/java/com/cliqz/browser/main/FlavoredActivityComponent.java b/app/src/cliqz/java/com/cliqz/browser/main/FlavoredActivityComponent.java index 43e2e2d96..561103300 100644 --- a/app/src/cliqz/java/com/cliqz/browser/main/FlavoredActivityComponent.java +++ b/app/src/cliqz/java/com/cliqz/browser/main/FlavoredActivityComponent.java @@ -3,7 +3,6 @@ import com.cliqz.browser.annotations.PerActivity; import com.cliqz.browser.controlcenter.ControlCenterDialog; import com.cliqz.browser.main.search.Freshtab; -import com.cliqz.browser.main.search.Incognito; import dagger.Subcomponent; diff --git a/app/src/cliqz/java/com/cliqz/browser/main/HistoryFragment.java b/app/src/cliqz/java/com/cliqz/browser/main/HistoryFragment.java index dc48f01f4..4ed7879dc 100644 --- a/app/src/cliqz/java/com/cliqz/browser/main/HistoryFragment.java +++ b/app/src/cliqz/java/com/cliqz/browser/main/HistoryFragment.java @@ -2,24 +2,28 @@ import android.database.Cursor; import android.os.Bundle; -import androidx.annotation.Nullable; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.ItemTouchHelper; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; -import android.widget.TextView; + +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import com.cliqz.browser.R; +import com.cliqz.browser.app.BrowserApp; import com.cliqz.browser.overview.OverviewFragment; import com.cliqz.browser.overview.OverviewTabsEnum; -import com.cliqz.browser.webview.CliqzMessages; +import com.cliqz.browser.main.HistoryAdapter; +import com.cliqz.browser.starttab.HistoryItemTouchHelper; import com.cliqz.nove.Subscribe; +import org.jetbrains.annotations.NotNull; + import java.util.ArrayList; -import java.util.Collections; +import java.util.Objects; import butterknife.BindView; import butterknife.ButterKnife; @@ -32,9 +36,8 @@ public class HistoryFragment extends FragmentWithBus { private boolean isMultiSelect; private HistoryAdapter adapter; - private final ArrayList historyList = new ArrayList<>(); private View contextualToolBar; - private TextView contextualToolBarTitle; + // private TextView contextualToolBarTitle; @BindView(R.id.history_rview) RecyclerView historyListView; @@ -43,11 +46,18 @@ public class HistoryFragment extends FragmentWithBus { LinearLayout noHistoryMessage; @Override - public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - contextualToolBar = ((OverviewFragment)getParentFragment()).contextualToolBar; - contextualToolBarTitle = contextualToolBar.findViewById(R.id.contextual_title); + public View onCreateView(@NotNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + contextualToolBar = Objects + .requireNonNull((OverviewFragment)getParentFragment()) + .contextualToolBar; + // contextualToolBarTitle = contextualToolBar.findViewById(R.id.contextual_title); final View view = inflater.inflate(R.layout.fragment_history, container, false); ButterKnife.bind(this,view); + final FlavoredActivityComponent component = BrowserApp.getActivityComponent(getContext()); + Objects.requireNonNull(component).inject(this); + adapter = new HistoryAdapter(engine, handler, bus); return view; } @@ -62,45 +72,13 @@ public void onStart() { preferenceManager.setShouldClearQueries(PreferenceManager.ClearQueriesOptions.NO); }*/ prepareListData(); - if (historyList.size() == 0) { + if (adapter.getItemCount() == 0) { noHistoryMessage.setVisibility(View.VISIBLE); return; } noHistoryMessage.setVisibility(View.GONE); if (adapter == null) { - adapter = new HistoryAdapter(historyList, engine, handler, new HistoryAdapter.ClickListener() { - @Override - public void onClick(View view, int position) { - //ignore click on date - if (adapter.getItemViewType(position) == HistoryAdapter.VIEW_TYPE_DATE) { - return; - } - if (isMultiSelect) { - multiSelect(position); - } else if (adapter.getItemViewType(position) == HistoryAdapter.VIEW_TYPE_HISTORY) { - bus.post(CliqzMessages.OpenLink.openFromHistory(historyList.get(position).getUrl())); - } else if (adapter.getItemViewType(position) == HistoryAdapter.VIEW_TYPE_QUERY) { - bus.post(new Messages.OpenQuery(historyList.get(position).getUrl())); - } - } - - @Override - public void onLongPress(View view, int position) { - //if view is being swiped ignore long press - if (view.getTranslationX() != 0) { - return; - } - //ignore long press on date - if (adapter.getItemViewType(position) == HistoryAdapter.VIEW_TYPE_DATE) { - return; - } - if (!isMultiSelect) { - adapter.multiSelectList.clear(); - showContextualMenu(); - } - multiSelect(position); - } - }); + adapter = new HistoryAdapter(engine, handler, bus); } prepareRecyclerView(); } @@ -120,18 +98,25 @@ public void onOverviewTabSwitched(Messages.OnOverviewTabSwitched event) { @Subscribe public void onContextualBarCanceled(Messages.OnContextualBarCancelPressed event) { - if (((OverviewFragment)getParentFragment()).getCurrentPageIndex() - == OverviewTabsEnum.HISTORY.getFragmentIndex()) { + final int pageIndex = Objects + .requireNonNull((OverviewFragment)getParentFragment()) + .getCurrentPageIndex(); + if (pageIndex == OverviewTabsEnum.HISTORY.getFragmentIndex()) { hideContextualMenu(); } } @Subscribe public void onContextualBarDelete(Messages.OnContextualBarDeletePressed event) { - if (((OverviewFragment)getParentFragment()).getCurrentPageIndex() - == OverviewTabsEnum.HISTORY.getFragmentIndex()) { + // TODO restore this + /* + final int pageIndex = Objects + .requireNonNull((OverviewFragment)getParentFragment()) + .getCurrentPageIndex(); + if (pageIndex == OverviewTabsEnum.HISTORY.getFragmentIndex()) { deleteSelectedItems(); } + */ } private void prepareRecyclerView() { @@ -142,59 +127,17 @@ private void prepareRecyclerView() { historyListView.setLayoutManager(new LinearLayoutManager(getContext())); //callback to handle swipe and delete - final ItemTouchHelper.SimpleCallback simpleItemTouchCallback = - new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) { - - @Override - public int getSwipeDirs(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { - //Dont swipe date view and when contextual menu is enabled - if (viewHolder instanceof HistoryAdapter.DateViewHolder || isMultiSelect) { - return 0; - } - return super.getSwipeDirs(recyclerView, viewHolder); - } - - @Override - public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, - RecyclerView.ViewHolder target) { - return false; - } - - @Override - public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { - final int position = viewHolder.getAdapterPosition(); - final int type = viewHolder.getItemViewType(); - if (type == HistoryAdapter.VIEW_TYPE_HISTORY) { - historyDatabase.deleteHistoryPoint(historyList.get(position).getId()); - } else { - historyDatabase.deleteQuery(historyList.get(position).getId()); - } - historyList.remove(position); - adapter.notifyItemRemoved(position); - //check if date view is to be removed - if ((position == historyList.size() - || historyListView.getAdapter().getItemViewType(position) == HistoryAdapter.VIEW_TYPE_DATE) - && historyListView.getAdapter().getItemViewType(position - 1 ) == HistoryAdapter.VIEW_TYPE_DATE) { - historyList.remove(position-1); - adapter.notifyItemRemoved(position-1); - if (historyList.size() == 0) { - noHistoryMessage.setVisibility(View.VISIBLE); - } - } - } - }; - - //callbacks for click and long click on items - final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleItemTouchCallback); + final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new HistoryItemTouchHelper(historyDatabase, adapter)); itemTouchHelper.attachToRecyclerView(historyListView); historyListView.setAdapter(adapter); - historyListView.scrollToPosition(historyList.size()-1); + historyListView.scrollToPosition(adapter.getItemCount()-1); } private void prepareListData() { //TODO historyDatabase.getHistoryItemsCount has to be modified to get the limit correctly; - historyList.clear(); - final Cursor cursor = historyDatabase.getHistoryItemsForRecyclerView(0, historyDatabase.getHistoryItemsCount()); + final int itemsCount = historyDatabase.getHistoryItemsCount(); + final ArrayList historyList = new ArrayList<>(itemsCount); + final Cursor cursor = historyDatabase.getHistoryItemsForRecyclerView(0, itemsCount); final int typeIndex = cursor.getColumnIndex("type"); final int idIndex = cursor.getColumnIndex("id"); final int urlIndex = cursor.getColumnIndex("url"); @@ -208,8 +151,10 @@ private void prepareListData() { cursor.getInt(typeIndex))); } cursor.close(); + adapter.setHistory(historyList); } + /* private void multiSelect(int position) { if (adapter.multiSelectList.contains(position)) { adapter.multiSelectList.remove(Integer.valueOf(position)); @@ -257,10 +202,11 @@ private void showContextualMenu() { return; } isMultiSelect = true; - getParentFragment().setHasOptionsMenu(false); + Objects.requireNonNull(getParentFragment()).setHasOptionsMenu(false); setDisplayHomeAsUpEnabled(false); contextualToolBar.setVisibility(View.VISIBLE); } + */ private void hideContextualMenu() { //if its already hidden dont execute the next lines @@ -268,9 +214,9 @@ private void hideContextualMenu() { return; } isMultiSelect = false; - adapter.multiSelectList.clear(); + // adapter.multiSelectList.clear(); adapter.notifyDataSetChanged(); - getParentFragment().setHasOptionsMenu(true); + Objects.requireNonNull(getParentFragment()).setHasOptionsMenu(true); setDisplayHomeAsUpEnabled(true); contextualToolBar.setVisibility(View.GONE); } diff --git a/app/src/cliqz/java/com/cliqz/browser/overview/OverviewFragment.java b/app/src/cliqz/java/com/cliqz/browser/overview/OverviewFragment.java index dfec65467..bd4e3144e 100644 --- a/app/src/cliqz/java/com/cliqz/browser/overview/OverviewFragment.java +++ b/app/src/cliqz/java/com/cliqz/browser/overview/OverviewFragment.java @@ -1,49 +1,34 @@ package com.cliqz.browser.overview; +import android.app.Activity; import android.content.res.Configuration; import android.content.res.TypedArray; import android.os.Build; import android.os.Bundle; -import androidx.annotation.NonNull; -import com.google.android.material.tabs.TabLayout; -import androidx.fragment.app.Fragment; -import androidx.core.content.ContextCompat; -import androidx.viewpager.widget.ViewPager; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.Toolbar; import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import androidx.annotation.NonNull; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.core.content.ContextCompat; +import androidx.viewpager.widget.ViewPager; + import com.cliqz.browser.R; -import com.cliqz.browser.app.BrowserApp; -import com.cliqz.browser.main.FlavoredActivityComponent; import com.cliqz.browser.main.Messages; import com.cliqz.browser.main.TabFragment; -import com.cliqz.browser.main.TabsManager; -import com.cliqz.browser.telemetry.Telemetry; import com.cliqz.browser.telemetry.TelemetryKeys; import com.cliqz.browser.webview.CliqzMessages; -import com.cliqz.nove.Bus; import com.cliqz.nove.Subscribe; +import com.google.android.material.tabs.TabLayout; import com.readystatesoftware.systembartint.SystemBarTintManager; -import javax.inject.Inject; - -public class OverviewFragment extends Fragment { - - @Inject - Bus bus; - - @Inject - TabsManager tabsManager; +import java.util.Objects; - @Inject - Telemetry telemetry; +public class OverviewFragment extends CommonOverviewFragment { private ViewPager mViewPager; private OverviewTabsEnum mSelectedTab = OverviewTabsEnum.UNDEFINED; @@ -62,15 +47,16 @@ public View onCreateView(@NonNull LayoutInflater inflater, final ViewGroup conta final View deleteButton = view.findViewById(R.id.action_delete); deleteButton.setOnClickListener(v -> bus.post(new Messages.OnContextualBarDeletePressed())); final int themeResId = R.style.Theme_Cliqz_Overview; - final TypedArray typedArray = getActivity().getTheme() + final Activity activity = Objects.requireNonNull(getActivity()); + final TypedArray typedArray = activity.getTheme() .obtainStyledAttributes(themeResId, new int[]{R.attr.colorPrimaryDark}); final int resourceId = typedArray.getResourceId(0, R.color.normal_tab_primary_color); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - getActivity().getWindow() - .setStatusBarColor(ContextCompat.getColor(getContext(), resourceId)); + activity.getWindow() + .setStatusBarColor(ContextCompat.getColor(activity, resourceId)); typedArray.recycle(); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - SystemBarTintManager tintManager = new SystemBarTintManager(getActivity()); + SystemBarTintManager tintManager = new SystemBarTintManager(activity); tintManager.setStatusBarTintEnabled(true); tintManager.setNavigationBarTintEnabled(true); tintManager.setTintColor(resourceId); @@ -81,14 +67,13 @@ public View onCreateView(@NonNull LayoutInflater inflater, final ViewGroup conta mViewPager.setOffscreenPageLimit(5); mViewPager.setAdapter(mPageAdapter); final Toolbar toolbar = view.findViewById(R.id.toolbar); - toolbar.setTitle(getContext().getString(R.string.overview)); - ((AppCompatActivity) getActivity()).setSupportActionBar(toolbar); - final ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar(); + toolbar.setTitle(activity.getString(R.string.overview)); + ((AppCompatActivity) activity).setSupportActionBar(toolbar); + final ActionBar actionBar = ((AppCompatActivity) activity).getSupportActionBar(); if (actionBar != null) { actionBar.setDisplayHomeAsUpEnabled(true); - actionBar.setTitle(getContext().getString(R.string.overview)); + actionBar.setTitle(activity.getString(R.string.overview)); } - setHasOptionsMenu(true); TabLayout tabLayout = view.findViewById(R.id.tabs); tabLayout.setupWithViewPager(mViewPager); return view; @@ -104,43 +89,6 @@ private void sendCurrentPageHideSignal() { } } - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - inflater.inflate(R.menu.fragment_overview_menu, menu); - super.onCreateOptionsMenu(menu, inflater); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - // All the options close the current visible page - sendCurrentPageHideSignal(); - final int id = item.getItemId(); - switch (id) { - case R.id.action_new_tab: - telemetry.sendMainMenuSignal(TelemetryKeys.NEW_TAB, false, TelemetryKeys.OVERVIEW); - tabsManager.buildTab().show(); - return true; - case R.id.action_new_forget_tab: - telemetry.sendMainMenuSignal(TelemetryKeys.NEW_FORGET_TAB, false, - TelemetryKeys.OVERVIEW); - tabsManager.buildTab().setForgetMode(true).show(); - return true; - case R.id.action_settings: - telemetry.sendMainMenuSignal(TelemetryKeys.SETTINGS, false, TelemetryKeys.OVERVIEW); - if (bus != null) { - bus.post(new Messages.GoToSettings()); - } - return true; - case R.id.action_close_all_tabs: - telemetry.sendMainMenuSignal(TelemetryKeys.CLOSE_ALL_TABS, false, - TelemetryKeys.OVERVIEW); - tabsManager.deleteAllTabs(); - return true; - default: - return false; - } - } - @SuppressWarnings("UnusedParameters") @Subscribe public void onBackPressed(Messages.BackPressed event) { @@ -176,16 +124,6 @@ public void onOrientationChanged(Configuration newConfig) { TelemetryKeys.LANDSCAPE : TelemetryKeys.PORTRAIT, TelemetryKeys.OVERVIEW); } - @Override - public void onStart() { - super.onStart(); - final FlavoredActivityComponent component = BrowserApp.getActivityComponent(getActivity()); - if (component != null) { - component.inject(this); - bus.register(this); - } - } - @Override public void onStop() { super.onStop(); @@ -270,4 +208,10 @@ public void onPageScrollStateChanged(int state) { } } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + sendCurrentPageHideSignal(); + return super.onOptionsItemSelected(item); + } } diff --git a/app/src/cliqz/java/com/cliqz/browser/starttab/StartTabContainer.java b/app/src/cliqz/java/com/cliqz/browser/starttab/StartTabContainer.java index fa33f4c23..ff50b0aa5 100644 --- a/app/src/cliqz/java/com/cliqz/browser/starttab/StartTabContainer.java +++ b/app/src/cliqz/java/com/cliqz/browser/starttab/StartTabContainer.java @@ -6,12 +6,9 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.fragment.app.FragmentManager; import com.cliqz.browser.main.search.Freshtab; -import acr.browser.lightning.preference.PreferenceManager; - /** * @author Ravjit Uppal */ @@ -29,14 +26,15 @@ public StartTabContainer(@NonNull Context context, @Nullable AttributeSet attrs) public StartTabContainer(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - } - - public void init(FragmentManager supportFragmentManager, PreferenceManager preferenceManager) { - mFreshtab = new Freshtab(getContext()); + mFreshtab = new Freshtab(context); addView(mFreshtab); } public void updateFreshTab() { mFreshtab.updateFreshTab(); } + + public void gotToFavorites() { + // NO-OP, stub for supporting the Lumen Flavor + } } diff --git a/app/src/cliqz/java/com/cliqz/browser/vpn/VpnPanel.java b/app/src/cliqz/java/com/cliqz/browser/vpn/VpnPanel.java index 9ca3c73b4..eeebc73d7 100644 --- a/app/src/cliqz/java/com/cliqz/browser/vpn/VpnPanel.java +++ b/app/src/cliqz/java/com/cliqz/browser/vpn/VpnPanel.java @@ -1,5 +1,6 @@ package com.cliqz.browser.vpn; +import android.app.Dialog; import android.view.View; import androidx.fragment.app.FragmentManager; @@ -12,6 +13,7 @@ public class VpnPanel { public static final int VPN_LAUNCH_REQUEST_CODE = 70; + public static final String ACTION_DISCONNECT_VPN = null; public static VpnPanel create(View view) { return null; @@ -19,4 +21,12 @@ public static VpnPanel create(View view) { public void show(FragmentManager fragmentManager, String tag) { } + + public boolean isVisible() { + return false; + } + + public Dialog getDialog() { + return null; + } } diff --git a/app/src/cliqz/res/drawable/deckview_tab_background.xml b/app/src/cliqz/res/drawable/deckview_tab_background.xml new file mode 100644 index 000000000..ecd85d85a --- /dev/null +++ b/app/src/cliqz/res/drawable/deckview_tab_background.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/cliqz/res/drawable/ic_clear.xml b/app/src/cliqz/res/drawable/ic_clear.xml new file mode 100644 index 000000000..ede4b7108 --- /dev/null +++ b/app/src/cliqz/res/drawable/ic_clear.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/cliqz/res/drawable/ic_lock_black.xml b/app/src/cliqz/res/drawable/ic_lock_black.xml new file mode 100644 index 000000000..67a7c73ab --- /dev/null +++ b/app/src/cliqz/res/drawable/ic_lock_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_cliqz_tab.xml b/app/src/cliqz/res/drawable/logo_start_tab.xml similarity index 100% rename from app/src/main/res/drawable/ic_cliqz_tab.xml rename to app/src/cliqz/res/drawable/logo_start_tab.xml diff --git a/app/src/cliqz/res/drawable/tab_default_favicon.xml b/app/src/cliqz/res/drawable/tab_default_favicon.xml new file mode 100644 index 000000000..fd150daba --- /dev/null +++ b/app/src/cliqz/res/drawable/tab_default_favicon.xml @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/app/src/cliqz/res/layout-land/deckview_tab_layout.xml b/app/src/cliqz/res/layout-land/deckview_tab_layout.xml new file mode 100644 index 000000000..06381701b --- /dev/null +++ b/app/src/cliqz/res/layout-land/deckview_tab_layout.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/cliqz/res/layout/activity_overview.xml b/app/src/cliqz/res/layout/activity_overview.xml index c73178c0c..972b871f6 100644 --- a/app/src/cliqz/res/layout/activity_overview.xml +++ b/app/src/cliqz/res/layout/activity_overview.xml @@ -32,7 +32,8 @@ android:layout_width="@dimen/icon_width" android:layout_height="@dimen/icon_height" android:layout_gravity="center" - app:srcCompat="@drawable/ic_clear_white"/> + android:tint="@android:color/white" + app:srcCompat="@drawable/ic_clear_black"/> + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/cliqz/res/layout/favorite_viewholder.xml b/app/src/cliqz/res/layout/favorite_viewholder.xml new file mode 100644 index 000000000..944be4787 --- /dev/null +++ b/app/src/cliqz/res/layout/favorite_viewholder.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/cliqz/res/layout/history_viewholder.xml b/app/src/cliqz/res/layout/history_viewholder.xml new file mode 100644 index 000000000..a7b54774c --- /dev/null +++ b/app/src/cliqz/res/layout/history_viewholder.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/cliqz/res/layout/search_bar_widget.xml b/app/src/cliqz/res/layout/search_bar_widget.xml index 3c7e4b324..8d97de27f 100644 --- a/app/src/cliqz/res/layout/search_bar_widget.xml +++ b/app/src/cliqz/res/layout/search_bar_widget.xml @@ -5,8 +5,18 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:background="@drawable/searchbar_background" + android:gravity="center_vertical" android:orientation="horizontal"> + + diff --git a/app/src/cliqz/res/values-de/strings.xml b/app/src/cliqz/res/values-de/strings.xml index 96caf24b3..826f0a0af 100644 --- a/app/src/cliqz/res/values-de/strings.xml +++ b/app/src/cliqz/res/values-de/strings.xml @@ -25,5 +25,13 @@ Datenschutzerklärung - Cliqz Hilf uns deine Browsernutzung zu verbessern. Cliqz sammelt streng anonyme Daten darüber, wie du Cliqz benutzt. Zu keinem Zeitpunkt wird eine PII erfasst. Hallo! Wir möchten sehr gerne etwas über Ihre Meinung zum Cliqz Browser erfahren. i + Neuer Vergessen-Tab + In neuem Vergessen-Tab öffnen + Vergessen-Tab automatisch aktiviert + Link in Vergessen-Tab öffnen + Bild in Vergessen-Tab öffnen + Vergessen Tab + Du surfst im Vergessen-Modus: Webseiten, die du in diesem Modus besuchst, werden nicht in deinem Verlauf auftauchen. Auch werden lokale Daten, Cookies mit eingeschlossen, nicht gespeichert. + Automatisches Vergessen-Tab diff --git a/app/src/cliqz/res/values-es/strings.xml b/app/src/cliqz/res/values-es/strings.xml index 5a05162d0..85104e2c4 100644 --- a/app/src/cliqz/res/values-es/strings.xml +++ b/app/src/cliqz/res/values-es/strings.xml @@ -23,4 +23,12 @@ Cliqz para Android se basa en el <a href=https://github.com/anthonycr/Lightning-Browser/blob/dev/LICENSE>proyecto de código abierto Lightning Browser</a>, y se distribuye bajo los términos de<a href=https://www.mozilla.org/en-US/MPL/2.0/>Mozilla Public License 2.0</a>, una licencia de software libre que permite utilizar este programa con cualquier objetivo, estudiar su funcionamiento, compartirlo con tus amigos y modificarlo según tus propias necesidades. No existe un Acuerdo de Licencia de Usuario Final (ALUF) adicional. Política de privacidad - Cliqz Ayúdanos a mejorar tu experiencia de usuario. Cliqz recopila datos anónimos sobre el uso de la aplicación. Cliqz no recopila información personal en ningún momento. + Nueva pestaña privada + Abrir en una pestaña privada + Modo privado automático activado + Abrir en una pestaña privada + Abrir imagen en pestaña privada + Pestaña privada + Estás navegando en modo privado: las páginas que visites no se guardarán en tu historial ni se almacenará información local, cookies incluidas. + Pestañas privadas automáticas diff --git a/app/src/cliqz/res/values-fr/strings.xml b/app/src/cliqz/res/values-fr/strings.xml index 763529046..abed21ec5 100644 --- a/app/src/cliqz/res/values-fr/strings.xml +++ b/app/src/cliqz/res/values-fr/strings.xml @@ -23,4 +23,12 @@ Bienvenu sur Cliqz ! Em utilisant cette application, vous acceptez les termes la <font color="#00aef0">politique de confidentialié de Cliqz</font> Politique de confidentialité - Cliqz + Nouvel Onglet en Mode Oubli + Ouvrir dans un Onglet Mode Oubli + Mode Oubli Automatique activé + Ouvrir dans un Onglet Mode Oubli + Ouvrir l\'image dans un Onglet Mode Oubli + Mode Oubli + Vous surfez en Mode Oubli : les sites web que vous visitez dans ce mode ne seront pas sauvegardés dans votre historique et les données locales, cookies inclus, ne seront pas conservées. + Mode Oubli Automatique diff --git a/app/src/cliqz/res/values-it/strings.xml b/app/src/cliqz/res/values-it/strings.xml index 815ddf6d3..0fde0e357 100644 --- a/app/src/cliqz/res/values-it/strings.xml +++ b/app/src/cliqz/res/values-it/strings.xml @@ -25,4 +25,12 @@ Cliqz ti dà il benvenuto! Informativa sulla Privacy - Cliqz Aiutaci a migliorare la tua esperienza di navigazione. Cliqz raccoglie dati di utilizzo in forma anonima. In nessun caso vengono raccolte informazioni personali. + Nuovo tab in Modalità Oblio + Apri in Modalità Oblio + Modalità Oblio automatica attivata + Apri link in nuovo tab Modalità Oblio + Apri immagine in Modalità Oblio + Modalità Oblio + Stai navigando in Modalità Oblio: i siti web che visiti in questa modalità non saranno salvati nella tua cronologia e i tuoi dati locali, cookie inclusi, non verranno salvati. + Modalità Oblio automatica diff --git a/app/src/cliqz/res/values-pl/strings.xml b/app/src/cliqz/res/values-pl/strings.xml index 88b4787ae..723960965 100644 --- a/app/src/cliqz/res/values-pl/strings.xml +++ b/app/src/cliqz/res/values-pl/strings.xml @@ -24,4 +24,12 @@ Polityka prywatności - Cliqz Dane zbierane przez Cliqz Pomóż nam udoskonalić Cliqz. Wszysztkie dane zbierane przez Cliqz są anonimowe. + Nowa karta prywatna + Otwórz w nowej karcie prywatnej + Automatyczne przełączanie w tryb prywatny zostało włączone + Otwórz link w nowej prywatnej karcie + Otwórz obraz w nowej prywatnej karcie + Karta prywatna + Przeglądasz Intertet w trybie prywatnym: odwiedzane przez Ciebie strony internetowe nie będą zapisane w Twojej historii, a dane lokalne, wliczając także cookies, nie będą zapisane. + Automatyczny tryb prywatny diff --git a/app/src/cliqz/res/values-pt/strings.xml b/app/src/cliqz/res/values-pt/strings.xml index 8a7b5e596..237845ef0 100644 --- a/app/src/cliqz/res/values-pt/strings.xml +++ b/app/src/cliqz/res/values-pt/strings.xml @@ -23,4 +23,12 @@ Você quer Cliqz salva suas senhas para esta página? O Navegador Cliqz para Android é desenvolvido com a utilização do <a href=https://github.com/anthonycr/Lightning-Browser/blob/dev/LICENSE>projeto open source Lightning Browser</a>, à nossa disposição nós temos da <a href=https://www.mozilla.org/en-US/MPL/2.0/>Mozilla Public License 2.0</a>, uma licença para software livre que concedem aos usuários a liberdade de controle na execução, análise, alterações e permite compartilhar com amigos. Não existe um Acordo de Licença do Usuário Final (EULA) separado. Ajude-nos a melhorar sua experiência de navegação. Cliqz coleta dados de uso estritamente anônimos. Em nenhuma ocasião, qualquer informação pessoal seja coletada. + Novo separador incógnito + Abrir em separador incógnito + Modo privado automático ativado + Abrir ligação num novo separador privado + Abrir imagem num novo separador privado + Separador incógnito + Está a navegar em modo privado:Não vamos salvar nenhum histórico e dados locais, incluindo cookies, não serão salvadas. + Modo privado automático ativado diff --git a/app/src/cliqz/res/values-ru/strings.xml b/app/src/cliqz/res/values-ru/strings.xml index 8e7be5ba1..1adb0d6ce 100644 --- a/app/src/cliqz/res/values-ru/strings.xml +++ b/app/src/cliqz/res/values-ru/strings.xml @@ -23,4 +23,12 @@ Cliqz браузер для Android разработан на основе <a href=https://github.com/anthonycr/Lightning-Browser/blob/dev/LICENSE>Lightning Browser открытого программного обеспечения</a> и доступен Вам на основе условий <a href=https://www.mozilla.org/en-US/MPL/2.0/>Mozilla Public License 2.0</a>, лицензии свободного ПО, которая даёт Вам право использовать программу для любых целей, изучать, как она работает, передавать копии Вашим друзьям и вносить в нее свои изменения. Отдельного лицензионного соглашениея с конечным пользователем (EULA) не существует. Политика безопасности - Cliqz Помогите нам улучшить процесс просмотра веб-страниц. Cliqz собирает только анонимные пользовательские данные. Персональные данные не собираются ни при каких обстоятельствах. + Приватная вкладка + Открыть в приватной вкладке + Автоматическая приватная вкладка включена + Открыть ссылку в приватной вкладке + Открыть картинку в приватной вкладке + Приватная вкладка + Вы находитесь в приватном режиме: веб-сайты, которые вы посещаете в этом режиме, не будут сохранены в истории; локальные данные, включая куки, также не будут сохранены. + Автоматическая приватная вкладка diff --git a/app/src/cliqz/res/values/colors.xml b/app/src/cliqz/res/values/colors.xml index 711e25567..744bd04bb 100644 --- a/app/src/cliqz/res/values/colors.xml +++ b/app/src/cliqz/res/values/colors.xml @@ -34,6 +34,7 @@ #333333 #ffffff #333333 + #AAAAAA #333333 #ffffff @@ -76,4 +77,9 @@ #CFFF #E7ECEE #2B5993 + + @color/primary_color + + @color/normal_tab_primary_color + @color/primary_color diff --git a/app/src/cliqz/res/values/strings.xml b/app/src/cliqz/res/values/strings.xml index 8b701f77c..612aa8cda 100644 --- a/app/src/cliqz/res/values/strings.xml +++ b/app/src/cliqz/res/values/strings.xml @@ -26,4 +26,12 @@ Privacy Policy - Cliqz Help us improve your browsing experience. Cliqz collects strictly anonymous usage data. At no occasion is any PII collected. Hello! We’d like to know your opinion about the Cliqz Browser. i + New Forget Tab + Open in new Forget Tab + Automatic Forget Tab enabled + Open link in Forget Tab + Open image in Forget Tab + Forget Tab + You are browsing in Forget Mode: websites you visit in this mode will not be saved in your history, and local data, including cookies, will not be stored. + Automatic Forget Tab diff --git a/app/src/lumen/AndroidManifest.xml b/app/src/lumen/AndroidManifest.xml new file mode 100644 index 000000000..ee8f47e45 --- /dev/null +++ b/app/src/lumen/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/lumen/java/com/cliqz/browser/app/AppComponent.java b/app/src/lumen/java/com/cliqz/browser/app/AppComponent.java new file mode 100644 index 000000000..58c02e935 --- /dev/null +++ b/app/src/lumen/java/com/cliqz/browser/app/AppComponent.java @@ -0,0 +1,16 @@ +package com.cliqz.browser.app; + +import com.cliqz.browser.purchases.trial.TrialPeriodRemoteRepo; + +import javax.inject.Singleton; + +import dagger.Component; + +/** + * @author Ravjit Uppal + */ +@Singleton +@Component(modules = {AppModule.class}) +public interface AppComponent extends BaseAppComponent { + void inject(TrialPeriodRemoteRepo trialPeriodRemoteRepo); +} \ No newline at end of file diff --git a/app/src/lumen/java/com/cliqz/browser/app/BrowserApp.java b/app/src/lumen/java/com/cliqz/browser/app/BrowserApp.java index c3ae68843..d0639ca7e 100644 --- a/app/src/lumen/java/com/cliqz/browser/app/BrowserApp.java +++ b/app/src/lumen/java/com/cliqz/browser/app/BrowserApp.java @@ -1,12 +1,20 @@ package com.cliqz.browser.app; +import android.app.ActivityManager; +import android.content.Context; + import com.cliqz.browser.BuildConfig; +import com.cliqz.browser.CliqzConfig; import com.cliqz.browser.purchases.PurchasesManager; import com.revenuecat.purchases.Purchases; import javax.inject.Inject; +import de.blinkt.openvpn.ConfigConverter; +import de.blinkt.openvpn.core.ProfileManager; import de.blinkt.openvpn.core.StatusListener; +import io.sentry.Sentry; +import io.sentry.android.AndroidSentryClientFactory; /** * @author Ravjit Uppal @@ -18,19 +26,57 @@ public class BrowserApp extends BaseBrowserApp { @Override public void init() { + //We need the listener in both the processes final StatusListener mStatus = new StatusListener(); mStatus.init(getApplicationContext()); + //If it's the vpn process we don't need to initialize any other library + if (isVpnProcess()) { + return; + } + //Initialize common libraries + super.init(); + //Initialize flavour specific libraries + setupCrashReporting(); getAppComponent().inject(this); setupSubscriptionSDK(); } + //@TODO Remove hardcoded imports once the integration with server is done + private void importVpnProfiles() { + final ProfileManager profileManager = ProfileManager.getInstance(getApplicationContext()); + if (profileManager.getProfileByName("austria-vpn") == null) { + final Uri usVpnUri = Uri.parse("android.resource://" + getPackageName() + "/raw/austria"); + final ConfigConverter usConvertor = new ConfigConverter(getApplicationContext()); + usConvertor.startImportTask(usVpnUri, "austria-vpn"); + } + } + + private void setupCrashReporting() { + if (!CliqzConfig.SENTRY_TOKEN.isEmpty()) { + Sentry.init(CliqzConfig.SENTRY_TOKEN, new AndroidSentryClientFactory(this)); + } + } + private void setupSubscriptionSDK() { //noinspection ConstantConditions - if (!BuildConfig.REVENUECAT_API_KEY.isEmpty()) { + if (!CliqzConfig.REVENUECAT_API_KEY.isEmpty()) { Purchases.setDebugLogsEnabled(BuildConfig.DEBUG); - Purchases.configure(this, BuildConfig.REVENUECAT_API_KEY); + Purchases.configure(this, CliqzConfig.REVENUECAT_API_KEY); purchasesManager.checkPurchases(); } } + + private boolean isVpnProcess() { + final ActivityManager manager = (ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE); + for (ActivityManager.RunningAppProcessInfo processInfo : manager.getRunningAppProcesses()) { + if (processInfo.pid == android.os.Process.myPid()) { + if (processInfo.processName.equals("com.cliqz.lumen:openvpn")) { + return true; + } + } + } + return false; + } + } diff --git a/app/src/lumen/java/com/cliqz/browser/controlcenter/BaseControlCenterPagerAdapter.java b/app/src/lumen/java/com/cliqz/browser/controlcenter/BaseControlCenterPagerAdapter.java deleted file mode 100644 index 84e35268e..000000000 --- a/app/src/lumen/java/com/cliqz/browser/controlcenter/BaseControlCenterPagerAdapter.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.cliqz.browser.controlcenter; - -import android.content.Context; - -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentPagerAdapter; - -import java.util.ArrayList; -import java.util.List; - -/** - * Copyright © Cliqz 2019 - * - * Abstraction layer for Control center adapter for different flavours - */ -public abstract class BaseControlCenterPagerAdapter extends FragmentPagerAdapter { - - private Context mContext; - final List mFragmentList = new ArrayList<>(); - - BaseControlCenterPagerAdapter(FragmentManager fm, Context context) { - super(fm); - mContext = context; - } - - public Fragment getItem(int position) { - return mFragmentList.get(position); - } - - public int getCount() { - return mFragmentList.size(); - } - - public CharSequence getPageTitle(int position) { - return mFragmentList.get(position).getTitle(mContext, position); - } - - void updateCurrentView(int position) { - mFragmentList.get(position).refreshUI(); - } - - void updateViewComponent(int position, boolean optionValue) { - mFragmentList.get(position).refreshUIComponent(optionValue); - } - -} \ No newline at end of file diff --git a/app/src/lumen/java/com/cliqz/browser/controlcenter/ControlCenterDialog.java b/app/src/lumen/java/com/cliqz/browser/controlcenter/ControlCenterDialog.java index eadaf01c0..c3657c3a3 100644 --- a/app/src/lumen/java/com/cliqz/browser/controlcenter/ControlCenterDialog.java +++ b/app/src/lumen/java/com/cliqz/browser/controlcenter/ControlCenterDialog.java @@ -1,6 +1,7 @@ package com.cliqz.browser.controlcenter; import android.app.Activity; +import android.content.Context; import android.content.res.Configuration; import android.os.Bundle; import android.util.Log; @@ -9,11 +10,13 @@ import android.view.View; import android.view.ViewGroup; import android.view.Window; +import android.view.WindowManager; import android.widget.Button; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.widget.SwitchCompat; +import androidx.core.content.ContextCompat; import androidx.fragment.app.DialogFragment; import androidx.fragment.app.FragmentManager; @@ -67,9 +70,6 @@ public class ControlCenterDialog extends DialogFragment { @BindView(R.id.subscribe_ultimate_protection_btn) Button subscribeUltimateProtectionBtn; - @BindView(R.id.dashboard_disable_overlay) - View dashboardDisableOverlay; - @Inject AntiTracking antiTracking; @@ -136,29 +136,28 @@ public void onSaveInstanceState(@NonNull Bundle outState) { @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - final View view = LayoutInflater.from(getContext()).inflate(R.layout.control_center_layout, + final Context context = getContext(); + final View view = LayoutInflater.from(context).inflate(R.layout.control_center_layout, container, false); ButterKnife.bind(this, view); - mControlCenterPagerAdapter = new ControlCenterPagerAdapter(getChildFragmentManager(), getContext()); + mControlCenterPagerAdapter = new ControlCenterPagerAdapter(getChildFragmentManager(), context); mControlCenterPagerAdapter.init(); controlCenterPager.setAdapter(mControlCenterPagerAdapter); controlCenterTabLayout.setupWithViewPager(controlCenterPager); - boolean isDashboardEnabled = - preferenceManager.isAttrackEnabled() && preferenceManager.getAdBlockEnabled(); - - hideSubscribeButton(isDashboardEnabled); + hideSubscribeButton(purchasesManager.isDashboardEnabled()); ultimateProtectionSwitch.setOnCheckedChangeListener((compoundButton, isChecked) -> { mControlCenterPagerAdapter.updateViewComponent(0, isChecked); mControlCenterPagerAdapter.updateViewComponent(1, isChecked); + toggleTabLayout(isChecked); try { adblocker.setEnabled(isChecked); - antiTracking.setEnabled(isChecked);; + antiTracking.setEnabled(isChecked); preferenceManager.setAttrackEnabled(isChecked); preferenceManager.setAdBlockEnabled(isChecked); - bus.post(new Messages.onDashboardStateChange()); + bus.post(new Messages.OnDashboardStateChange()); } catch (EngineNotYetAvailable engineNotYetAvailable) { Log.e("JsEngineError", "Cannot enable/disable tracking modules", engineNotYetAvailable); @@ -169,6 +168,14 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c return view; } + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + final Window window = getDialog().getWindow(); + window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL); + getDialog().setCanceledOnTouchOutside(false); + } + @Subscribe void clearDashboardData(Messages.ClearDashboardData clearDashboardData) { insights.clearData(); @@ -177,7 +184,7 @@ void clearDashboardData(Messages.ClearDashboardData clearDashboardData) { @Subscribe void onPurchaseCompleted(Messages.PurchaseCompleted purchaseCompleted) { - hideSubscribeButton(purchasesManager.getPurchase().isDashboardEnabled()); + hideSubscribeButton(purchasesManager.isDashboardEnabled()); } @Override @@ -191,9 +198,12 @@ public void onConfigurationChanged(Configuration newConfig) { } private void hideSubscribeButton(boolean isDashboardEnabled) { - ultimateProtectionSwitch.setChecked(isDashboardEnabled); + toggleTabLayout(isDashboardEnabled && + preferenceManager.isAttrackEnabled() && preferenceManager.getAdBlockEnabled()); + ultimateProtectionSwitch.setChecked(preferenceManager.isAttrackEnabled() && + preferenceManager.getAdBlockEnabled()); subscribeUltimateProtectionView.setVisibility(isDashboardEnabled ? View.GONE : View.VISIBLE); - dashboardDisableOverlay.setVisibility(isDashboardEnabled ? View.GONE : View.VISIBLE); + ultimateProtectionContainer.setVisibility(isDashboardEnabled ? View.VISIBLE : View.GONE); ViewExtensionsKt.enableViewHierarchy(ultimateProtectionContainer, isDashboardEnabled); ViewExtensionsKt.enableViewHierarchy(controlCenterTabLayout, isDashboardEnabled); controlCenterPager.isPagingEnabled = isDashboardEnabled; @@ -204,9 +214,25 @@ private void hideSubscribeButton(boolean isDashboardEnabled) { } } + private void toggleTabLayout(boolean isEnabled) { + final Context context = getContext(); + if (isEnabled) { + controlCenterTabLayout.setSelectedTabIndicatorColor( + ContextCompat.getColor(context, R.color.bond_general_color_blue)); + controlCenterTabLayout.setTabTextColors( + ContextCompat.getColor(context, R.color.bond_disabled_text_color), + ContextCompat.getColor(context, R.color.bond_general_color_blue)); + } else { + controlCenterTabLayout.setSelectedTabIndicatorColor( + ContextCompat.getColor(context, R.color.lumen_color_grey_text)); + controlCenterTabLayout.setTabTextColors( + ContextCompat.getColor(context, R.color.lumen_color_grey_text), + ContextCompat.getColor(context, R.color.lumen_color_grey_text)); + } + } private void updateUI() { - for (ControlCenterFragment fragment : mControlCenterPagerAdapter.mFragmentList) { + for (DashboardFragment fragment : mControlCenterPagerAdapter.mFragmentList) { fragment.updateUI(); } } diff --git a/app/src/lumen/java/com/cliqz/browser/controlcenter/ControlCenterFragment.java b/app/src/lumen/java/com/cliqz/browser/controlcenter/ControlCenterFragment.java deleted file mode 100644 index 8fda22f22..000000000 --- a/app/src/lumen/java/com/cliqz/browser/controlcenter/ControlCenterFragment.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.cliqz.browser.controlcenter; - -import android.content.Context; - -import androidx.fragment.app.Fragment; - -/** - * Copyright © Cliqz 2019 - */ -public abstract class ControlCenterFragment extends Fragment { - - public abstract String getTitle(Context context, int position); - - public abstract void updateUI(); - - public abstract void refreshUI(); - - public abstract void refreshUIComponent(boolean optionValue); -} diff --git a/app/src/lumen/java/com/cliqz/browser/controlcenter/ControlCenterHelper.kt b/app/src/lumen/java/com/cliqz/browser/controlcenter/ControlCenterHelper.kt index 292039298..fd65e4a9e 100644 --- a/app/src/lumen/java/com/cliqz/browser/controlcenter/ControlCenterHelper.kt +++ b/app/src/lumen/java/com/cliqz/browser/controlcenter/ControlCenterHelper.kt @@ -1,15 +1,30 @@ package com.cliqz.browser.controlcenter import acr.browser.lightning.constant.Constants +import android.content.Context import android.view.View import androidx.fragment.app.FragmentManager +import com.cliqz.browser.app.BrowserApp +import com.cliqz.browser.main.MainActivityHandler import com.cliqz.browser.main.Messages +import com.cliqz.nove.Bus import com.cliqz.nove.Subscribe +import javax.inject.Inject /** * Copyright © Cliqz 2019 */ -class ControlCenterHelper(private val fragmentManager: FragmentManager) : ControlCenterActions { +class ControlCenterHelper(context : Context, private val fragmentManager: FragmentManager) : ControlCenterActions { + + @Inject + lateinit var handler: MainActivityHandler + + @Inject + lateinit var bus : Bus + + init { + BrowserApp.getActivityComponent(context)?.inject(this) + } private var mSource: View? = null @@ -20,8 +35,15 @@ class ControlCenterHelper(private val fragmentManager: FragmentManager) : Contro } override fun toggleControlCenter() { + if (controlCenterDialog != null && controlCenterDialog!!.isVisible) { + controlCenterDialog?.dialog?.dismiss() + return + } controlCenterDialog = ControlCenterDialog.create(mSource) controlCenterDialog?.show(fragmentManager, Constants.CONTROL_CENTER) + handler.postDelayed({ + bus.post(Messages.DismissVpnPanel()) + }, 500) } override fun setControlCenterData(source: View, isIncognito: Boolean, hashCode: Int, url: String) { diff --git a/app/src/lumen/java/com/cliqz/browser/controlcenter/ControlCenterPagerAdapter.java b/app/src/lumen/java/com/cliqz/browser/controlcenter/ControlCenterPagerAdapter.java index 7eed075e7..759572b43 100644 --- a/app/src/lumen/java/com/cliqz/browser/controlcenter/ControlCenterPagerAdapter.java +++ b/app/src/lumen/java/com/cliqz/browser/controlcenter/ControlCenterPagerAdapter.java @@ -3,17 +3,35 @@ import android.content.Context; import android.os.Bundle; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentPagerAdapter; + +import com.cliqz.browser.R; + +import java.util.ArrayList; +import java.util.List; /** * Copyright © Cliqz 2019 */ -public class ControlCenterPagerAdapter extends BaseControlCenterPagerAdapter { +public class ControlCenterPagerAdapter extends FragmentPagerAdapter { static final String IS_TODAY = "is_daily"; + private final Context mContext; + + final List mFragmentList = new ArrayList<>(); + ControlCenterPagerAdapter(FragmentManager fm, Context context) { - super(fm, context); + super(fm); + this.mContext = context; + } + + @Override + public Fragment getItem(int position) { + return mFragmentList.get(position); } public void init() { @@ -28,4 +46,23 @@ public void init() { mFragmentList.add(dashboardTodayFragment); mFragmentList.add(dashboardWeekFragment); } + + @Override + public int getCount() { + return mFragmentList.size(); + } + + @Nullable + @Override + public CharSequence getPageTitle(int position) { + if (position == 0) { + return mContext.getString(R.string.bond_dashboard_today_title); + } else { + return mContext.getString(R.string.bond_dashboard_week_title); + } + } + + void updateViewComponent(int position, boolean optionValue) { + mFragmentList.get(position).refreshUIComponent(optionValue); + } } \ No newline at end of file diff --git a/app/src/lumen/java/com/cliqz/browser/controlcenter/ControlCenterTabs.java b/app/src/lumen/java/com/cliqz/browser/controlcenter/ControlCenterTabs.java deleted file mode 100644 index 8368ed6fc..000000000 --- a/app/src/lumen/java/com/cliqz/browser/controlcenter/ControlCenterTabs.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.cliqz.browser.controlcenter; - -import androidx.annotation.NonNull; -import androidx.annotation.StringRes; - -/** - * @author Stefano Pacifici - */ -enum ControlCenterTabs { - - ; - - @StringRes - final int title; - - final Class fragmentClass; - - ControlCenterTabs(@StringRes int title, @NonNull Class clazz) { - this.title = title; - this.fragmentClass = clazz; - } -} diff --git a/app/src/lumen/java/com/cliqz/browser/controlcenter/ControlCenterViewPager.kt b/app/src/lumen/java/com/cliqz/browser/controlcenter/ControlCenterViewPager.kt index f632d6084..db171b708 100644 --- a/app/src/lumen/java/com/cliqz/browser/controlcenter/ControlCenterViewPager.kt +++ b/app/src/lumen/java/com/cliqz/browser/controlcenter/ControlCenterViewPager.kt @@ -16,24 +16,6 @@ class ControlCenterViewPager @JvmOverloads constructor( @JvmField internal var isPagingEnabled = true - init { - addOnPageChangeListener(object : OnPageChangeListener { - override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { - // Do nothing - } - - override fun onPageSelected(position: Int) { - if (adapter != null) { - (adapter as ControlCenterPagerAdapter).updateCurrentView(position) - } - } - - override fun onPageScrollStateChanged(state: Int) { - // Do nothing - } - }) - } - @SuppressLint("ClickableViewAccessibility") override fun onTouchEvent(event: MotionEvent): Boolean { return this.isPagingEnabled && super.onTouchEvent(event) diff --git a/app/src/lumen/java/com/cliqz/browser/controlcenter/DashboardAdapter.java b/app/src/lumen/java/com/cliqz/browser/controlcenter/DashboardAdapter.java deleted file mode 100644 index c8f10520d..000000000 --- a/app/src/lumen/java/com/cliqz/browser/controlcenter/DashboardAdapter.java +++ /dev/null @@ -1,174 +0,0 @@ -package com.cliqz.browser.controlcenter; - -import android.app.AlertDialog; -import android.content.Context; -import android.text.Spannable; -import android.text.SpannableString; -import android.text.Spanned; -import android.text.style.TypefaceSpan; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.RelativeLayout; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; - -import com.cliqz.browser.R; -import com.cliqz.browser.main.Messages; -import com.cliqz.nove.Bus; - -import java.util.ArrayList; -import java.util.List; - -/** - * Copyright © Cliqz 2019 - */ -public class DashboardAdapter extends RecyclerView.Adapter { - - private static final int FOOTER_VIEW = 1; - private static final int REGULAR_VIEW = 2; - - private final Context mContext; - private final List mDashboardItems; - private final Bus mBus; - - private boolean mIsDashboardEnabled = true; - - DashboardAdapter(Context context, Bus bus) { - mContext = context; - mDashboardItems = new ArrayList<>(); - mBus = bus; - } - - @NonNull - @Override - public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - if (viewType == REGULAR_VIEW) { - final View view = LayoutInflater.from(mContext).inflate(R.layout.dashboard_item2, - parent, false); - return new TwoItemsViewHolder(view); - } else { - final View view = LayoutInflater.from(mContext).inflate(R.layout.bond_dashboard_footer_view, - parent, false); - return new FooterViewHolder(view); - } - } - - private void setTwoItemsRowValues(ItemStructure view, DashboardItemEntity item) { - String measurementText = item.getMeasurementValue(); - final int unitStartIdx = measurementText.length(); - if (!item.getMeasurementUnit().isEmpty()) { - measurementText = measurementText.concat("\n").concat(item.getMeasurementUnit()); - } - final Spannable spannable = new SpannableString(measurementText); - spannable.setSpan(new TypefaceSpan(mContext.getString(R.string.roboto_light)), unitStartIdx, measurementText.length() - , Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - view.itemMeasurementView.setText(spannable); - if (item.getmViewType() == DashboardItemEntity.VIEW_TYPE_SHIELD) { - view.itemMeasurementView.setBackgroundResource(item.getIconResId()); - } else { - view.itemMeasurementView.setCompoundDrawablesWithIntrinsicBounds(0, item.getIconResId(), 0, 0); - } - view.itemTitleView.setText(item.getTitle()); - } - - @Override - public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { - if (holder instanceof TwoItemsViewHolder) { - final TwoItemsViewHolder viewHolder = (TwoItemsViewHolder) holder; - setTwoItemsRowValues(viewHolder.leftItem, mDashboardItems.get(position * 2)); - setTwoItemsRowValues(viewHolder.rightItem, mDashboardItems.get(position * 2 + 1)); - viewHolder.leftItem.changeState(mIsDashboardEnabled); - viewHolder.rightItem.changeState(mIsDashboardEnabled); - } else { - final FooterViewHolder viewHolder = (FooterViewHolder) holder; - if (mIsDashboardEnabled) { - viewHolder.resetButton.setOnClickListener(v -> new AlertDialog.Builder(v.getContext()) - .setTitle(R.string.bond_dashboard_clear_dialog_title) - .setMessage(R.string.bond_dashboard_clear_dialog_message) - .setPositiveButton(R.string.button_ok, (dialogInterface, i) -> { - mBus.post(new Messages.ClearDashboardData()); - }) - .setNegativeButton(R.string.cancel, null) - .show()); - } - } - } - - @Override - public int getItemCount() { - //The data is shown in groups of 2 and we have a footer item in the end. - return mDashboardItems.size() / 2 + 1; - } - - @Override - public int getItemViewType(int position) { - if (position == mDashboardItems.size() / 2) { - return FOOTER_VIEW; - } else { - return REGULAR_VIEW; - } - } - - void addItems(List items) { - mDashboardItems.clear(); - mDashboardItems.addAll(items); - notifyDataSetChanged(); - } - - void setIsDashboardEnabled(boolean isDashboardEnabled) { - mIsDashboardEnabled = isDashboardEnabled; - notifyDataSetChanged(); - } - - private enum ItemType { - ONE_VIEW, TWO_VIEWS - } - - private class ItemStructure { - RelativeLayout itemLayout; - TextView itemMeasurementView; - TextView itemTitleView; - - void changeState(boolean isEnabled) { - itemMeasurementView.setEnabled(isEnabled); - itemTitleView.setEnabled(isEnabled); - } - } - - private class TwoItemsViewHolder extends RecyclerView.ViewHolder { - final ItemStructure leftItem; - final ItemStructure rightItem; - - TwoItemsViewHolder(View itemView) { - super(itemView); - final RelativeLayout leftView = itemView.findViewById(R.id.left_item); - leftItem = findViews(leftView); - leftItem.itemLayout = leftView; - - leftItem.changeState(mIsDashboardEnabled); - final RelativeLayout rightView = itemView.findViewById(R.id.right_item); - rightItem = findViews(rightView); - rightItem.itemLayout = rightView; - rightItem.changeState(mIsDashboardEnabled); - } - - private ItemStructure findViews(View view) { - final ItemStructure item = new ItemStructure(); - item.itemTitleView = view.findViewById(R.id.item_title); - item.itemMeasurementView = view.findViewById(R.id.item_measurement); - return item; - } - } - - private class FooterViewHolder extends RecyclerView.ViewHolder { - final TextView resetButton; - - FooterViewHolder(View view) { - super(view); - resetButton = view.findViewById(R.id.reset); - } - } -} \ No newline at end of file diff --git a/app/src/lumen/java/com/cliqz/browser/controlcenter/DashboardFragment.java b/app/src/lumen/java/com/cliqz/browser/controlcenter/DashboardFragment.java deleted file mode 100644 index 478f3b98a..000000000 --- a/app/src/lumen/java/com/cliqz/browser/controlcenter/DashboardFragment.java +++ /dev/null @@ -1,174 +0,0 @@ -package com.cliqz.browser.controlcenter; - -import android.content.Context; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import com.cliqz.browser.R; -import com.cliqz.browser.app.BrowserApp; -import com.cliqz.browser.main.FlavoredActivityComponent; -import com.cliqz.browser.main.Messages; -import com.cliqz.browser.purchases.PurchasesManager; -import com.cliqz.jsengine.Insights; -import com.cliqz.jsengine.ReadableMapUtils; -import com.cliqz.nove.Bus; -import com.cliqz.nove.Subscribe; -import com.facebook.react.bridge.ReadableMap; - -import java.util.ArrayList; -import java.util.List; - -import javax.inject.Inject; - -import acr.browser.lightning.preference.PreferenceManager; - -import static com.cliqz.browser.controlcenter.DashboardItemEntity.VIEW_TYPE_ICON; -import static com.cliqz.browser.controlcenter.DashboardItemEntity.VIEW_TYPE_SHIELD; - -/** - * Copyright © Cliqz 2019 - */ -public class DashboardFragment extends ControlCenterFragment { - - private DashboardAdapter mDashboardAdapter; - private boolean mIsDailyView; - private final int[] tabsTitleIds = { - R.string.bond_dashboard_today_title, R.string.bond_dashboard_week_title}; - - @Inject - Bus bus; - - @Inject - Insights insights; - - @Inject - PurchasesManager purchasesManager; - - @Inject - PreferenceManager preferenceManager; - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - final Bundle arguments = getArguments(); - if (arguments != null) { - mIsDailyView = arguments.getBoolean(ControlCenterPagerAdapter.IS_TODAY); - } - final FlavoredActivityComponent component = getActivity() != null ? - BrowserApp.getActivityComponent(getActivity()) : null; - if (component != null) { - component.inject(this); - } - bus.register(this); - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - final View view = inflater.inflate(R.layout.bond_dashboard_fragment, container, - false); - final RecyclerView dashBoardListView = view.findViewById(R.id.dashboard_list_view); - mDashboardAdapter = new DashboardAdapter(getContext(), bus); - updateUI(); - dashBoardListView.setAdapter(mDashboardAdapter); - dashBoardListView.setLayoutManager(new LinearLayoutManager(getContext())); - changeDashboardState(preferenceManager.isAttrackEnabled() && preferenceManager.getAdBlockEnabled()); - return view; - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - updateUI(); - } - - public void changeDashboardState(boolean isEnabled) { - mDashboardAdapter.setIsDashboardEnabled(isEnabled); - } - - @Override - public String getTitle(Context context, int pos) { - return context.getString(tabsTitleIds[pos]); - } - - @Override - public void updateUI() { - if (mDashboardAdapter == null || getView() == null) { - return; - } - if (mIsDailyView) { - insights.getInsightsData((data) -> getView().post(() -> updateViews(data.getMap("result"))), "day"); - } else { - insights.getInsightsData((data) -> getView().post(() -> updateViews(data.getMap("result"))), "week"); - } - - } - - @Override - public void refreshUI() { - if (getView() == null) { - return; //return if view is not inflated yet - } - mDashboardAdapter.notifyDataSetChanged(); - } - - @Override - public void refreshUIComponent(boolean optionValue) { - changeDashboardState(optionValue); - } - - public void updateViews(ReadableMap data) { - if (data == null) { - return; - } - - final MeasurementWrapper dataSaved; - final MeasurementWrapper adsBlocked; - final MeasurementWrapper trackersDetected; - final MeasurementWrapper pagesVisited; - - if (preferenceManager.getAdBlockEnabled() && preferenceManager.isAttrackEnabled()) { - dataSaved = ValuesFormatterUtil.formatBytesCount(ReadableMapUtils.getSafeInt(data, "dataSaved")); - adsBlocked = ValuesFormatterUtil.formatBlockCount(ReadableMapUtils.getSafeInt(data,"adsBlocked")); - trackersDetected = ValuesFormatterUtil.formatBlockCount(ReadableMapUtils.getSafeInt(data, "trackersDetected")); - pagesVisited = ValuesFormatterUtil.formatBlockCount(ReadableMapUtils.getSafeInt(data,"pages")); - } else { - dataSaved = ValuesFormatterUtil.formatBytesCount(0); - adsBlocked = ValuesFormatterUtil.formatBlockCount(0); - trackersDetected = ValuesFormatterUtil.formatBlockCount(0); - pagesVisited = ValuesFormatterUtil.formatBlockCount(0); - } - - final List dashboardItems = new ArrayList<>(); - - dashboardItems.add(new DashboardItemEntity(adsBlocked.getValue(), - adsBlocked.getUnit() == 0 ? "" : getString(adsBlocked.getUnit()), - R.drawable.ic_ad_blocking_shiel, getString(R.string.bond_dashboard_ads_blocked_title), -1, VIEW_TYPE_SHIELD)); - dashboardItems.add(new DashboardItemEntity(trackersDetected.getValue(), "", - R.drawable.ic_eye, getString(R.string.bond_dashboard_tracking_companies_title), -1, VIEW_TYPE_ICON)); - dashboardItems.add(new DashboardItemEntity(dataSaved.getValue(), - dataSaved.getUnit() == 0 ? "" : getString(dataSaved.getUnit()), R.drawable.ic_ad_blocking_shiel, - getString(R.string.bond_dashboard_data_saved_title), -1, VIEW_TYPE_SHIELD)); - dashboardItems.add(new DashboardItemEntity(pagesVisited.getValue(), "", R.drawable.ic_anti_phishing_hook, - getString(R.string.bond_dashboard_phishing_protection_title), -1, VIEW_TYPE_ICON)); - mDashboardAdapter.addItems(dashboardItems); - } - - @Subscribe - void onPurchaseCompleted(Messages.PurchaseCompleted purchaseCompleted) { - if (purchasesManager.getPurchase().isDashboardEnabled()) { - bus.post(new Messages.EnableAdBlock()); - bus.post(new Messages.EnableAttrack()); - changeDashboardState(true); - } - } - -} \ No newline at end of file diff --git a/app/src/lumen/java/com/cliqz/browser/controlcenter/DashboardFragment.kt b/app/src/lumen/java/com/cliqz/browser/controlcenter/DashboardFragment.kt new file mode 100644 index 000000000..e58bd129f --- /dev/null +++ b/app/src/lumen/java/com/cliqz/browser/controlcenter/DashboardFragment.kt @@ -0,0 +1,175 @@ +package com.cliqz.browser.controlcenter + +import acr.browser.lightning.preference.PreferenceManager +import android.graphics.Color +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.cliqz.browser.R +import com.cliqz.browser.app.BrowserApp +import com.cliqz.browser.extensions.color +import com.cliqz.browser.extensions.tintVectorDrawable +import com.cliqz.browser.main.Messages +import com.cliqz.browser.purchases.PurchasesManager +import com.cliqz.jsengine.Insights +import com.cliqz.jsengine.ReadableMapUtils +import com.cliqz.nove.Bus +import com.cliqz.nove.Subscribe +import com.facebook.react.bridge.ReadableMap +import kotlinx.android.synthetic.lumen.bond_dashboard_fragment.* +import javax.inject.Inject + +class DashboardFragment : Fragment() { + + private var isDailyView = false + + @Inject + internal lateinit var bus: Bus + + @Inject + internal lateinit var insights: Insights + + @Inject + internal lateinit var purchasesManager: PurchasesManager + + @Inject + internal lateinit var preferenceManager: PreferenceManager + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + isDailyView = arguments?.getBoolean(ControlCenterPagerAdapter.IS_TODAY) ?: true + BrowserApp.getActivityComponent(activity)?.inject(this) + bus.register(this) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.bond_dashboard_fragment, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + changeDashboardState(purchasesManager.isDashboardEnabled() && + preferenceManager.isAttrackEnabled && preferenceManager.adBlockEnabled) + reset.setOnClickListener { + if (!preferenceManager.isAttrackEnabled && !preferenceManager.adBlockEnabled) { + return@setOnClickListener + } + android.app.AlertDialog.Builder(it.context) + .setTitle(R.string.bond_dashboard_clear_dialog_title) + .setMessage(R.string.bond_dashboard_clear_dialog_message) + .setPositiveButton(R.string.button_ok) { _, _ -> + bus.post(Messages.ClearDashboardData()) + } + .setNegativeButton(R.string.cancel, null) + .show() + } + updateUI() + } + + fun updateUI() { + val periodType = if (isDailyView) "day" else "week" + insights.getInsightsData({ + view?.post { + updateViews(it.getMap("result")) + } + }, periodType) + } + + fun refreshUIComponent(optionValue: Boolean) { + changeDashboardState(optionValue) + } + + private fun updateViews(data: ReadableMap?) { + if (data == null) return + + val dataSaved: MeasurementWrapper + val adsBlocked: MeasurementWrapper + val trackersDetected: MeasurementWrapper + val pagesVisited: MeasurementWrapper + + if (purchasesManager.isDashboardEnabled() && + preferenceManager.adBlockEnabled && preferenceManager.isAttrackEnabled) { + dataSaved = ValuesFormatterUtil.formatBytesCount(ReadableMapUtils.getSafeInt(data, "dataSaved")) + adsBlocked = ValuesFormatterUtil.formatBlockCount(ReadableMapUtils.getSafeInt(data, "adsBlocked")) + trackersDetected = ValuesFormatterUtil.formatBlockCount(ReadableMapUtils.getSafeInt(data, "trackersDetected")) + pagesVisited = ValuesFormatterUtil.formatBlockCount(ReadableMapUtils.getSafeInt(data, "pages")) + } else { + dataSaved = ValuesFormatterUtil.formatBytesCount(0) + adsBlocked = ValuesFormatterUtil.formatBlockCount(0) + trackersDetected = ValuesFormatterUtil.formatBlockCount(0) + pagesVisited = ValuesFormatterUtil.formatBlockCount(0) + } + + ads_blocked.text = adsBlocked.value + trackers_detected.text = trackersDetected.value + data_saved.text = dataSaved.value + phishing_checked.text = pagesVisited.value + + data_saved_icon.setImageResource(when (dataSaved.unit) { + R.string.bond_dashboard_units_kb -> R.drawable.ic_kb_data_saved_on + R.string.bond_dashboard_units_mb -> R.drawable.ic_mb_data_saved_on + R.string.bond_dashboard_units_gb -> R.drawable.ic_gb_data_saved_on + else -> throw IllegalArgumentException("Wrong unit for 'data saved' data") + }) + } + + private fun changeDashboardState(isEnabled: Boolean) { + if (isEnabled) { + ads_blocked_icon.clearColorFilter() + trackers_detected_icon.clearColorFilter() + data_saved_icon.clearColorFilter() + phishing_checked_icon.clearColorFilter() + context?.apply { + ads_blocked.setTextColor(Color.WHITE) + trackers_detected.setTextColor(Color.WHITE) + data_saved.setTextColor(Color.WHITE) + phishing_checked.setTextColor(Color.WHITE) + + ads_blocked_text.setTextColor(Color.WHITE) + trackers_detected_text.setTextColor(Color.WHITE) + data_saved_text.setTextColor(Color.WHITE) + phishing_checked_text.setTextColor(Color.WHITE) + + reset.setTextColor(color(R.color.lumen_color_blue_primary)) + + vertical_line.setBackgroundColor(color(R.color.lumen_color_blue_primary_opaque)) + horizontal_line.setBackgroundColor(color(R.color.lumen_color_blue_primary_opaque)) + } + } else { + ads_blocked_icon.tintVectorDrawable(R.color.lumen_color_grey_text) + trackers_detected_icon.tintVectorDrawable(R.color.lumen_color_grey_text) + data_saved_icon.tintVectorDrawable(R.color.lumen_color_grey_text) + phishing_checked_icon.tintVectorDrawable(R.color.lumen_color_grey_text) + context?.apply { + ads_blocked.setTextColor(color(R.color.lumen_color_grey_text)) + trackers_detected.setTextColor(color(R.color.lumen_color_grey_text)) + data_saved.setTextColor(color(R.color.lumen_color_grey_text)) + phishing_checked.setTextColor(color(R.color.lumen_color_grey_text)) + + ads_blocked_text.setTextColor(color(R.color.lumen_color_grey_text)) + trackers_detected_text.setTextColor(color(R.color.lumen_color_grey_text)) + data_saved_text.setTextColor(color(R.color.lumen_color_grey_text)) + phishing_checked_text.setTextColor(color(R.color.lumen_color_grey_text)) + + reset.setTextColor(color(R.color.lumen_color_grey_text)) + + vertical_line.setBackgroundColor(color(R.color.lumen_color_grey_text)) + horizontal_line.setBackgroundColor(color(R.color.lumen_color_grey_text)) + } + } + } + + @Subscribe + internal fun onPurchaseCompleted(purchaseCompleted: Messages.PurchaseCompleted) { + if (purchasesManager.purchase.isDashboardEnabled) { + bus.post(Messages.EnableAdBlock()) + bus.post(Messages.EnableAttrack()) + bus.post(Messages.OnDashboardStateChange()) + changeDashboardState(true) + } + } + +} \ No newline at end of file diff --git a/app/src/lumen/java/com/cliqz/browser/controlcenter/DashboardItemEntity.java b/app/src/lumen/java/com/cliqz/browser/controlcenter/DashboardItemEntity.java deleted file mode 100644 index 2a938cbf1..000000000 --- a/app/src/lumen/java/com/cliqz/browser/controlcenter/DashboardItemEntity.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.cliqz.browser.controlcenter; - -/** - * Copyright © Cliqz 2019 - */ -public class DashboardItemEntity { - - static final int VIEW_TYPE_SHIELD = 1; - static final int VIEW_TYPE_ICON = 2; - - private String mMeasurementValue; - private String mMeasurementUnit; - private int mIconResId; - private String mTitle; - private int mOptionValue; - private int mViewType; - - DashboardItemEntity(String measurementValue, String measurementUnit, int iconResId, String title, int optionValue, int viewType) { - this.mMeasurementValue = measurementValue; - this.mMeasurementUnit = measurementUnit; - this.mIconResId = iconResId; - this.mTitle = title; - this.mOptionValue = optionValue; - this.mViewType = viewType; - } - - String getMeasurementValue() { - return mMeasurementValue; - } - - String getMeasurementUnit() { - return mMeasurementUnit; - } - - int getIconResId() { - return mIconResId; - } - - public void setIconResId(int iconResId) { - this.mIconResId = iconResId; - } - - public String getTitle() { - return mTitle; - } - - public void setTitle(String title) { - this.mTitle = title; - } - - public int getmViewType() { - return mViewType; - } -} diff --git a/app/src/lumen/java/com/cliqz/browser/extensions/DrawableExtensions.kt b/app/src/lumen/java/com/cliqz/browser/extensions/DrawableExtensions.kt index a377f1d3e..0ff3c16ed 100644 --- a/app/src/lumen/java/com/cliqz/browser/extensions/DrawableExtensions.kt +++ b/app/src/lumen/java/com/cliqz/browser/extensions/DrawableExtensions.kt @@ -1,10 +1,46 @@ package com.cliqz.browser.extensions +import android.content.Context +import android.graphics.PorterDuff +import android.graphics.drawable.Drawable +import android.widget.ImageView import android.widget.TextView +import androidx.annotation.ColorInt +import androidx.annotation.ColorRes import androidx.annotation.DrawableRes +import androidx.appcompat.content.res.AppCompatResources import androidx.core.content.ContextCompat +import androidx.core.graphics.drawable.DrawableCompat fun TextView.drawableStart(@DrawableRes id: Int) { - val drawable = ContextCompat.getDrawable(context, id) + val drawable = AppCompatResources.getDrawable(context, id) setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null) } + +fun ImageView.setDrawable(@DrawableRes id: Int) { + setImageDrawable(AppCompatResources.getDrawable(context, id)) +} + +@ColorInt fun Context.getColorCompat(@ColorRes id: Int): Int { + return ContextCompat.getColor(this, id) +} + +fun ImageView.tint(@ColorRes id: Int) { + drawable.tint(context, id) +} + +fun Drawable.tint(@ColorInt value: Int): Drawable { + val tintedDrawable = DrawableCompat.wrap(this).mutate() + DrawableCompat.setTint(tintedDrawable, value) + return tintedDrawable +} + +fun Drawable.tint(context: Context, @ColorRes id: Int): Drawable { + return tint(context.getColorCompat(id)) +} + +fun ImageView.tintVectorDrawable(@ColorRes id: Int) { + setColorFilter(ContextCompat.getColor(context, id), PorterDuff.Mode.SRC_IN) +} + +fun Context.color(@ColorRes id: Int) = ContextCompat.getColor(this, id) diff --git a/app/src/lumen/java/com/cliqz/browser/main/FlavoredActivityComponent.java b/app/src/lumen/java/com/cliqz/browser/main/FlavoredActivityComponent.java index fef0b3822..9ab600fc3 100644 --- a/app/src/lumen/java/com/cliqz/browser/main/FlavoredActivityComponent.java +++ b/app/src/lumen/java/com/cliqz/browser/main/FlavoredActivityComponent.java @@ -5,8 +5,8 @@ import com.cliqz.browser.controlcenter.DashboardFragment; import com.cliqz.browser.purchases.PurchaseFragment; import com.cliqz.browser.purchases.PurchasesManager; -import com.cliqz.browser.starttab.FavoritesFragment; -import com.cliqz.browser.starttab.HistoryFragment; +import com.cliqz.browser.starttab.FavoritesView; +import com.cliqz.browser.starttab.HistoryView; import com.cliqz.browser.starttab.freshtab.FreshTab; import com.cliqz.browser.vpn.VpnHandler; import com.cliqz.browser.vpn.VpnPanel; @@ -26,9 +26,9 @@ public interface FlavoredActivityComponent extends MainActivityComponent { void inject(FreshTab freshTab); - void inject(HistoryFragment historyFragment); + void inject(HistoryView historyView); - void inject(FavoritesFragment favoritesFragment); + void inject(FavoritesView favoritesView); void inject(PurchaseFragment purchaseFragment); diff --git a/app/src/lumen/java/com/cliqz/browser/overview/OverviewFragment.java b/app/src/lumen/java/com/cliqz/browser/overview/OverviewFragment.java index 3a7e1ede0..69b5bed75 100644 --- a/app/src/lumen/java/com/cliqz/browser/overview/OverviewFragment.java +++ b/app/src/lumen/java/com/cliqz/browser/overview/OverviewFragment.java @@ -14,6 +14,7 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; +import com.cliqz.browser.BuildConfig; import com.cliqz.browser.R; import com.cliqz.browser.app.BrowserApp; import com.cliqz.browser.main.FlavoredActivityComponent; @@ -24,16 +25,10 @@ import javax.inject.Inject; -public class OverviewFragment extends Fragment { +public class OverviewFragment extends CommonOverviewFragment { private final static String TAB_OVERVIEW_TAG = "tab_overview_tag"; - @Inject - TabsManager tabsManager; - - @Inject - Bus bus; - public void setDisplayFavorites() { } @@ -53,12 +48,14 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c final Toolbar toolbar = view.findViewById(R.id.toolbar); final AppCompatActivity activity = (AppCompatActivity) getActivity(); assert activity != null; - toolbar.setTitle(activity.getString(R.string.overview)); + final String title = + activity.getString(BuildConfig.IS_LUMEN ? R.string.open_tabs : R.string.overview); + toolbar.setTitle(title); activity.setSupportActionBar(toolbar); final ActionBar actionBar = activity.getSupportActionBar(); if (actionBar != null) { actionBar.setDisplayHomeAsUpEnabled(true); - actionBar.setTitle(activity.getString(R.string.overview)); + actionBar.setTitle(title); } return view; diff --git a/app/src/lumen/java/com/cliqz/browser/purchases/PurchaseFragment.kt b/app/src/lumen/java/com/cliqz/browser/purchases/PurchaseFragment.kt index a362c9427..c6d4c663e 100644 --- a/app/src/lumen/java/com/cliqz/browser/purchases/PurchaseFragment.kt +++ b/app/src/lumen/java/com/cliqz/browser/purchases/PurchaseFragment.kt @@ -3,32 +3,28 @@ package com.cliqz.browser.purchases import acr.browser.lightning.preference.PreferenceManager import android.app.Activity import android.os.Bundle -import androidx.fragment.app.DialogFragment -import androidx.recyclerview.widget.LinearLayoutManager - import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Toast +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.DialogFragment +import androidx.recyclerview.widget.LinearLayoutManager import com.android.billingclient.api.BillingClient - import com.cliqz.browser.R import com.cliqz.browser.app.BrowserApp import com.cliqz.browser.main.MainActivity import com.cliqz.browser.main.Messages +import com.cliqz.browser.purchases.Products.PRODUCTS_LIST import com.cliqz.browser.purchases.productlist.OnBuyClickListener import com.cliqz.browser.purchases.productlist.ProductListAdapter import com.cliqz.browser.purchases.productlist.ProductRowData -import com.revenuecat.purchases.interfaces.ReceiveEntitlementsListener -import kotlinx.android.synthetic.lumen.fragment_purchase.* -import com.cliqz.browser.purchases.SubscriptionConstants.Entitlements -import com.cliqz.browser.purchases.SubscriptionConstants.Product import com.cliqz.nove.Bus import com.revenuecat.purchases.* -import java.util.* +import com.revenuecat.purchases.interfaces.ReceiveEntitlementsListener +import kotlinx.android.synthetic.lumen.fragment_purchase.* import javax.inject.Inject -import kotlin.collections.ArrayList private val TAG = PurchaseFragment::class.java.simpleName @@ -58,40 +54,63 @@ class PurchaseFragment : DialogFragment(), OnBuyClickListener { super.onViewCreated(view, savedInstanceState) BrowserApp.getActivityComponent(activity as MainActivity)?.inject(this) close_btn.setOnClickListener { dismiss() } - initRecyclerView() + initializeViews() setLoading(true) getProductDetails() } - private fun initRecyclerView() { - mAdapter = ProductListAdapter(context, null, this) + private fun initializeViews() { + mAdapter = ProductListAdapter(context, this) product_list.adapter = mAdapter product_list.layoutManager = LinearLayoutManager(context) + + restore_purchase.setOnClickListener { + checkExistingPurchases( + onSuccess = { activeSku -> + enableFeatures(activeSku) + Toast.makeText(context, getString(R.string.restore_subscription_success), + Toast.LENGTH_LONG).show() + }, + onError = { + Toast.makeText(context, R.string.restore_subscription_error_msg, + Toast.LENGTH_LONG).show() + } + ) + } } private fun getProductDetails() { Purchases.sharedInstance.getEntitlements(object : ReceiveEntitlementsListener { override fun onReceived(entitlementMap: Map) { - val productList = ArrayList() - var isSubscribed = false - entitlementMap[Entitlements.PREMIUM_SALE]?.offerings?.forEach { (_, offering) -> - offering.skuDetails?.apply { - if (sku == purchasesManager.purchase.sku) { - isSubscribed = true - productList.add(ProductRowData(sku, title, price, description, true)) - } else { - productList.add(ProductRowData(sku, title, price, description, false)) - } + val entitlement = + entitlementMap[Entitlements.PREMIUM_SALE] ?: + entitlementMap[Entitlements.PREMIUM_SALE_STAGING] + + val productSet = (entitlement?.offerings ?: emptyMap()) + .map { (_, off) -> off.activeProductIdentifier to off.skuDetails } + .toMap() + val productList = PRODUCTS_LIST.mapNotNull { sku -> + productSet[sku]?.let { detail -> + ProductRowData( + detail.sku, + detail.title, + detail.price, + detail.description, + detail.sku == purchasesManager.purchase.sku + ) } } + val subscription = productList.find { it.isSubscribed } + if (productList.isEmpty()) { - // TODO: Display error + Toast.makeText(context, "Error retrieving available products", Toast.LENGTH_LONG).show() + Log.e(TAG, "Product list is empty.") + dismiss() } else { - customSwapProductListElements(productList) - mAdapter.setHasSubscription(isSubscribed) + mAdapter.setSubscription(subscription) mAdapter.updateProductList(productList) - setLoading(false) } + setLoading(false) } override fun onError(error: PurchasesError) { @@ -101,61 +120,131 @@ class PurchaseFragment : DialogFragment(), OnBuyClickListener { }) } - /** - * The middle product in the list should always be "Basic + VPN". - */ - private fun customSwapProductListElements(productList: List) { - if (productList.size < 2) return - val middleElement = productList[1] - val basicVpnProductElement: ProductRowData = productList.find { it.sku == Product.BASIC_VPN } - ?: return - Collections.swap(productList, productList.indexOf(basicVpnProductElement), productList.indexOf(middleElement)) - } - fun setLoading(flag: Boolean) { product_list.visibility = if (flag) View.GONE else View.VISIBLE loading.visibility = if (flag) View.VISIBLE else View.GONE + restore_purchase.visibility = if (flag || purchasesManager.purchase.isASubscriber) { + View.GONE + } else { + View.VISIBLE + } } override fun onBuyClicked(position: Int) { + mAdapter.getProduct(position).apply { + val contentView = view?.rootView?.findViewById(android.R.id.content) + val progress = LayoutInflater.from(context) + .inflate(R.layout.fullscreen_progress, contentView, false) + contentView?.addView(progress) + + // Please notice, it is pointless to add an animation to make the progress disappear, + // the payment interface will appear in any case half a second after the animation ends + checkExistingPurchases( + onSuccess = {activeSku -> + contentView?.removeView(progress) + showRestorePurchasesDialog(sku = activeSku) + }, + onError = { + contentView?.removeView(progress) + makePurchase(sku) + } + ) + } + } + + private fun checkExistingPurchases(onSuccess: (activeSku: String) -> Unit, onError: () -> Unit) { + if (purchasesManager.purchase.sku.isNotEmpty()) { + onError() + return + } + Purchases.sharedInstance.restorePurchasesWith( + onError = { + Log.w(TAG, it.message) + onError() + }, + onSuccess = { + if (it.activeSubscriptions.isEmpty()) { + onError() + } else { + onSuccess(it.activeSubscriptions.first()) + } + } + ) + } + + private fun showRestorePurchasesDialog(sku: String) { + val productName = getProductNameById(sku) + AlertDialog.Builder(context!!) + .setTitle(R.string.restore_subscription_dialog_title) + .setMessage(getString(R.string.restore_subscription_dialog_desc, productName)) + .setPositiveButton(R.string.restore_subscription_dialog_positive_btn) { _, _ -> + enableFeatures(sku) + Toast.makeText(context, getString(R.string.restore_subscription_success), + Toast.LENGTH_LONG).show() + } + .setNegativeButton(R.string.cancel, null) + .show() + } + + private fun makePurchase(sku: String) { val oldSku = ArrayList() if (purchasesManager.purchase.sku.isNotEmpty()) { oldSku.add(purchasesManager.purchase.sku) } - mAdapter.getProduct(position)?.apply { - Purchases.sharedInstance.makePurchaseWith(activity as Activity, sku, - BillingClient.SkuType.SUBS, oldSku, - { error, _ -> - Log.e(TAG, error.underlyingErrorMessage) + Purchases.sharedInstance.makePurchaseWith( + activity as Activity, + sku, + BillingClient.SkuType.SUBS, + oldSku, + onError = { error, userCancelled -> + if (!userCancelled) { + Log.e(TAG, "${error.underlyingErrorMessage}") Toast.makeText(context, error.message, Toast.LENGTH_LONG).show() - }, - { purchase, _ -> - when (purchase.sku) { - Product.BASIC_VPN -> { - purchasesManager.purchase.isVpnEnabled = true - purchasesManager.purchase.isDashboardEnabled = true - preferenceManager.isAttrackEnabled = true - preferenceManager.adBlockEnabled = true - } - Product.BASIC -> { - purchasesManager.purchase.isVpnEnabled = false - purchasesManager.purchase.isDashboardEnabled = true - preferenceManager.isAttrackEnabled = true - preferenceManager.adBlockEnabled = true - } - Product.VPN -> { - purchasesManager.purchase.isVpnEnabled = true - purchasesManager.purchase.isDashboardEnabled = false - preferenceManager.isAttrackEnabled = false - preferenceManager.adBlockEnabled = false - } - } - purchasesManager.purchase.sku = purchase.sku - purchasesManager.purchase.isASubscriber = true - bus.post(Messages.PurchaseCompleted()) - this@PurchaseFragment.dismiss() } - ) + }, + onSuccess = { purchase, _ -> + enableFeatures(purchase.sku) + Toast.makeText(context, + getString(R.string.purchase_message_complete), + Toast.LENGTH_SHORT).show() + } + ) + } + + private fun enableFeatures(sku: String) { + when (sku) { + Products.BASIC_PLUS_VPN -> { + purchasesManager.purchase.isVpnEnabled = true + purchasesManager.purchase.isDashboardEnabled = true + preferenceManager.isAttrackEnabled = true + preferenceManager.adBlockEnabled = true + } + Products.BASIC -> { + purchasesManager.purchase.isVpnEnabled = false + purchasesManager.purchase.isDashboardEnabled = true + preferenceManager.isAttrackEnabled = true + preferenceManager.adBlockEnabled = true + } + Products.VPN -> { + purchasesManager.purchase.isVpnEnabled = true + purchasesManager.purchase.isDashboardEnabled = false + preferenceManager.isAttrackEnabled = false + preferenceManager.adBlockEnabled = false + } + } + purchasesManager.purchase.sku = sku + purchasesManager.purchase.isASubscriber = true + if (purchasesManager.purchase.isVpnEnabled) { + purchasesManager.loadTrialPeriodInfo() } + bus.post(Messages.PurchaseCompleted()) + dismiss() + } + + private fun getProductNameById(sku: String) = when (sku) { + Products.BASIC -> ProductName.BASIC + Products.VPN -> ProductName.VPN + Products.BASIC_PLUS_VPN -> ProductName.BASIC_VPN + else -> IllegalArgumentException("Invalid product id $sku") } } diff --git a/app/src/lumen/java/com/cliqz/browser/purchases/PurchasesManager.kt b/app/src/lumen/java/com/cliqz/browser/purchases/PurchasesManager.kt index 484be83c7..e0758f650 100644 --- a/app/src/lumen/java/com/cliqz/browser/purchases/PurchasesManager.kt +++ b/app/src/lumen/java/com/cliqz/browser/purchases/PurchasesManager.kt @@ -4,7 +4,6 @@ import acr.browser.lightning.preference.PreferenceManager import android.content.Context import android.util.Log import com.cliqz.browser.main.Messages -import com.cliqz.browser.purchases.SubscriptionConstants.Product import com.cliqz.browser.purchases.trial.ServerData import com.cliqz.browser.purchases.trial.TrialPeriodLocalRepo import com.cliqz.browser.purchases.trial.TrialPeriodRemoteRepo @@ -25,7 +24,6 @@ class PurchasesManager( ReceivePurchaserInfoListener { private val trialPeriodLocalRepo = TrialPeriodLocalRepo(preferenceManager) - private val trialPeriodRemoteRepo = TrialPeriodRemoteRepo(context) var purchase = Purchase() @@ -36,36 +34,29 @@ class PurchasesManager( override fun onTrialPeriodResponse(serverData: ServerData?) { this.serverData = serverData isLoading = false - preferenceManager.adBlockEnabled = serverData == null || serverData.isInTrial - preferenceManager.isAttrackEnabled = serverData == null || serverData.isInTrial - bus.post(Messages.OnTrialPeriodResponse()) + trialPeriodLocalRepo.saveTrialPeriodInfo(serverData) } override fun onReceived(purchaserInfo: PurchaserInfo) { if (purchaserInfo.activeSubscriptions.isNotEmpty()) { // If subscribed, enable features. for (sku in purchaserInfo.activeSubscriptions) { - val isVpnEnabled = sku == Product.VPN || sku == Product.BASIC_VPN - val isDashboardEnabled = sku == Product.BASIC || sku == Product.BASIC_VPN + val isVpnEnabled = sku == Products.VPN || sku == Products.BASIC_PLUS_VPN + val isDashboardEnabled = sku == Products.BASIC || sku == Products.BASIC_PLUS_VPN purchase.apply { this.isASubscriber = true this.isVpnEnabled = isVpnEnabled this.isDashboardEnabled = isDashboardEnabled this.sku = sku } - if (!isDashboardEnabled) { - preferenceManager.adBlockEnabled = false - preferenceManager.isAttrackEnabled = false - } } isLoading = false } else { purchase.isASubscriber = false - // Check if in trial period. - this.loadTrialPeriodInfo(this@PurchasesManager) } - + // Get Trial Period data and vpn username, password. + this.loadTrialPeriodInfo() } override fun onError(error: PurchasesError) { @@ -80,18 +71,12 @@ class PurchasesManager( } } - private fun loadTrialPeriodInfo(trialPeriodResponseListener: TrialPeriodResponseListener) { + fun loadTrialPeriodInfo() { // Read trial period object from cache trialPeriodLocalRepo.loadPurchaseInfo(object : TrialPeriodResponseListener { override fun onTrialPeriodResponse(serverData: ServerData?) { - trialPeriodResponseListener.onTrialPeriodResponse(serverData) - // TODO: Can avoid querying network if not needed. - trialPeriodRemoteRepo.loadPurchaseInfo(object : TrialPeriodResponseListener { - override fun onTrialPeriodResponse(serverData: ServerData?) { - trialPeriodResponseListener.onTrialPeriodResponse(serverData) - trialPeriodLocalRepo.saveTrialPeriodInfo(serverData) - } - }) + this@PurchasesManager.onTrialPeriodResponse(serverData) + TrialPeriodRemoteRepo(context, this@PurchasesManager).execute() } }) } diff --git a/app/src/lumen/java/com/cliqz/browser/purchases/SubscriptionConstants.kt b/app/src/lumen/java/com/cliqz/browser/purchases/SubscriptionConstants.kt index 45747e70c..7a9a26b13 100644 --- a/app/src/lumen/java/com/cliqz/browser/purchases/SubscriptionConstants.kt +++ b/app/src/lumen/java/com/cliqz/browser/purchases/SubscriptionConstants.kt @@ -1,19 +1,48 @@ package com.cliqz.browser.purchases -class SubscriptionConstants { +import com.cliqz.browser.BuildConfig - internal object Entitlements { - const val PREMIUM_PROMO = "[Staging] Premium Promo" - const val PREMIUM_SALE = "[Staging] Premium Sale" - } +internal object Entitlements { + const val PREMIUM_SALE_STAGING = "[Staging] Premium Sale" + const val PREMIUM_SALE = "Premium Sale" +} + +/** + * ProductIds on RevenueCat and Google Play Console. + */ +internal object Products { + val BASIC = if (BuildConfig.DEBUG) + "com.cliqz.android.lumen.staging.sale.basic" + else + "com.cliqz.android.lumen.sale.basic" + + val VPN = if (BuildConfig.DEBUG) + "com.cliqz.android.lumen.staging.sale.vpn" + else + "com.cliqz.android.lumen.sale.vpn" + + val BASIC_PLUS_VPN = if (BuildConfig.DEBUG) + "com.cliqz.android.lumen.staging.sale.basic_vpn" + else + "com.cliqz.android.lumen.sale.basic_vpn" /** - * Product ID on RevenueCat and Google Play Console. + * This is the order in which products should be displayed */ - internal object Product { - const val BASIC = "com.cliqz.android.lumen.staging.sale.basic" - const val VPN = "com.cliqz.android.lumen.staging.sale.vpn" - const val BASIC_VPN = "com.cliqz.android.lumen.staging.sale.basic_vpn" - } + val PRODUCTS_LIST = listOf(BASIC, BASIC_PLUS_VPN, VPN) + /** + * Maps a subscribed product to the ones to which the user can upgrade + */ + val UPGRADE_MAP = mapOf( + BASIC to setOf(BASIC_PLUS_VPN), + VPN to setOf(BASIC_PLUS_VPN), + BASIC_PLUS_VPN to emptySet()) + .withDefault { setOf(BASIC, VPN, BASIC_PLUS_VPN) } } + +internal object ProductName { + const val BASIC = "Basic Monthly" + const val VPN = "VPN Monthly" + const val BASIC_VPN = "Basic + VPN Monthly" +} \ No newline at end of file diff --git a/app/src/lumen/java/com/cliqz/browser/purchases/productlist/ProductListAdapter.kt b/app/src/lumen/java/com/cliqz/browser/purchases/productlist/ProductListAdapter.kt index b3631b7c3..36f7f13ff 100644 --- a/app/src/lumen/java/com/cliqz/browser/purchases/productlist/ProductListAdapter.kt +++ b/app/src/lumen/java/com/cliqz/browser/purchases/productlist/ProductListAdapter.kt @@ -6,7 +6,8 @@ import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import com.cliqz.browser.R -import com.cliqz.browser.purchases.SubscriptionConstants.Product +import com.cliqz.browser.purchases.Products +import com.cliqz.browser.purchases.Products.UPGRADE_MAP import kotlinx.android.synthetic.lumen.subscription_product_row_default.view.* interface OnBuyClickListener { @@ -17,11 +18,12 @@ const val LAYOUT_DEFAULT = 0 const val LAYOUT_HIGHLIGHTED = 1 class ProductListAdapter(private val context: Context?, - private var mListData: List?, private val onBuyClickListener: OnBuyClickListener) : RecyclerView.Adapter() { - private var hasSubscription = false + private var subscription: ProductRowData? = null + + private var mListData = emptyList() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductRowViewHolder { val layoutResource = when (viewType) { @@ -33,7 +35,7 @@ class ProductListAdapter(private val context: Context?, .inflate(layoutResource, parent, false)) } - override fun getItemCount() = mListData?.size ?: 0 + override fun getItemCount() = mListData.size override fun onBindViewHolder(holder: ProductRowViewHolder, position: Int) { holder.bindData(getProduct(position)) @@ -46,19 +48,21 @@ class ProductListAdapter(private val context: Context?, } override fun getItemViewType(position: Int): Int { - return mListData?.let { - if (it[position].sku == Product.BASIC_VPN) LAYOUT_HIGHLIGHTED else LAYOUT_DEFAULT - } ?: LAYOUT_DEFAULT + return if (mListData[position].sku == Products.BASIC_PLUS_VPN) { + LAYOUT_HIGHLIGHTED + } else { + LAYOUT_DEFAULT + } } - fun getProduct(position: Int) = mListData?.get(position) + fun getProduct(position: Int) = mListData[position] fun updateProductList(data: List) { mListData = data } - fun setHasSubscription(hasSubscription: Boolean) { - this.hasSubscription = hasSubscription + fun setSubscription(subscription: ProductRowData?) { + this.subscription = subscription } inner class ProductRowViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { @@ -73,16 +77,23 @@ class ProductListAdapter(private val context: Context?, itemView.title.text = title itemView.description.text = description itemView.price.text = context?.getString(R.string.per_month_text, price) - if (this@ProductListAdapter.hasSubscription) { - if (this.isSubscribed) { + val subscription = this@ProductListAdapter.subscription + when { + subscription == null -> { + itemView.is_subscribed?.visibility = View.GONE + itemView.buy_subscription.visibility = View.VISIBLE + } + this.isSubscribed -> { itemView.is_subscribed?.visibility = View.VISIBLE itemView.buy_subscription.visibility = View.GONE - } else { + } + UPGRADE_MAP[subscription.sku]?.contains(this.sku) ?: false -> { itemView.buy_subscription.text = context?.getString(R.string.upgrade_button) } - } else { - itemView.is_subscribed?.visibility = View.GONE - itemView.buy_subscription.visibility = View.VISIBLE + else -> { + itemView.is_subscribed?.visibility = View.GONE + itemView.buy_subscription.visibility = View.GONE + } } } } diff --git a/app/src/lumen/java/com/cliqz/browser/purchases/trial/TrialPeriodRemoteRepo.kt b/app/src/lumen/java/com/cliqz/browser/purchases/trial/TrialPeriodRemoteRepo.kt index f29d9b95e..38078aa34 100644 --- a/app/src/lumen/java/com/cliqz/browser/purchases/trial/TrialPeriodRemoteRepo.kt +++ b/app/src/lumen/java/com/cliqz/browser/purchases/trial/TrialPeriodRemoteRepo.kt @@ -1,13 +1,16 @@ package com.cliqz.browser.purchases.trial -import android.annotation.SuppressLint import android.content.Context import android.net.Uri import android.os.AsyncTask import android.provider.Settings import android.util.Log -import com.cliqz.browser.BuildConfig +import com.cliqz.browser.CliqzConfig +import com.cliqz.browser.app.BrowserApp +import com.cliqz.browser.main.Messages import com.cliqz.browser.utils.HttpHandler +import com.cliqz.browser.vpn.VpnCountries +import com.cliqz.nove.Bus import com.revenuecat.purchases.Purchases import de.blinkt.openvpn.ConfigConverter import de.blinkt.openvpn.core.ProfileManager @@ -18,98 +21,90 @@ import java.io.FileWriter import java.io.IOException import java.net.HttpURLConnection import java.net.URL +import javax.inject.Inject private val TAG = TrialPeriodRemoteRepo::class.java.simpleName private const val CONTENT_TYPE_JSON = "application/json" -private val LUMEN_CREDENTIAL_URL = URL("https://auth-staging.lumenbrowser.com/get_credentials") -private val HEADERS = mapOf("x-api-key" to BuildConfig.VPN_API_KEY) +private val HEADERS = mapOf("x-api-key" to CliqzConfig.VPN_API_KEY) -class TrialPeriodRemoteRepo(private val context: Context) { +class TrialPeriodRemoteRepo(private val context: Context, + private val trialPeriodResponseListener: TrialPeriodResponseListener) : AsyncTask() { - fun loadPurchaseInfo(trialPeriodResponseListener: TrialPeriodResponseListener) { - TrialPeriodHandler.TrialPeriodHandlerTask(context, object : TrialPeriodResponseListener { - override fun onTrialPeriodResponse(serverData: ServerData?) { - trialPeriodResponseListener.onTrialPeriodResponse(serverData) - } - }).execute() - } - - object TrialPeriodHandler { - - class TrialPeriodHandlerTask() : AsyncTask() { + @Inject + lateinit var bus: Bus - private lateinit var requestBody: String - - private lateinit var trialPeriodResponseListener: TrialPeriodResponseListener - - private lateinit var context: Context - - @SuppressLint("HardwareIds") - constructor(context: Context, trialPeriodResponseListener: TrialPeriodResponseListener) : this() { - this.context = context - this.trialPeriodResponseListener = trialPeriodResponseListener - requestBody = JSONObject() - .put("device_id", Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID)) - .put("revenue_cat_token", Purchases.sharedInstance.appUserID) - .toString() - } + init { + BrowserApp.getAppComponent()?.inject(this) + } - override fun doInBackground(vararg params: Void): ServerData { - val responseJSON = - HttpHandler.sendRequest("POST", LUMEN_CREDENTIAL_URL, CONTENT_TYPE_JSON, HEADERS, requestBody) - var trialDaysLeft = 0 - var userName = "" - var password = "" - if (responseJSON != null && responseJSON.get("status_code") == HttpURLConnection.HTTP_OK) { - trialDaysLeft = responseJSON.getJSONObject("body").getString("trial_days_left").toInt() - userName = responseJSON.getJSONObject("body").getJSONObject("credentials") - .getString("username") - password = responseJSON.getJSONObject("body").getJSONObject("credentials") - .getString("password") - val vpnCountries = responseJSON.getJSONObject("body").getJSONArray("nodes") - for (i in 0 until vpnCountries.length()) { - val country = vpnCountries.getJSONObject(i) - val countryCode = country.getString("countryCode") - val services = country.getJSONArray("services") - var openVpnConfig = "" - for (j in 0 until services.length()) { - val service = services.getJSONObject(j) - if (service.getString("name") == "openvpn" || service.getString("name") == "openvpn/standard") { - openVpnConfig = service.getString("config") - } - } - importVpnProfiles(countryCode, openVpnConfig) + override fun doInBackground(vararg params: Void): ServerData { + val requestBody = JSONObject() + .put("device_id", Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID)) + .put("revenue_cat_token", Purchases.sharedInstance.appUserID) + .toString() + val responseJSON = HttpHandler.sendRequest("POST", URL(CliqzConfig.CREDENTIAL_URL), + CONTENT_TYPE_JSON, HEADERS, requestBody) + var trialDaysLeft = 0 + var userName = "" + var password = "" + if (responseJSON != null && responseJSON.get("status_code") == HttpURLConnection.HTTP_OK) { + trialDaysLeft = responseJSON.getJSONObject("body").getString("trial_days_left").toInt() + userName = responseJSON.getJSONObject("body").getJSONObject("credentials") + .getString("username") + password = responseJSON.getJSONObject("body").getJSONObject("credentials") + .getString("password") + val vpnCountries = responseJSON.getJSONObject("body").getJSONArray("nodes") + for (i in 0 until vpnCountries.length()) { + val country = vpnCountries.getJSONObject(i) + val countryCode = country.getString("countryCode") + val services = country.getJSONArray("services") + var openVpnConfig = "" + for (j in 0 until services.length()) { + val service = services.getJSONObject(j) + if (service.getString("name") == "openvpn" || service.getString("name") == "openvpn/standard") { + openVpnConfig = service.getString("config") } } - return ServerData(trialDaysLeft > 0, trialDaysLeft, userName, password) + importVpnProfiles(countryCode, openVpnConfig, vpnCountries.length()) } + } + return ServerData(trialDaysLeft > 0, trialDaysLeft, userName, password) + } - override fun onPostExecute(serverData: ServerData) { - trialPeriodResponseListener.onTrialPeriodResponse(serverData) - } + override fun onPostExecute(serverData: ServerData) { + trialPeriodResponseListener.onTrialPeriodResponse(serverData) + } - private fun importVpnProfiles(countryCode: String, openVpnConfig: String) { - if (openVpnConfig.isEmpty()) { - return - } - val profileManager = ProfileManager.getInstance(context) - if (profileManager.getProfileByName(countryCode) == null) { - val outputDir = context.cacheDir - val outputFile = File.createTempFile(countryCode + "vpn", ".ovpn", outputDir) - outputFile.deleteOnExit() - try { - val bufferedWriter = BufferedWriter(FileWriter(outputFile)) - bufferedWriter.write(openVpnConfig) - bufferedWriter.close() - } catch (e: IOException) { - Log.e(TAG, "File write failed: $e") + private fun importVpnProfiles(countryCode: String, openVpnConfig: String, totalProfiles: Int) { + if (openVpnConfig.isEmpty()) { + return + } + val profileManager = ProfileManager.getInstance(context) + if (profileManager.getProfileByName(countryCode) == null) { + val outputDir = context.cacheDir + val outputFile = File.createTempFile(countryCode + "vpn", ".ovpn", outputDir) + outputFile.deleteOnExit() + try { + val bufferedWriter = BufferedWriter(FileWriter(outputFile)) + bufferedWriter.write(openVpnConfig) + bufferedWriter.close() + } catch (e: IOException) { + Log.e(TAG, "File write failed: $e") + } + val vpnConfigUri = Uri.fromFile(outputFile) + val configConverter = ConfigConverter(context.applicationContext) + configConverter.setOnProfileImportListener { + //No use of posting this message unless all profiles have been imported + if (totalProfiles == ProfileManager.getInstance(context).profiles.size) { + bus.post(Messages.OnAllProfilesImported()) + for (vpnProfile in ProfileManager.getInstance(context).profiles) { + vpnProfile.profileNameRes = VpnCountries.getCountryName(vpnProfile.name) + ProfileManager.getInstance(context).saveProfile(context, vpnProfile) } - val vpnConfigUri = Uri.fromFile(outputFile) - val configConverter = ConfigConverter(context.applicationContext) - configConverter.startImportTask(vpnConfigUri, countryCode) } } + configConverter.startImportTask(vpnConfigUri, countryCode) } } } diff --git a/app/src/lumen/java/com/cliqz/browser/starttab/FavoritesFragment.java b/app/src/lumen/java/com/cliqz/browser/starttab/FavoritesFragment.java deleted file mode 100644 index 76a7fe0b2..000000000 --- a/app/src/lumen/java/com/cliqz/browser/starttab/FavoritesFragment.java +++ /dev/null @@ -1,177 +0,0 @@ -package com.cliqz.browser.starttab; - -import android.os.Bundle; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.recyclerview.widget.ItemTouchHelper; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import com.cliqz.browser.R; -import com.cliqz.browser.app.BrowserApp; -import com.cliqz.browser.main.FavoriteModel; -import com.cliqz.browser.main.FavoritesAdapter; -import com.cliqz.browser.main.FlavoredActivityComponent; -import com.cliqz.browser.main.MainActivityHandler; -import com.cliqz.browser.webview.CliqzMessages; -import com.cliqz.jsengine.Engine; -import com.cliqz.nove.Bus; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.ArrayList; - -import javax.inject.Inject; - -import acr.browser.lightning.database.HistoryDatabase; -import butterknife.ButterKnife; - -/** - * @author Ravjit Uppal - */ -public class FavoritesFragment extends StartTabFragment { - - private static final String TAG = FavoritesFragment.class.getSimpleName(); - private FavoritesAdapter adapter; - private RecyclerView favoritesListView; - private final ArrayList favoritesList = new ArrayList<>(); - - @Inject - Engine engine; - - @Inject - MainActivityHandler handler; - - @Inject - Bus bus; - - @Inject - HistoryDatabase historyDatabase; - - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, - @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - favoritesListView = null; - final View view = inflater.inflate(R.layout.fragment_favorite, container, false); - ButterKnife.bind(this, view); - return view; - } - - @Override - public void onStart() { - super.onStart(); - final FlavoredActivityComponent component = BrowserApp.getActivityComponent(getActivity()); - if (component != null) { - component.inject(this); - } - prepareListData(); - if (checkNoFavorites()) { - return; - } - if (adapter == null) { - adapter = new FavoritesAdapter(favoritesList, engine, handler, new FavoritesAdapter.ClickListener() { - @Override - public void onClick(View view, int position) { - bus.post(CliqzMessages.OpenLink.open(favoritesList.get(position).getUrl())); - } - - @Override - public void onLongPress(View view, int position) { - } - }); - } - prepareRecyclerView(); - } - - private boolean checkNoFavorites() { - return favoritesList.size() == 0; - } - - private void prepareRecyclerView() { - if (favoritesListView != null) { - return; - } - final View view = getView(); - if (view == null) { - return; - } - favoritesListView = view.findViewById(R.id.history_rview); - favoritesListView.setLayoutManager(new LinearLayoutManager(getContext())); - - //callback to handle swipe and delete - final ItemTouchHelper.SimpleCallback simpleItemTouchCallback = - new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) { - - @Override - public int getSwipeDirs(@NonNull RecyclerView recyclerView, - @NonNull RecyclerView.ViewHolder viewHolder) { - //Dont swipe when contextual menu is enabled - return super.getSwipeDirs(recyclerView, viewHolder); - } - - @Override - public boolean onMove(@NonNull RecyclerView recyclerView, - @NonNull RecyclerView.ViewHolder viewHolder, - @NonNull RecyclerView.ViewHolder target) { - return false; - } - - @Override - public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { - final int position = viewHolder.getAdapterPosition(); - historyDatabase.setFavorites(favoritesList.get(position).getUrl(), null, - System.currentTimeMillis(), false); - favoritesList.remove(position); - adapter.notifyItemRemoved(position); - checkNoFavorites(); - } - }; - - //callbacks for click and long click on items - final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleItemTouchCallback); - itemTouchHelper.attachToRecyclerView(favoritesListView); - favoritesListView.setAdapter(adapter); - } - - private void prepareListData() { - favoritesList.clear(); - final JSONArray favoritesJsonArray = historyDatabase.getFavorites(); - try { - for (int i = 0; i < favoritesJsonArray.length(); i++) { - final JSONObject favoriteItem = favoritesJsonArray.getJSONObject(i); - favoritesList.add(new FavoriteModel(favoriteItem.optString(HistoryDatabase.HistoryKeys.URL), - favoriteItem.optString(HistoryDatabase.HistoryKeys.TITLE))); - } - } catch (JSONException e) { - Log.e(TAG, "error parsing favorites json", e); - } - } - - @Override - public String getTitle() { - return ""; - } - - @Override - public int getIconId() { - return R.drawable.ic_star_white; - } - - @Override - public void updateView() { - if (!isAdded() || adapter == null) { - return; - } - prepareListData(); - adapter.notifyDataSetChanged(); - } -} diff --git a/app/src/lumen/java/com/cliqz/browser/starttab/FavoritesView.java b/app/src/lumen/java/com/cliqz/browser/starttab/FavoritesView.java new file mode 100644 index 000000000..2c23a2ea7 --- /dev/null +++ b/app/src/lumen/java/com/cliqz/browser/starttab/FavoritesView.java @@ -0,0 +1,137 @@ +package com.cliqz.browser.starttab; + +import android.content.Context; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.FrameLayout; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.cliqz.browser.R; +import com.cliqz.browser.app.BrowserApp; +import com.cliqz.browser.main.FavoriteModel; +import com.cliqz.browser.main.FavoritesAdapter; +import com.cliqz.browser.main.FlavoredActivityComponent; +import com.cliqz.browser.main.MainActivityHandler; +import com.cliqz.browser.webview.CliqzMessages; +import com.cliqz.jsengine.Engine; +import com.cliqz.nove.Bus; + +import org.jetbrains.annotations.NotNull; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; + +import javax.inject.Inject; + +import acr.browser.lightning.database.HistoryDatabase; +import butterknife.BindView; +import butterknife.ButterKnife; + +/** + * @author Ravjit Uppal + */ +public class FavoritesView extends FrameLayout implements Updatable { + + private static final String TAG = FavoritesView.class.getSimpleName(); + private FavoritesAdapter adapter; + private final ArrayList favoritesList = new ArrayList<>(); + + @BindView(R.id.history_rview) + RecyclerView favoritesListView; + + @Inject + Engine engine; + + @Inject + MainActivityHandler handler; + + @Inject + Bus bus; + + @Inject + HistoryDatabase historyDatabase; + + + public FavoritesView(@NonNull Context context) { + super(context); + final View view = LayoutInflater.from(context).inflate(R.layout.fragment_favorite, this); + ButterKnife.bind(this, view); + + final FlavoredActivityComponent component = BrowserApp.getActivityComponent(context); + if (component != null) { + component.inject(this); + } + prepareListData(); + adapter = new FavoritesAdapter(favoritesList, engine, handler, new FavoritesAdapter.ClickListener() { + @Override + public void onClick(View view, int position) { + bus.post(CliqzMessages.OpenLink.open(favoritesList.get(position).getUrl())); + } + + @Override + public void onLongPress(View view, int position) { + } + }); + + favoritesListView.setLayoutManager(new LinearLayoutManager(context)); + final ItemTouchHelper itemTouchHelper =new ItemTouchHelper(new TouchCallback()); + itemTouchHelper.attachToRecyclerView(favoritesListView); + favoritesListView.setAdapter(adapter); + } + + @Override + public void update() { + prepareListData(); + adapter.notifyDataSetChanged(); + } + + private void prepareListData() { + favoritesList.clear(); + final JSONArray favoritesJsonArray = historyDatabase.getFavorites(); + try { + for (int i = 0; i < favoritesJsonArray.length(); i++) { + final JSONObject favoriteItem = favoritesJsonArray.getJSONObject(i); + favoritesList.add(new FavoriteModel(favoriteItem.optString(HistoryDatabase.HistoryKeys.URL), + favoriteItem.optString(HistoryDatabase.HistoryKeys.TITLE))); + } + } catch (JSONException e) { + Log.e(TAG, "error parsing favorites json", e); + } + } + + private class TouchCallback extends ItemTouchHelper.SimpleCallback { + TouchCallback() { + super(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT); + } + + @Override + public int getSwipeDirs(@NonNull RecyclerView recyclerView, + @NonNull RecyclerView.ViewHolder viewHolder) { + //Dont swipe when contextual menu is enabled + return super.getSwipeDirs(recyclerView, viewHolder); + } + + @Override + public boolean onMove(@NonNull RecyclerView recyclerView, + @NonNull RecyclerView.ViewHolder viewHolder, + @NonNull RecyclerView.ViewHolder target) { + return false; + } + + @Override + public void onSwiped(@NotNull RecyclerView.ViewHolder viewHolder, int direction) { + final int position = viewHolder.getAdapterPosition(); + historyDatabase.setFavorites(favoritesList.get(position).getUrl(), null, + System.currentTimeMillis(), false); + favoritesList.remove(position); + adapter.notifyItemRemoved(position); + } + } +} diff --git a/app/src/lumen/java/com/cliqz/browser/starttab/HistoryFragment.java b/app/src/lumen/java/com/cliqz/browser/starttab/HistoryFragment.java deleted file mode 100644 index 34c7f210b..000000000 --- a/app/src/lumen/java/com/cliqz/browser/starttab/HistoryFragment.java +++ /dev/null @@ -1,199 +0,0 @@ -package com.cliqz.browser.starttab; - -import android.database.Cursor; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.recyclerview.widget.ItemTouchHelper; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import com.cliqz.browser.R; -import com.cliqz.browser.app.BrowserApp; -import com.cliqz.browser.main.FlavoredActivityComponent; -import com.cliqz.browser.main.HistoryAdapter; -import com.cliqz.browser.main.HistoryModel; -import com.cliqz.browser.main.MainActivityHandler; -import com.cliqz.browser.main.Messages; -import com.cliqz.browser.webview.CliqzMessages; -import com.cliqz.jsengine.Engine; -import com.cliqz.nove.Bus; - -import java.util.ArrayList; - -import javax.inject.Inject; - -import acr.browser.lightning.database.HistoryDatabase; -import butterknife.BindView; -import butterknife.ButterKnife; - -/** - * @author Stefano Pacifici - * @author Ravjit Singh - */ -public class HistoryFragment extends StartTabFragment { - - private final ArrayList historyList = new ArrayList<>(); - private HistoryAdapter adapter; - - @BindView(R.id.history_rview) - RecyclerView historyListView; - - @Inject - Engine engine; - - @Inject - MainActivityHandler handler; - - @Inject - Bus bus; - - @Inject - HistoryDatabase historyDatabase; - - @Override - public void onStart() { - super.onStart(); - final FlavoredActivityComponent component = BrowserApp.getActivityComponent(getActivity()); - if (component != null) { - component.inject(this); - } - prepareListData(); - if (historyList.size() == 0) { - return; - } - if (adapter == null) { - adapter = new HistoryAdapter(historyList, engine, handler, new HistoryAdapter.ClickListener() { - @Override - public void onClick(View view, int position) { - //ignore click on date - if (adapter.getItemViewType(position) == HistoryAdapter.VIEW_TYPE_DATE) { - return; - } - if (adapter.getItemViewType(position) == HistoryAdapter.VIEW_TYPE_HISTORY) { - bus.post(CliqzMessages.OpenLink.openFromHistory(historyList.get(position).getUrl())); - } else if (adapter.getItemViewType(position) == HistoryAdapter.VIEW_TYPE_QUERY) { - bus.post(new Messages.OpenQuery(historyList.get(position).getUrl())); - } - } - - @Override - public void onLongPress(View view, int position) { - - } - }); - } - prepareRecyclerView(); - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, - @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - final View view = inflater.inflate(R.layout.fragment_history, container, false); - ButterKnife.bind(this, view); - return view; - } - - private void prepareRecyclerView() { - final View view = getView(); - if (view == null) { - return; - } - historyListView.setLayoutManager(new LinearLayoutManager(getContext())); - - //callback to handle swipe and delete - final ItemTouchHelper.SimpleCallback simpleItemTouchCallback = - new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) { - - @Override - public int getSwipeDirs(@NonNull RecyclerView recyclerView, - @NonNull RecyclerView.ViewHolder viewHolder) { - //Dont swipe date view and when contextual menu is enabled - if (viewHolder instanceof HistoryAdapter.DateViewHolder) { - return 0; - } - return super.getSwipeDirs(recyclerView, viewHolder); - } - - @Override - public boolean onMove(@NonNull RecyclerView recyclerView, - @NonNull RecyclerView.ViewHolder viewHolder, - @NonNull RecyclerView.ViewHolder target) { - return false; - } - - @Override - public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { - final int position = viewHolder.getAdapterPosition(); - final int type = viewHolder.getItemViewType(); - if (type == HistoryAdapter.VIEW_TYPE_HISTORY) { - historyDatabase.deleteHistoryPoint(historyList.get(position).getId()); - } else { - historyDatabase.deleteQuery(historyList.get(position).getId()); - } - historyList.remove(position); - adapter.notifyItemRemoved(position); - //check if date view is to be removed - if ((position == historyList.size() - || adapter.getItemViewType(position) == HistoryAdapter.VIEW_TYPE_DATE) - && adapter.getItemViewType(position - 1 ) == HistoryAdapter.VIEW_TYPE_DATE) { - historyList.remove(position-1); - adapter.notifyItemRemoved(position-1); - } - } - }; - - //callbacks for click and long click on items - final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleItemTouchCallback); - itemTouchHelper.attachToRecyclerView(historyListView); - historyListView.setAdapter(adapter); - historyListView.scrollToPosition(historyList.size()-1); - } - - private void prepareListData() { - //TODO historyDatabase.getHistoryItemsCount has to be modified to get the limit correctly; - historyList.clear(); - final Cursor cursor = historyDatabase.getHistoryItemsForRecyclerView(0, historyDatabase.getHistoryItemsCount()); - final int typeIndex = cursor.getColumnIndex("type"); - final int idIndex = cursor.getColumnIndex("id"); - final int urlIndex = cursor.getColumnIndex("url"); - final int titleIndex = cursor.getColumnIndex("title"); - final int timeIndex = cursor.getColumnIndex("date"); - while (cursor.moveToNext()) { - if (cursor.getInt(typeIndex) != HistoryAdapter.VIEW_TYPE_QUERY) { - historyList.add(new HistoryModel(cursor.getInt(idIndex), - cursor.getString(urlIndex), - cursor.getString(titleIndex), - cursor.getString(timeIndex), - cursor.getInt(typeIndex))); - } - } - cursor.close(); - } - - @Override - public String getTitle() { - return ""; - } - - @Override - public int getIconId() { - return R.drawable.ic_history_white; - } - - @Override - public void updateView() { - if (!isAdded() || adapter == null) { - return; - } - prepareListData(); - adapter.notifyDataSetChanged(); - historyListView.scrollToPosition(0); - } -} \ No newline at end of file diff --git a/app/src/lumen/java/com/cliqz/browser/starttab/HistoryView.java b/app/src/lumen/java/com/cliqz/browser/starttab/HistoryView.java new file mode 100644 index 000000000..af596a7fa --- /dev/null +++ b/app/src/lumen/java/com/cliqz/browser/starttab/HistoryView.java @@ -0,0 +1,100 @@ +package com.cliqz.browser.starttab; + +import android.content.Context; +import android.database.Cursor; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.FrameLayout; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.cliqz.browser.R; +import com.cliqz.browser.app.BrowserApp; +import com.cliqz.browser.main.FlavoredActivityComponent; +import com.cliqz.browser.main.HistoryAdapter; +import com.cliqz.browser.main.HistoryModel; +import com.cliqz.browser.main.MainActivityHandler; +import com.cliqz.jsengine.Engine; +import com.cliqz.nove.Bus; + +import java.util.ArrayList; + +import javax.inject.Inject; + +import acr.browser.lightning.database.HistoryDatabase; +import butterknife.BindView; +import butterknife.ButterKnife; + +/** + * @author Stefano Pacifici + * @author Ravjit Singh + */ +public class HistoryView extends FrameLayout implements Updatable { + + private final HistoryAdapter adapter; + + @BindView(R.id.history_rview) + RecyclerView historyListView; + + @Inject + Engine engine; + + @Inject + MainActivityHandler handler; + + @Inject + Bus bus; + + @Inject + HistoryDatabase historyDatabase; + + public HistoryView(@NonNull Context context) { + super(context); + final View view = LayoutInflater.from(context).inflate(R.layout.fragment_history, this); + ButterKnife.bind(this, view); + final FlavoredActivityComponent component = BrowserApp.getActivityComponent(context); + if (component != null) { + component.inject(this); + } + adapter = new HistoryAdapter(engine, handler, bus); + historyListView.setLayoutManager(new LinearLayoutManager(getContext())); + //callbacks for click and long click on items + final ItemTouchHelper itemTouchHelper = + new ItemTouchHelper(new HistoryItemTouchHelper(historyDatabase, adapter)); + itemTouchHelper.attachToRecyclerView(historyListView); + historyListView.scrollToPosition(0); + historyListView.setAdapter(adapter); + prepareListData(); + } + + private void prepareListData() { + //TODO historyDatabase.getHistoryItemsCount has to be modified to get the limit correctly; + final Cursor cursor = historyDatabase.getHistoryItemsForRecyclerView(0, historyDatabase.getHistoryItemsCount()); + final int typeIndex = cursor.getColumnIndex("type"); + final int idIndex = cursor.getColumnIndex("id"); + final int urlIndex = cursor.getColumnIndex("url"); + final int titleIndex = cursor.getColumnIndex("title"); + final int timeIndex = cursor.getColumnIndex("date"); + final ArrayList history = new ArrayList<>(cursor.getCount()); + while (cursor.moveToNext()) { + if (cursor.getInt(typeIndex) != HistoryAdapter.VIEW_TYPE_QUERY) { + history.add(new HistoryModel(cursor.getInt(idIndex), + cursor.getString(urlIndex), + cursor.getString(titleIndex), + cursor.getString(timeIndex), + cursor.getInt(typeIndex))); + } + } + cursor.close(); + adapter.setHistory(history); + } + + @Override + public void update() { + prepareListData(); + historyListView.scrollToPosition(0); + } +} \ No newline at end of file diff --git a/app/src/lumen/java/com/cliqz/browser/starttab/IconTabLayout.java b/app/src/lumen/java/com/cliqz/browser/starttab/IconTabLayout.java index 952f54785..0c58ec89e 100644 --- a/app/src/lumen/java/com/cliqz/browser/starttab/IconTabLayout.java +++ b/app/src/lumen/java/com/cliqz/browser/starttab/IconTabLayout.java @@ -3,13 +3,15 @@ import android.content.Context; import android.util.AttributeSet; +import androidx.annotation.DrawableRes; import androidx.annotation.Nullable; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentPagerAdapter; +import androidx.viewpager.widget.PagerAdapter; import androidx.viewpager.widget.ViewPager; import com.google.android.material.tabs.TabLayout; +import java.util.Objects; + /** * @author Ravjit Uppal * @@ -18,12 +20,10 @@ */ public class IconTabLayout extends TabLayout { - abstract static class ImageFragmentPagerAdapter extends FragmentPagerAdapter { - - ImageFragmentPagerAdapter(FragmentManager fm) { - super(fm); - } + abstract static class ImagePagerAdapter extends PagerAdapter { + @SuppressWarnings("WeakerAccess") + @DrawableRes public abstract int getIcon(int position); } @@ -42,9 +42,15 @@ public IconTabLayout(Context context, AttributeSet attrs, int defStyleAttr) { @Override public void setupWithViewPager(@Nullable ViewPager viewPager) { super.setupWithViewPager(viewPager); + final ImagePagerAdapter adapter = + viewPager != null ? (ImagePagerAdapter) viewPager.getAdapter() : null; + if (adapter == null) { + return; + } for (int i = 0; i < getTabCount(); i++) { - getTabAt(i).setText(""); - getTabAt(i).setIcon(((ImageFragmentPagerAdapter)viewPager.getAdapter()).getIcon(i)); + final TabLayout.Tab tab = Objects.requireNonNull(getTabAt(i)); + tab.setText(""); + tab.setIcon(adapter.getIcon(i)); } } } diff --git a/app/src/lumen/java/com/cliqz/browser/starttab/StartTabAdapter.java b/app/src/lumen/java/com/cliqz/browser/starttab/StartTabAdapter.java index 5b15fb27f..b65c12d21 100644 --- a/app/src/lumen/java/com/cliqz/browser/starttab/StartTabAdapter.java +++ b/app/src/lumen/java/com/cliqz/browser/starttab/StartTabAdapter.java @@ -1,56 +1,66 @@ package com.cliqz.browser.starttab; -import androidx.annotation.DrawableRes; -import androidx.annotation.Nullable; -import androidx.fragment.app.FragmentManager; +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; -import com.cliqz.browser.starttab.freshtab.FreshTab; - -import java.util.ArrayList; +import androidx.annotation.NonNull; -import acr.browser.lightning.preference.PreferenceManager; +import com.cliqz.browser.R; +import com.cliqz.browser.starttab.freshtab.FreshTab; /** * @author Ravjit Uppal */ -public class StartTabAdapter extends IconTabLayout.ImageFragmentPagerAdapter { +public class StartTabAdapter extends IconTabLayout.ImagePagerAdapter { - private final ArrayList mFragments = new ArrayList<>(); + private final View[] views; + private final int[] icons = new int[] { + R.drawable.ic_fresh_tab, + R.drawable.ic_history_tab, + R.drawable.ic_favorite_tab + }; + StartTabAdapter(@NonNull Context context) { + views = new View[] { + new FreshTab(context), + new HistoryView(context), + new FavoritesView(context) + }; + } - StartTabAdapter(FragmentManager fm, PreferenceManager preferenceManager) { - super(fm); - mFragments.clear(); - boolean isFreshInstall = preferenceManager.getIsFreshInstall(); - if (isFreshInstall) { - preferenceManager.setIsFreshInstall(false); - } - mFragments.add(FreshTab.newInstance(isFreshInstall)); - mFragments.add(new HistoryFragment()); - mFragments.add(new FavoritesFragment()); + @NonNull + @Override + public Object instantiateItem(@NonNull ViewGroup container, int position) { + final View view = views[position]; + container.addView(view); + return view; } @Override - public StartTabFragment getItem(int i) { - return mFragments.get(i); + public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) { + final View view = views[position]; + container.removeView(view); } @Override public int getCount() { - return mFragments.size(); + return views.length; } - @Nullable @Override - public CharSequence getPageTitle(int position) { - return getItem(position).getTitle(); + public boolean isViewFromObject(@NonNull View view, @NonNull Object object) { + return view.equals(object); } - @DrawableRes + @Override public int getIcon(int position) { - return getItem(position).getIconId(); + return icons[position]; } void updateView(int position) { - mFragments.get(position).updateView(); + final View view = views[position]; + if (view instanceof Updatable) { + ((Updatable) view).update(); + } } -} +} \ No newline at end of file diff --git a/app/src/lumen/java/com/cliqz/browser/starttab/StartTabContainer.java b/app/src/lumen/java/com/cliqz/browser/starttab/StartTabContainer.java index e587c26b7..df179a482 100644 --- a/app/src/lumen/java/com/cliqz/browser/starttab/StartTabContainer.java +++ b/app/src/lumen/java/com/cliqz/browser/starttab/StartTabContainer.java @@ -8,20 +8,28 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.content.ContextCompat; import androidx.fragment.app.FragmentManager; import androidx.viewpager.widget.ViewPager; import com.cliqz.browser.R; +import com.google.android.material.tabs.TabLayout; import acr.browser.lightning.preference.PreferenceManager; +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnPageChange; /** * @author Ravjit Uppal */ -public class StartTabContainer extends FrameLayout implements ViewPager.OnPageChangeListener { +public class StartTabContainer extends FrameLayout { - private ViewPager mViewPager; - private StartTabAdapter mStartTabAdapter; + @BindView(R.id.view_pager) + ViewPager mViewPager; + + private final StartTabAdapter mStartTabAdapter; public StartTabContainer(@NonNull Context context) { this(context, null); @@ -33,41 +41,34 @@ public StartTabContainer(@NonNull Context context, @Nullable AttributeSet attrs) public StartTabContainer(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - final LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - final View view = inflater.inflate(R.layout.starttab_container_layout, null); - addView(view); + final LayoutInflater inflater = LayoutInflater.from(context); + final View view = inflater.inflate(R.layout.starttab_container_layout, this); + ButterKnife.bind(this, view); + mStartTabAdapter = new StartTabAdapter(context); + final IconTabLayout pagerTabStrip = findViewById(R.id.tab_layout); + mViewPager.setAdapter(mStartTabAdapter); + pagerTabStrip.setupWithViewPager(mViewPager); } public void updateFreshTab() { + mViewPager.setCurrentItem(mViewPager.getCurrentItem()); + mStartTabAdapter.updateView(mViewPager.getCurrentItem()); } - public void init(FragmentManager supportFragmentManager, PreferenceManager preferenceManager) { - mViewPager = findViewById(R.id.view_pager); - mViewPager.addOnPageChangeListener(this); - mStartTabAdapter = new StartTabAdapter(supportFragmentManager, preferenceManager); - final IconTabLayout pagerTabStrip = findViewById(R.id.tab_layout); - mViewPager.setAdapter(mStartTabAdapter); - pagerTabStrip.setupWithViewPager(mViewPager); + public void gotToFavorites() { + mViewPager.setCurrentItem(2); } @Override public void setVisibility(int visibility) { super.setVisibility(visibility); if (visibility == VISIBLE) { - mStartTabAdapter.updateView(mViewPager.getCurrentItem()); + // mStartTabAdapter.updateView(mViewPager.getCurrentItem()); } } - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - } - - @Override - public void onPageSelected(int position) { + @OnPageChange(R.id.view_pager) + void onPageChange(int position) { mStartTabAdapter.updateView(position); } - - @Override - public void onPageScrollStateChanged(int state) { - } } diff --git a/app/src/lumen/java/com/cliqz/browser/starttab/StartTabFragment.java b/app/src/lumen/java/com/cliqz/browser/starttab/StartTabFragment.java deleted file mode 100644 index e13ec8bb0..000000000 --- a/app/src/lumen/java/com/cliqz/browser/starttab/StartTabFragment.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.cliqz.browser.starttab; - -import androidx.annotation.DrawableRes; -import androidx.fragment.app.Fragment; - -/** - * @author Ravjit Uppal - */ -abstract public class StartTabFragment extends Fragment { - - abstract public String getTitle(); - - abstract @DrawableRes - public int getIconId(); - - abstract public void updateView(); -} diff --git a/app/src/lumen/java/com/cliqz/browser/starttab/Updatable.java b/app/src/lumen/java/com/cliqz/browser/starttab/Updatable.java new file mode 100644 index 000000000..007259799 --- /dev/null +++ b/app/src/lumen/java/com/cliqz/browser/starttab/Updatable.java @@ -0,0 +1,5 @@ +package com.cliqz.browser.starttab; + +public interface Updatable { + void update(); +} diff --git a/app/src/lumen/java/com/cliqz/browser/starttab/freshtab/FreshTab.kt b/app/src/lumen/java/com/cliqz/browser/starttab/freshtab/FreshTab.kt index 561d5cbbe..ca24265fd 100644 --- a/app/src/lumen/java/com/cliqz/browser/starttab/freshtab/FreshTab.kt +++ b/app/src/lumen/java/com/cliqz/browser/starttab/freshtab/FreshTab.kt @@ -1,31 +1,34 @@ package com.cliqz.browser.starttab.freshtab import acr.browser.lightning.preference.PreferenceManager -import android.os.Bundle +import android.content.Context +import android.util.AttributeSet import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import android.view.animation.ScaleAnimation import android.widget.AdapterView +import android.widget.FrameLayout import com.cliqz.browser.R import com.cliqz.browser.app.BrowserApp import com.cliqz.browser.main.Messages import com.cliqz.browser.main.search.TopsitesAdapter import com.cliqz.browser.purchases.PurchasesManager -import com.cliqz.browser.starttab.StartTabFragment +import com.cliqz.browser.starttab.Updatable import com.cliqz.browser.telemetry.Telemetry import com.cliqz.browser.webview.CliqzMessages import com.cliqz.browser.webview.Topsite import com.cliqz.nove.Bus import com.cliqz.nove.Subscribe -import kotlinx.android.synthetic.lumen.fragment_freshtab.* -import kotlinx.android.synthetic.lumen.fragment_freshtab_trial_over_msg.* -import kotlinx.android.synthetic.lumen.fragment_freshtab_trial_upgrade_msg.* +import kotlinx.android.synthetic.lumen.fragment_freshtab.view.* +import kotlinx.android.synthetic.lumen.fragment_freshtab_trial_over_msg.view.* +import kotlinx.android.synthetic.lumen.fragment_freshtab_trial_upgrade_msg.view.* import javax.inject.Inject private const val SEVEN_DAYS_IN_MILLIS = 604800000L -internal class FreshTab : StartTabFragment() { +internal class FreshTab + @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleRes: Int = 0) : + FrameLayout(context, attrs, defStyleRes), Updatable { private var isFreshInstall: Boolean = false @@ -44,30 +47,11 @@ internal class FreshTab : StartTabFragment() { @Inject lateinit var telemetry: Telemetry - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?): View? { - isFreshInstall = arguments?.getBoolean(ARG_IS_FRESH_INSTALL) ?: false - BrowserApp.getActivityComponent(activity)?.inject(this) - return inflater.inflate(R.layout.fragment_freshtab, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - initializeViews() - } - - override fun onResume() { - super.onResume() - bus.register(this) - updateView() - } - - override fun onPause() { - super.onPause() - bus.unregister(this) - } + init { + BrowserApp.getActivityComponent(context)?.inject(this) + isFreshInstall = preferenceManager.isFreshInstall + LayoutInflater.from(context).inflate(R.layout.fragment_freshtab, this) - private fun initializeViews() { topsites_grid.adapter = topsitesAdapter topsites_grid.onItemClickListener = AdapterView.OnItemClickListener { _, view, position, _ -> if (topsitesAdapter.getItemViewType(position) == TopsitesAdapter.PLACEHOLDER_TYPE) { @@ -85,10 +69,26 @@ internal class FreshTab : StartTabFragment() { } else { getTrialPeriod(Messages.OnTrialPeriodResponse()) } + + trial_period_lumen_upgrade.setOnClickListener { + bus.post(Messages.GoToPurchase(0)) + } + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + bus.register(this) + update() + } + + override fun onDetachedFromWindow() { + bus.unregister(this) + super.onDetachedFromWindow() } @Subscribe fun getTrialPeriod(onTrialPeriodResponse: Messages.OnTrialPeriodResponse) { + if (purchasesManager.purchase.isASubscriber) return if (purchasesManager.serverData != null) { purchasesManager.serverData?.apply { if (isInTrial) { @@ -103,14 +103,7 @@ internal class FreshTab : StartTabFragment() { } } - override fun getTitle() = "" - - override fun getIconId() = R.drawable.ic_fresh_tab - - override fun updateView() { - if (!isAdded) { - return - } + override fun update() { topsitesAdapter.fetchTopsites() toggleTopSitesView(show = preferenceManager.shouldShowTopSites() && !topsitesAdapter.isEmpty) toggleWelcomeMessage(show = isFreshInstall && topsitesAdapter.isEmpty) @@ -136,9 +129,6 @@ internal class FreshTab : StartTabFragment() { trial_over_lumen_upgrade.visibility = View.GONE trial_period_upgrade_description.text = resources.getQuantityString(R.plurals.trial_period_upgrade_description, daysLeft, daysLeft) - trial_period_lumen_upgrade.setOnClickListener { - bus.post(Messages.GoToPurchase(0)) - } } private fun hideAllTrialPeriodViews() { @@ -160,16 +150,4 @@ internal class FreshTab : StartTabFragment() { hideAllTrialPeriodViews() } } - - companion object { - - const val ARG_IS_FRESH_INSTALL = "is_fresh_install" - - @JvmStatic - fun newInstance(isFreshInstall: Boolean) = FreshTab().apply{ - arguments = Bundle().apply { - putBoolean(ARG_IS_FRESH_INSTALL, isFreshInstall) - } - } - } } diff --git a/app/src/lumen/java/com/cliqz/browser/vpn/VpnCountries.java b/app/src/lumen/java/com/cliqz/browser/vpn/VpnCountries.java new file mode 100644 index 000000000..845ce409b --- /dev/null +++ b/app/src/lumen/java/com/cliqz/browser/vpn/VpnCountries.java @@ -0,0 +1,48 @@ +package com.cliqz.browser.vpn; + +import androidx.annotation.StringRes; + +import com.cliqz.browser.R; + +/** + * @author Ravjit Uppal + */ +public enum VpnCountries { + + AT(R.string.vpn_austria), + BA(R.string.vpn_bosnia), + BG(R.string.vpn_bulgaria), + CA(R.string.vpn_canada), + DE(R.string.vpn_germany), + ES(R.string.vpn_spain), + FR(R.string.vpn_france), + GR(R.string.vpn_greece), + HR(R.string.vpn_croatia), + HU(R.string.vpn_hungary), + IN(R.string.vpn_india), + IT(R.string.vpn_italy), + NL(R.string.vpn_netherlands), + PL(R.string.vpn_poland), + PT(R.string.vpn_portugal), + RO(R.string.vpn_romania), + RS(R.string.vpn_serbia), + TR(R.string.vpn_turkey), + UA(R.string.vpn_ukraine), + UK(R.string.vpn_uk), + US(R.string.vpn_usa), + VN(R.string.vpn_vietnam); + + public final @StringRes int countryName; + + VpnCountries(@StringRes int countryName) { + this.countryName = countryName; + } + + public static @StringRes int getCountryName(String value) { + try { + return VpnCountries.valueOf(value).countryName; + } catch (IllegalArgumentException e) { + return 0; + } + } +} diff --git a/app/src/lumen/java/com/cliqz/browser/vpn/VpnPanel.java b/app/src/lumen/java/com/cliqz/browser/vpn/VpnPanel.java index 8f180a8ce..259fc986f 100644 --- a/app/src/lumen/java/com/cliqz/browser/vpn/VpnPanel.java +++ b/app/src/lumen/java/com/cliqz/browser/vpn/VpnPanel.java @@ -9,16 +9,17 @@ import android.os.Bundle; import android.os.Handler; import android.os.SystemClock; -import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.Window; +import android.view.WindowManager; import android.view.animation.LinearInterpolator; import android.widget.Chronometer; import android.widget.ImageView; import android.widget.TextView; +import android.widget.Toast; import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; @@ -27,25 +28,27 @@ import com.cliqz.browser.R; import com.cliqz.browser.app.BrowserApp; +import com.cliqz.browser.extensions.DrawableExtensionsKt; import com.cliqz.browser.main.FlavoredActivityComponent; import com.cliqz.browser.main.Messages; import com.cliqz.browser.purchases.PurchasesManager; -import com.cliqz.browser.extensions.DrawableExtensionsKt; import com.cliqz.browser.webview.CliqzMessages; import com.cliqz.nove.Bus; import com.cliqz.nove.Subscribe; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import javax.inject.Inject; import acr.browser.lightning.preference.PreferenceManager; -import de.blinkt.openvpn.VpnProfile; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; +import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.core.ConnectionStatus; -import de.blinkt.openvpn.core.LogItem; +import de.blinkt.openvpn.core.OpenVPNService; import de.blinkt.openvpn.core.ProfileManager; import de.blinkt.openvpn.core.VpnStatus; @@ -57,11 +60,10 @@ public class VpnPanel extends DialogFragment implements VpnStatus.StateListener private static String TAG = VpnPanel.class.getSimpleName(); private static final String KEY_ANCHOR_HEIGHT = TAG + ".ANCHOR_HEIGHT"; - + public static final String ACTION_DISCONNECT_VPN = OpenVPNService.DISCONNECT_VPN; public static final int VPN_LAUNCH_REQUEST_CODE = 70; private int mAnchorHeight; - private boolean mSaveInstanceStateCalled = false; private Handler mMainHandler; private boolean mShouldAnimate = false; @@ -112,7 +114,6 @@ public static VpnPanel create(View source) { final Bundle arguments = new Bundle(); arguments.putInt(KEY_ANCHOR_HEIGHT, source.getHeight()); dialog.setArguments(arguments); - dialog.selectedProfile = ProfileManager.getInstance(source.getContext()).getProfiles().iterator().next(); return dialog; } @@ -130,13 +131,13 @@ public void onCreate(@Nullable Bundle savedInstanceState) { mAnchorHeight = arguments.getInt(KEY_ANCHOR_HEIGHT, 0); } bus.register(this); + setSelectedProfile(); } @Override public void onResume() { super.onResume(); vpnHandler.onResume(); - mSaveInstanceStateCalled = false; final Window window = getDialog().getWindow(); final Activity activity = getActivity(); final View contentView = activity != null ? activity.findViewById(android.R.id.content) : null; @@ -164,7 +165,11 @@ public void onPause() { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); ButterKnife.bind(this, view); - mSelectedCountry.setText(selectedProfile.getName()); + if (selectedProfile == null) { + mSelectedCountry.setText(R.string.germany); + } else { + mSelectedCountry.setText(selectedProfile.profileNameRes); + } mSelectedCountry.setPaintFlags(mSelectedCountry.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG); vpnMsgsChangeDrawable(VpnStatus.isVPNConnected() ? R.drawable.ic_check : R.drawable.ic_cross); @@ -173,6 +178,9 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat getString(R.string.vpn_cta_enabled) : getString(R.string.vpn_cta_disabled)); mMainHandler = new Handler(getContext().getMainLooper()); + final Window window = getDialog().getWindow(); + window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL); + getDialog().setCanceledOnTouchOutside(false); } @Nullable @@ -182,9 +190,14 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c return inflater.inflate(R.layout.home_vpn_panel, container, false); } + @OnClick(R.id.vpn_country) void vpnCountryClicked() { - showVpnCountriesDialog(); + if (selectedProfile != null && purchasesManager.isVpnEnabled()) { + showVpnCountriesDialog(); + } else { + unlockVpnDialog(); + } } @OnClick(R.id.vpn_connect_button) @@ -194,6 +207,10 @@ void connectClicked() { vpnHandler.disconnectVpn(); updateStateToConnect(); } else { + if (selectedProfile == null) { + Toast.makeText(getContext(), "Please try again later", Toast.LENGTH_LONG).show(); + return; + } vpnHandler.connectVpn(selectedProfile); } } else { @@ -214,15 +231,16 @@ void learnMoreClicked() { private void showVpnCountriesDialog() { final AlertDialog.Builder mBuilder = new AlertDialog.Builder(getContext()); final ArrayList vpnProfiles = new ArrayList<>(ProfileManager.getInstance(getContext()).getProfiles()); + Collections.sort(vpnProfiles, vpnListComparator); final String[] vpnCountryNames = new String[vpnProfiles.size()]; for (int i = 0; i < vpnProfiles.size(); i++) { - vpnCountryNames[i] = vpnProfiles.get(i).getName(); + vpnCountryNames[i] = getString(vpnProfiles.get(i).profileNameRes); } mBuilder.setTitle("Choose an item"); mBuilder.setSingleChoiceItems(vpnCountryNames, checkedItem, (dialogInterface, i) -> { checkedItem = i; selectedProfile = vpnProfiles.get(i); - mSelectedCountry.setText(selectedProfile.getName()); + mSelectedCountry.setText(selectedProfile.profileNameRes); dialogInterface.dismiss(); vpnHandler.disconnectVpn(); vpnHandler.connectVpn(selectedProfile); @@ -313,7 +331,7 @@ private void updateStateToConnected() { mVpnTimer.setVisibility(View.VISIBLE); mVpnTimer.setBase(SystemClock.elapsedRealtime() - (System.currentTimeMillis() - preferenceManager.getVpnStartTime())); mVpnTimer.start(); - mVpnButtonDesc.setText("disconnect"); + mVpnButtonDesc.setText(R.string.vpn_action_disconnect); mVpnConnectButton.setBackground(getResources().getDrawable(R.drawable.vpn_connect_button_bg)); mWorldMap.setImageResource(R.drawable.vpn_map_on); vpnMsgsChangeDrawable(R.drawable.ic_check); @@ -325,7 +343,7 @@ private void updateStateToConnect() { mVpnTimer.setVisibility(View.GONE); mVpnButtonTitle.setVisibility(View.VISIBLE); mVpnButtonTitle.setText("vpn"); - mVpnButtonDesc.setText("connect"); + mVpnButtonDesc.setText(R.string.vpn_action_connect); mVpnConnectButton.setBackground(getResources().getDrawable(R.drawable.vpn_connect_button_bg)); mWorldMap.setImageResource(R.drawable.vpn_map_off); vpnMsgsChangeDrawable(R.drawable.ic_cross); @@ -340,4 +358,49 @@ private void vpnMsgsChangeDrawable(@DrawableRes int id) { DrawableExtensionsKt.drawableStart(mVpnMsgLineOne, id); DrawableExtensionsKt.drawableStart(mVpnMsgLineTwo, id); } + + private void setSelectedProfile() { + final ArrayList vpnProfiles = + new ArrayList<>(ProfileManager.getInstance(getContext()).getProfiles()); + if (!vpnProfiles.isEmpty()) { + Collections.sort(vpnProfiles, vpnListComparator); + if (VpnStatus.isVPNConnected()) { + selectedProfile = ProfileManager.get(getContext(), VpnStatus.getLastConnectedVPNProfile()); + checkedItem = vpnProfiles.indexOf(selectedProfile); + } else { + selectedProfile = vpnProfiles.get(0); + checkedItem = 0; + } + } + } + + @Subscribe + public void onAllProfilesImported(Messages.OnAllProfilesImported onAllProfilesImported) { + setSelectedProfile(); + mSelectedCountry.setText(selectedProfile.profileNameRes); + mVpnCtaTitle.setText(R.string.vpn_cta_enabled); + } + + @Subscribe + void dismissVpnPanel(Messages.DismissVpnPanel event) { + dismissAllowingStateLoss(); + } + + private Comparator vpnListComparator = (v1, v2) -> { + final String country1 = getString(v1.profileNameRes); + final String country2 = getString(v2.profileNameRes); + if (v1.profileNameRes == R.string.vpn_usa && v2.profileNameRes == R.string.vpn_germany) { + return 1; + } + if (v1.profileNameRes == R.string.vpn_germany && v2.profileNameRes == R.string.vpn_usa) { + return -1; + } + if (v1.profileNameRes == R.string.vpn_usa || v1.profileNameRes == R.string.vpn_germany) { + return -1; + } + if (v2.profileNameRes == R.string.vpn_usa || v2.profileNameRes == R.string.vpn_germany) { + return 1; + } + return country1.compareTo(country2); + }; } \ No newline at end of file diff --git a/app/src/lumen/res/drawable-xxhdpi/lumen_logo_start_tab.png b/app/src/lumen/res/drawable-xxhdpi/lumen_logo_start_tab.png new file mode 100644 index 000000000..53c2f635f Binary files /dev/null and b/app/src/lumen/res/drawable-xxhdpi/lumen_logo_start_tab.png differ diff --git a/app/src/lumen/res/drawable/ic_action_back.xml b/app/src/lumen/res/drawable/ic_action_back.xml new file mode 100644 index 000000000..e88e0fa88 --- /dev/null +++ b/app/src/lumen/res/drawable/ic_action_back.xml @@ -0,0 +1,18 @@ + + + + diff --git a/app/src/lumen/res/drawable/ic_adblock_on.xml b/app/src/lumen/res/drawable/ic_adblock_on.xml new file mode 100644 index 000000000..bb7459422 --- /dev/null +++ b/app/src/lumen/res/drawable/ic_adblock_on.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + diff --git a/app/src/lumen/res/drawable/ic_antiphishing_on.xml b/app/src/lumen/res/drawable/ic_antiphishing_on.xml new file mode 100644 index 000000000..2f76b1ff0 --- /dev/null +++ b/app/src/lumen/res/drawable/ic_antiphishing_on.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + diff --git a/app/src/lumen/res/drawable/ic_antitracking_on.xml b/app/src/lumen/res/drawable/ic_antitracking_on.xml new file mode 100644 index 000000000..1d76baf8d --- /dev/null +++ b/app/src/lumen/res/drawable/ic_antitracking_on.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_clear_white.xml b/app/src/lumen/res/drawable/ic_clear.xml similarity index 65% rename from app/src/main/res/drawable/ic_clear_white.xml rename to app/src/lumen/res/drawable/ic_clear.xml index 66003fb7b..1e2d044be 100644 --- a/app/src/main/res/drawable/ic_clear_white.xml +++ b/app/src/lumen/res/drawable/ic_clear.xml @@ -1,8 +1,8 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> diff --git a/app/src/lumen/res/drawable/ic_close_black.xml b/app/src/lumen/res/drawable/ic_close_black.xml deleted file mode 100644 index 78a987db9..000000000 --- a/app/src/lumen/res/drawable/ic_close_black.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/lumen/res/drawable/ic_dashboard_checked.xml b/app/src/lumen/res/drawable/ic_dashboard_checked.xml new file mode 100644 index 000000000..652fce387 --- /dev/null +++ b/app/src/lumen/res/drawable/ic_dashboard_checked.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + diff --git a/app/src/lumen/res/drawable/ic_dashboard_off.xml b/app/src/lumen/res/drawable/ic_dashboard_off.xml index d3ca74824..9e59de417 100644 --- a/app/src/lumen/res/drawable/ic_dashboard_off.xml +++ b/app/src/lumen/res/drawable/ic_dashboard_off.xml @@ -1,18 +1,20 @@ - - - - - - - - - - - + + + diff --git a/app/src/lumen/res/drawable/ic_dashboard_on.xml b/app/src/lumen/res/drawable/ic_dashboard_on.xml index 5869d34c5..8668f8c2a 100644 --- a/app/src/lumen/res/drawable/ic_dashboard_on.xml +++ b/app/src/lumen/res/drawable/ic_dashboard_on.xml @@ -1,26 +1,27 @@ - - - - - - - - - - - - - - - - - - - + + + + + + + + + + diff --git a/app/src/lumen/res/drawable/ic_favorite_tab.xml b/app/src/lumen/res/drawable/ic_favorite_tab.xml new file mode 100644 index 000000000..6b9129960 --- /dev/null +++ b/app/src/lumen/res/drawable/ic_favorite_tab.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/lumen/res/drawable/ic_fresh_tab.xml b/app/src/lumen/res/drawable/ic_fresh_tab.xml index 373f7752b..f23257b24 100644 --- a/app/src/lumen/res/drawable/ic_fresh_tab.xml +++ b/app/src/lumen/res/drawable/ic_fresh_tab.xml @@ -1,5 +1,69 @@ - - + + + + + + + + + + diff --git a/app/src/lumen/res/drawable/ic_gb_data_saved_on.xml b/app/src/lumen/res/drawable/ic_gb_data_saved_on.xml new file mode 100644 index 000000000..a6d09255b --- /dev/null +++ b/app/src/lumen/res/drawable/ic_gb_data_saved_on.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/lumen/res/drawable/ic_history_tab.xml b/app/src/lumen/res/drawable/ic_history_tab.xml new file mode 100644 index 000000000..f9f9ee8be --- /dev/null +++ b/app/src/lumen/res/drawable/ic_history_tab.xml @@ -0,0 +1,27 @@ + + + + + diff --git a/app/src/lumen/res/drawable/ic_history_white.xml b/app/src/lumen/res/drawable/ic_history_white.xml deleted file mode 100644 index de25eb445..000000000 --- a/app/src/lumen/res/drawable/ic_history_white.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/lumen/res/drawable/ic_kb_data_saved_on.xml b/app/src/lumen/res/drawable/ic_kb_data_saved_on.xml new file mode 100644 index 000000000..4f20112f1 --- /dev/null +++ b/app/src/lumen/res/drawable/ic_kb_data_saved_on.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/lumen/res/drawable/ic_lock_lumen.xml b/app/src/lumen/res/drawable/ic_lock_lumen.xml new file mode 100644 index 000000000..ff576dc70 --- /dev/null +++ b/app/src/lumen/res/drawable/ic_lock_lumen.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/lumen/res/drawable/ic_mb_data_saved_on.xml b/app/src/lumen/res/drawable/ic_mb_data_saved_on.xml new file mode 100644 index 000000000..0c3a39082 --- /dev/null +++ b/app/src/lumen/res/drawable/ic_mb_data_saved_on.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/lumen/res/drawable/ic_reader_mode_off.xml b/app/src/lumen/res/drawable/ic_reader_mode_off.xml new file mode 100644 index 000000000..71c9c81d9 --- /dev/null +++ b/app/src/lumen/res/drawable/ic_reader_mode_off.xml @@ -0,0 +1,30 @@ + + + + + diff --git a/app/src/lumen/res/drawable/ic_reader_mode_on.xml b/app/src/lumen/res/drawable/ic_reader_mode_on.xml new file mode 100644 index 000000000..bc6307657 --- /dev/null +++ b/app/src/lumen/res/drawable/ic_reader_mode_on.xml @@ -0,0 +1,24 @@ + + + + + diff --git a/app/src/lumen/res/drawable/ic_tab_close.xml b/app/src/lumen/res/drawable/ic_tab_close.xml new file mode 100644 index 000000000..63f1c0e15 --- /dev/null +++ b/app/src/lumen/res/drawable/ic_tab_close.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/lumen/res/drawable/ic_vpn_off.xml b/app/src/lumen/res/drawable/ic_vpn_off.xml index 41b8d1e2c..100ede006 100644 --- a/app/src/lumen/res/drawable/ic_vpn_off.xml +++ b/app/src/lumen/res/drawable/ic_vpn_off.xml @@ -1,18 +1,18 @@ - - - - - - - - - - - + + + diff --git a/app/src/lumen/res/drawable/ic_vpn_on.xml b/app/src/lumen/res/drawable/ic_vpn_on.xml index c64fb3f05..fdf45f9ad 100644 --- a/app/src/lumen/res/drawable/ic_vpn_on.xml +++ b/app/src/lumen/res/drawable/ic_vpn_on.xml @@ -1,33 +1,31 @@ - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + diff --git a/app/src/lumen/res/drawable/lumen_logo_start_tab.xml b/app/src/lumen/res/drawable/logo_start_tab.xml similarity index 100% rename from app/src/lumen/res/drawable/lumen_logo_start_tab.xml rename to app/src/lumen/res/drawable/logo_start_tab.xml diff --git a/app/src/lumen/res/drawable/lumen_tab_layout_background.xml b/app/src/lumen/res/drawable/lumen_tab_layout_background.xml new file mode 100644 index 000000000..184cd83ea --- /dev/null +++ b/app/src/lumen/res/drawable/lumen_tab_layout_background.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/lumen/res/drawable/rounded_corner_solid_button.xml b/app/src/lumen/res/drawable/rounded_corner_solid_button.xml index 51f7008ac..f14ade2c5 100644 --- a/app/src/lumen/res/drawable/rounded_corner_solid_button.xml +++ b/app/src/lumen/res/drawable/rounded_corner_solid_button.xml @@ -3,12 +3,12 @@ + android:color="@color/lumen_color_blue_primary" /> + android:color="@color/lumen_color_blue_primary" /> + android:radius="4dp" /> \ No newline at end of file diff --git a/app/src/lumen/res/drawable/subscription_button_background.xml b/app/src/lumen/res/drawable/subscription_button_background.xml index e57dc6db5..bad8929e1 100644 --- a/app/src/lumen/res/drawable/subscription_button_background.xml +++ b/app/src/lumen/res/drawable/subscription_button_background.xml @@ -1,5 +1,5 @@ - - + + diff --git a/app/src/lumen/res/drawable/subscription_item_border.xml b/app/src/lumen/res/drawable/subscription_item_border.xml index 829163c63..165a2ca45 100644 --- a/app/src/lumen/res/drawable/subscription_item_border.xml +++ b/app/src/lumen/res/drawable/subscription_item_border.xml @@ -4,12 +4,12 @@ + android:bottom="4dp" + android:left="4dp" + android:right="4dp" + android:top="4dp"> - + diff --git a/app/src/lumen/res/drawable/subscription_item_hollow_border.xml b/app/src/lumen/res/drawable/subscription_item_hollow_border.xml index c03b2ff4e..e415ec901 100644 --- a/app/src/lumen/res/drawable/subscription_item_hollow_border.xml +++ b/app/src/lumen/res/drawable/subscription_item_hollow_border.xml @@ -3,9 +3,9 @@ diff --git a/app/src/lumen/res/drawable/tab_default_favicon.xml b/app/src/lumen/res/drawable/tab_default_favicon.xml new file mode 100644 index 000000000..7045f959e --- /dev/null +++ b/app/src/lumen/res/drawable/tab_default_favicon.xml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/lumen/res/drawable/tabs_overview_header_gradient.xml b/app/src/lumen/res/drawable/tabs_overview_header_gradient.xml new file mode 100644 index 000000000..e1d859d12 --- /dev/null +++ b/app/src/lumen/res/drawable/tabs_overview_header_gradient.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/lumen/res/drawable/tabs_title_color.xml b/app/src/lumen/res/drawable/tabs_title_color.xml new file mode 100644 index 000000000..981ef1357 --- /dev/null +++ b/app/src/lumen/res/drawable/tabs_title_color.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/lumen/res/layout-land/deckview_tab_layout.xml b/app/src/lumen/res/layout-land/deckview_tab_layout.xml new file mode 100644 index 000000000..7fdb3f45e --- /dev/null +++ b/app/src/lumen/res/layout-land/deckview_tab_layout.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/lumen/res/layout/bond_dashboard_footer_view.xml b/app/src/lumen/res/layout/bond_dashboard_footer_view.xml deleted file mode 100644 index 90683e0a3..000000000 --- a/app/src/lumen/res/layout/bond_dashboard_footer_view.xml +++ /dev/null @@ -1,16 +0,0 @@ - \ No newline at end of file diff --git a/app/src/lumen/res/layout/bond_dashboard_fragment.xml b/app/src/lumen/res/layout/bond_dashboard_fragment.xml index 0a2827b71..7b11d38d8 100644 --- a/app/src/lumen/res/layout/bond_dashboard_fragment.xml +++ b/app/src/lumen/res/layout/bond_dashboard_fragment.xml @@ -1,15 +1,212 @@ - - + + android:orientation="vertical" + tools:ignore="ContentDescription"> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + android:layout_height="wrap_content" + android:layout_alignParentBottom="true" + android:layout_centerInParent="true" + android:layout_marginBottom="8dp" + android:background="@color/primary_color" + android:gravity="center" + android:paddingTop="15dp" + android:paddingBottom="15dp" + android:text="@string/reset_statistics" + android:textAllCaps="true" + android:textColor="@color/lumen_color_blue_primary" + android:textStyle="bold" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toBottomOf="@id/vertical_line" + app:layout_constraintVertical_bias="1" /> - + diff --git a/app/src/lumen/res/layout/control_center_layout.xml b/app/src/lumen/res/layout/control_center_layout.xml index 8bbe10305..0a37240c4 100644 --- a/app/src/lumen/res/layout/control_center_layout.xml +++ b/app/src/lumen/res/layout/control_center_layout.xml @@ -50,12 +50,4 @@ android:layout_below="@+id/control_center_tab_layout" android:background="@android:color/transparent" /> - - diff --git a/app/src/lumen/res/layout/dashboard_item1.xml b/app/src/lumen/res/layout/dashboard_item1.xml deleted file mode 100644 index 0837b3e2d..000000000 --- a/app/src/lumen/res/layout/dashboard_item1.xml +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/lumen/res/layout/dashboard_item2.xml b/app/src/lumen/res/layout/dashboard_item2.xml deleted file mode 100644 index b254b9e1c..000000000 --- a/app/src/lumen/res/layout/dashboard_item2.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/lumen/res/layout/dashboard_item2_structure.xml b/app/src/lumen/res/layout/dashboard_item2_structure.xml deleted file mode 100644 index d9b7383c3..000000000 --- a/app/src/lumen/res/layout/dashboard_item2_structure.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/lumen/res/layout/deckview_tab_layout.xml b/app/src/lumen/res/layout/deckview_tab_layout.xml new file mode 100644 index 000000000..dedf33003 --- /dev/null +++ b/app/src/lumen/res/layout/deckview_tab_layout.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/lumen/res/layout/favorite_viewholder.xml b/app/src/lumen/res/layout/favorite_viewholder.xml new file mode 100644 index 000000000..f2898c78b --- /dev/null +++ b/app/src/lumen/res/layout/favorite_viewholder.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/lumen/res/layout/fragment_favorite.xml b/app/src/lumen/res/layout/fragment_favorite.xml index 2ee4d2702..01d30c1e0 100644 --- a/app/src/lumen/res/layout/fragment_favorite.xml +++ b/app/src/lumen/res/layout/fragment_favorite.xml @@ -3,5 +3,6 @@ android:id="@+id/history_rview" android:layout_width="match_parent" android:layout_height="match_parent" + android:paddingTop="24dp" android:background="@drawable/tab_fragment_background" android:paddingBottom="10dp" /> diff --git a/app/src/lumen/res/layout/fragment_freshtab.xml b/app/src/lumen/res/layout/fragment_freshtab.xml index 2a000828f..f1f49a9ad 100644 --- a/app/src/lumen/res/layout/fragment_freshtab.xml +++ b/app/src/lumen/res/layout/fragment_freshtab.xml @@ -11,11 +11,11 @@ layout="@layout/fragment_freshtab_welcome_msg" android:layout_width="wrap_content" android:layout_height="wrap_content" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toBottomOf="@id/trial_over_lumen_upgrade" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" - app:layout_constraintVertical_bias="0.392" /> + app:layout_constraintVertical_bias="0.18" /> + android:textColor="#92B6FF" + android:textSize="@dimen/normal_text_size" /> + android:textColor="#92B6FF" + android:textSize="@dimen/small_text_size" />