From df2f0906ae430a82b2cd19aaa2b01905d08c7414 Mon Sep 17 00:00:00 2001 From: Alex C Date: Wed, 24 Nov 2021 13:46:32 -0800 Subject: [PATCH] [Android] Brave news on NTP, integration with mojom and feature flag --- android/brave_java_resources.gni | 20 +- android/brave_java_sources.gni | 15 + .../chrome/browser/BraveFeatureList.java | 1 + .../chrome/browser/app/BraveActivity.java | 124 +- .../BraveTabbedAppMenuPropertiesDelegate.java | 7 + .../brave_news/BraveNewsAdapterFeedCard.java | 105 ++ .../BraveNewsBottomSheetDialogFragment.java | 124 ++ .../BraveNewsControllerFactory.java | 53 + .../browser/brave_news/BraveNewsUtils.java | 82 + .../brave_news/CardBuilderFeedCard.java | 1421 +++++++++++++++++ .../brave_news/models/FeedItemCard.java | 49 + .../brave_news/models/FeedItemsCard.java | 79 + .../browser/ntp/BraveNewTabPageLayout.java | 883 +++++++++- .../ntp_background_images/util/NTPUtil.java | 117 +- .../preferences/BravePrefServiceBridge.java | 23 + .../preferences/BravePreferenceKeys.java | 4 + .../browser/settings/BraveAddNewsSources.java | 154 ++ .../settings/BraveMainPreferencesBase.java | 5 +- .../settings/BraveNewsCategorySources.java | 185 +++ .../settings/BraveNewsPreferences.java | 197 +++ .../browser/settings/SearchPreference.java | 45 + .../toolbar/top/BraveToolbarLayoutImpl.java | 1 - .../browser/util/ConfigurationUtils.java | 38 +- .../drawable-hdpi/sparkling_newsreader.png | Bin 0 -> 878 bytes .../drawable-mdpi/sparkling_newsreader.png | Bin 0 -> 612 bytes .../drawable-xhdpi/sparkling_newsreader.png | Bin 0 -> 1230 bytes android/java/res/drawable/card_bg.xml | 7 + android/java/res/drawable/ic_close.xml | 9 + android/java/res/drawable/ic_news.xml | 11 + .../java/res/drawable/ic_news_settings.xml | 11 + android/java/res/drawable/ic_promoted.xml | 10 + android/java/res/drawable/news_button_bg.xml | 6 + .../java/res/drawable/rounded_top_corners.xml | 6 + .../java/res/layout/brave_news_card_menu.xml | 78 + .../layout/brave_news_load_new_content.xml | 39 + android/java/res/layout/brave_news_row.xml | 13 + .../layout/brave_news_settings_bar_layout.xml | 33 + .../res/layout/custom_layout_preference.xml | 13 + .../java/res/layout/new_tab_page_layout.xml | 26 + android/java/res/layout/optin_layout.xml | 97 ++ android/java/res/layout/search_preference.xml | 13 + .../java/res/values-night/brave_colors.xml | 7 + android/java/res/values/brave_colors.xml | 10 + android/java/res/values/brave_ids.xml | 1 + android/java/res/values/brave_styles.xml | 9 + .../java/res/xml/brave_main_preferences.xml | 12 +- .../java/res/xml/brave_news_preferences.xml | 33 + .../res/xml/brave_news_sources_default.xml | 10 + browser/BUILD.gn | 1 - browser/about_flags.cc | 14 + .../preferences/brave_pref_service_bridge.cc | 21 + browser/brave_content_browser_client.cc | 11 +- browser/brave_news/BUILD.gn | 21 + .../brave_news_controller_factory_android.cc | 27 + .../brave_news_controller_factory.cc | 15 + .../brave_news_controller_factory.h | 7 + browser/brave_news/sources.gni | 34 +- browser/brave_profile_prefs.cc | 13 +- ...browser_context_keyed_service_factories.cc | 12 +- .../brave_browsing_data_remover_delegate.cc | 19 +- browser/profiles/brave_profile_manager.cc | 13 +- browser/ui/BUILD.gn | 14 +- .../android/strings/android_brave_strings.grd | 69 +- .../ui/webui/new_tab_page/brave_new_tab_ui.cc | 29 +- .../ui/webui/new_tab_page/brave_new_tab_ui.h | 18 +- build/android/BUILD.gn | 4 + build/android/config.gni | 5 + .../flags/android/chrome_feature_list.cc | 4 +- components/brave_ads/browser/BUILD.gn | 7 +- .../brave_ads/browser/ads_service_impl.cc | 21 +- components/brave_new_tab_ui/BUILD.gn | 2 +- components/brave_today/browser/BUILD.gn | 6 +- .../browser/brave_news_controller.cc | 9 + .../browser/brave_news_controller.h | 1 + components/brave_today/browser/test/BUILD.gn | 2 +- components/brave_today/buildflags/BUILD.gn | 12 - .../brave_today/buildflags/buildflags.gni | 9 - components/brave_today/common/BUILD.gn | 13 +- .../brave_today/common/buildflags/BUILD.gn | 7 + .../common/buildflags/buildflags.gni | 3 + components/brave_today/common/features.cc | 24 + components/brave_today/common/features.h | 23 + .../src/bat/ads/internal/ads_impl.cc | 5 + 83 files changed, 4483 insertions(+), 198 deletions(-) create mode 100644 android/java/org/chromium/chrome/browser/brave_news/BraveNewsAdapterFeedCard.java create mode 100644 android/java/org/chromium/chrome/browser/brave_news/BraveNewsBottomSheetDialogFragment.java create mode 100644 android/java/org/chromium/chrome/browser/brave_news/BraveNewsControllerFactory.java create mode 100644 android/java/org/chromium/chrome/browser/brave_news/BraveNewsUtils.java create mode 100644 android/java/org/chromium/chrome/browser/brave_news/CardBuilderFeedCard.java create mode 100644 android/java/org/chromium/chrome/browser/brave_news/models/FeedItemCard.java create mode 100644 android/java/org/chromium/chrome/browser/brave_news/models/FeedItemsCard.java create mode 100644 android/java/org/chromium/chrome/browser/settings/BraveAddNewsSources.java create mode 100644 android/java/org/chromium/chrome/browser/settings/BraveNewsCategorySources.java create mode 100644 android/java/org/chromium/chrome/browser/settings/BraveNewsPreferences.java create mode 100644 android/java/org/chromium/chrome/browser/settings/SearchPreference.java create mode 100644 android/java/res/drawable-hdpi/sparkling_newsreader.png create mode 100644 android/java/res/drawable-mdpi/sparkling_newsreader.png create mode 100644 android/java/res/drawable-xhdpi/sparkling_newsreader.png create mode 100644 android/java/res/drawable/card_bg.xml create mode 100644 android/java/res/drawable/ic_close.xml create mode 100644 android/java/res/drawable/ic_news.xml create mode 100644 android/java/res/drawable/ic_news_settings.xml create mode 100644 android/java/res/drawable/ic_promoted.xml create mode 100644 android/java/res/drawable/news_button_bg.xml create mode 100644 android/java/res/drawable/rounded_top_corners.xml create mode 100644 android/java/res/layout/brave_news_card_menu.xml create mode 100644 android/java/res/layout/brave_news_load_new_content.xml create mode 100644 android/java/res/layout/brave_news_row.xml create mode 100644 android/java/res/layout/brave_news_settings_bar_layout.xml create mode 100644 android/java/res/layout/custom_layout_preference.xml create mode 100644 android/java/res/layout/optin_layout.xml create mode 100644 android/java/res/layout/search_preference.xml create mode 100644 android/java/res/xml/brave_news_preferences.xml create mode 100644 android/java/res/xml/brave_news_sources_default.xml create mode 100644 browser/brave_news/BUILD.gn create mode 100644 browser/brave_news/android/brave_news_controller_factory_android.cc delete mode 100644 components/brave_today/buildflags/BUILD.gn delete mode 100644 components/brave_today/buildflags/buildflags.gni create mode 100644 components/brave_today/common/buildflags/BUILD.gn create mode 100644 components/brave_today/common/buildflags/buildflags.gni create mode 100644 components/brave_today/common/features.cc create mode 100644 components/brave_today/common/features.h diff --git a/android/brave_java_resources.gni b/android/brave_java_resources.gni index 9b0e2fbcccc..766a16ac145 100644 --- a/android/brave_java_resources.gni +++ b/android/brave_java_resources.gni @@ -98,6 +98,7 @@ brave_java_resources = [ "java/res/drawable-hdpi/smiley_happy.png", "java/res/drawable-hdpi/smiley_neutral.png", "java/res/drawable-hdpi/smiley_sad.png", + "java/res/drawable-hdpi/sparkling_newsreader.png", "java/res/drawable-hdpi/uphold_green.png", "java/res/drawable-hdpi/uphold_white.png", "java/res/drawable-hdpi/verified_creator_large.png", @@ -192,6 +193,7 @@ brave_java_resources = [ "java/res/drawable-mdpi/smiley_happy.png", "java/res/drawable-mdpi/smiley_neutral.png", "java/res/drawable-mdpi/smiley_sad.png", + "java/res/drawable-mdpi/sparkling_newsreader.png", "java/res/drawable-mdpi/uphold_green.png", "java/res/drawable-mdpi/uphold_white.png", "java/res/drawable-mdpi/verified_creator_large.png", @@ -290,6 +292,7 @@ brave_java_resources = [ "java/res/drawable-xhdpi/smiley_happy.png", "java/res/drawable-xhdpi/smiley_neutral.png", "java/res/drawable-xhdpi/smiley_sad.png", + "java/res/drawable-xhdpi/sparkling_newsreader.png", "java/res/drawable-xhdpi/uphold_green.png", "java/res/drawable-xhdpi/uphold_white.png", "java/res/drawable-xhdpi/verified_creator_large.png", @@ -528,6 +531,7 @@ brave_java_resources = [ "java/res/drawable/brave_sync_toggle_on_btn_right.xml", "java/res/drawable/brave_sync_top_rounded.xml", "java/res/drawable/brave_sync_warning.xml", + "java/res/drawable/card_bg.xml", "java/res/drawable/circular_progress.xml", "java/res/drawable/crypto_wallet_blue_button.xml", "java/res/drawable/default_dot.xml", @@ -559,6 +563,7 @@ brave_java_resources = [ "java/res/drawable/ic_chevron_left.xml", "java/res/drawable/ic_chevron_right.xml", "java/res/drawable/ic_clock.xml", + "java/res/drawable/ic_close.xml", "java/res/drawable/ic_closing_all_closes_brave.xml", "java/res/drawable/ic_copy.xml", "java/res/drawable/ic_crypto_wallets.xml", @@ -584,6 +589,8 @@ brave_java_resources = [ "java/res/drawable/ic_mask.xml", "java/res/drawable/ic_more_horiz.xml", "java/res/drawable/ic_new_tab_page.xml", + "java/res/drawable/ic_news.xml", + "java/res/drawable/ic_news_settings.xml", "java/res/drawable/ic_notification.xml", "java/res/drawable/ic_onboarding_graphic_android_brave_ads.xml", "java/res/drawable/ic_onboarding_graphic_auto_contribute.xml", @@ -597,6 +604,7 @@ brave_java_resources = [ "java/res/drawable/ic_phone.xml", "java/res/drawable/ic_plans.xml", "java/res/drawable/ic_privacy_reports.xml", + "java/res/drawable/ic_promoted.xml", "java/res/drawable/ic_qr_code.xml", "java/res/drawable/ic_qr_code_18.xml", "java/res/drawable/ic_rate.xml", @@ -634,6 +642,7 @@ brave_java_resources = [ "java/res/drawable/image_credit_rounded_shape.xml", "java/res/drawable/modern_toolbar_background_grey_end_segment.xml", "java/res/drawable/monthly_selector.xml", + "java/res/drawable/news_button_bg.xml", "java/res/drawable/ntp_widget_indicator_bg.xml", "java/res/drawable/orange_rounded_button.xml", "java/res/drawable/plain_checkbox.xml", @@ -656,6 +665,7 @@ brave_java_resources = [ "java/res/drawable/rounded_filled_bg_radius_12.xml", "java/res/drawable/rounded_holo_button.xml", "java/res/drawable/rounded_shape.xml", + "java/res/drawable/rounded_top_corners.xml", "java/res/drawable/rounded_wallet_edittext.xml", "java/res/drawable/rounded_white_bg.xml", "java/res/drawable/rounded_white_holo_bg.xml", @@ -692,7 +702,6 @@ brave_java_resources = [ "java/res/layout/activity_brave_vpn_profile.xml", "java/res/layout/activity_brave_vpn_support.xml", "java/res/layout/activity_brave_wallet.xml", - "java/res/layout/activity_brave_wallet.xml", "java/res/layout/activity_buy_send_swap.xml", "java/res/layout/activity_ntp_widget_stack.xml", "java/res/layout/activity_onboarding.xml", @@ -712,6 +721,10 @@ brave_java_resources = [ "java/res/layout/brave_bottom_new_tab_button.xml", "java/res/layout/brave_custom_tabs_toolbar.xml", "java/res/layout/brave_dialog_preference.xml", + "java/res/layout/brave_news_card_menu.xml", + "java/res/layout/brave_news_load_new_content.xml", + "java/res/layout/brave_news_row.xml", + "java/res/layout/brave_news_settings_bar_layout.xml", "java/res/layout/brave_rewards_auto_contrib.xml", "java/res/layout/brave_rewards_donation_sent.xml", "java/res/layout/brave_rewards_onboarding_ac_layout.xml", @@ -755,6 +768,7 @@ brave_java_resources = [ "java/res/layout/brave_webrtc_policy_preference.xml", "java/res/layout/bre_bottom_banner.xml", "java/res/layout/copy_save_bottom_sheet.xml", + "java/res/layout/custom_layout_preference.xml", "java/res/layout/edit_visible_assets_bottom_sheet.xml", "java/res/layout/fragment_accounts.xml", "java/res/layout/fragment_backup_wallet.xml", @@ -790,12 +804,14 @@ brave_java_resources = [ "java/res/layout/ntp_bottom_sheet.xml", "java/res/layout/ntp_non_disruptive_banner.xml", "java/res/layout/ntp_widget_list_item_layout.xml", + "java/res/layout/optin_layout.xml", "java/res/layout/qa_code_check.xml", "java/res/layout/qa_command_line.xml", "java/res/layout/radio_button_group_ens_resolve_method_preference.xml", "java/res/layout/radio_button_group_unstoppable_domains_resolve_method_preference.xml", "java/res/layout/recovery_phrase_item_layout.xml", "java/res/layout/report_broken_site_layout.xml", + "java/res/layout/search_preference.xml", "java/res/layout/swap_bottom_sheet.xml", "java/res/layout/tab_switcher_toolbar.xml", "java/res/layout/toolbar_space.xml", @@ -838,6 +854,8 @@ brave_java_resources = [ "java/res/xml/brave_download_preferences.xml", "java/res/xml/brave_license_preferences.xml", "java/res/xml/brave_main_preferences.xml", + "java/res/xml/brave_news_preferences.xml", + "java/res/xml/brave_news_sources_default.xml", "java/res/xml/brave_privacy_preferences.xml", "java/res/xml/brave_rewards_debug_preferences.xml", "java/res/xml/brave_rewards_preferences.xml", diff --git a/android/brave_java_sources.gni b/android/brave_java_sources.gni index 03279413e21..4636c97190a 100644 --- a/android/brave_java_sources.gni +++ b/android/brave_java_sources.gni @@ -5,6 +5,7 @@ import("//brave/browser/brave_ads/android/java_sources.gni") import("//brave/components/brave_referrals/buildflags/buildflags.gni") +import("//brave/components/brave_today/common/buildflags/buildflags.gni") import("//brave/components/permissions/android/java_sources.gni") import("//brave/components/safetynet/java_sources.gni") import("//components/feed/features.gni") @@ -44,6 +45,12 @@ brave_java_sources = [ "../../brave/android/java/org/chromium/chrome/browser/appmenu/BraveTabbedAppMenuPropertiesDelegate.java", "../../brave/android/java/org/chromium/chrome/browser/autofill/BraveAutofillPopupBridge.java", "../../brave/android/java/org/chromium/chrome/browser/bookmarks/BraveBookmarkUtils.java", + "../../brave/android/java/org/chromium/chrome/browser/brave_news/BraveNewsAdapterFeedCard.java", + "../../brave/android/java/org/chromium/chrome/browser/brave_news/BraveNewsBottomSheetDialogFragment.java", + "../../brave/android/java/org/chromium/chrome/browser/brave_news/BraveNewsUtils.java", + "../../brave/android/java/org/chromium/chrome/browser/brave_news/CardBuilderFeedCard.java", + "../../brave/android/java/org/chromium/chrome/browser/brave_news/models/FeedItemCard.java", + "../../brave/android/java/org/chromium/chrome/browser/brave_news/models/FeedItemsCard.java", "../../brave/android/java/org/chromium/chrome/browser/brave_stats/BraveStatsBottomSheetDialogFragment.java", "../../brave/android/java/org/chromium/chrome/browser/brave_stats/BraveStatsUtil.java", "../../brave/android/java/org/chromium/chrome/browser/compositor/layouts/BraveLayoutManagerChrome.java", @@ -198,11 +205,14 @@ brave_java_sources = [ "../../brave/android/java/org/chromium/chrome/browser/safe_browsing/settings/BraveStandardProtectionSettingsFragment.java", "../../brave/android/java/org/chromium/chrome/browser/settings/AppearancePreferences.java", "../../brave/android/java/org/chromium/chrome/browser/settings/BackgroundImagesPreferences.java", + "../../brave/android/java/org/chromium/chrome/browser/settings/BraveAddNewsSources.java", "../../brave/android/java/org/chromium/chrome/browser/settings/BraveCustomTabsPreference.java", "../../brave/android/java/org/chromium/chrome/browser/settings/BraveDialogPreference.java", "../../brave/android/java/org/chromium/chrome/browser/settings/BraveLicensePreference.java", "../../brave/android/java/org/chromium/chrome/browser/settings/BraveLicensePreferences.java", "../../brave/android/java/org/chromium/chrome/browser/settings/BraveMainPreferencesBase.java", + "../../brave/android/java/org/chromium/chrome/browser/settings/BraveNewsCategorySources.java", + "../../brave/android/java/org/chromium/chrome/browser/settings/BraveNewsPreferences.java", "../../brave/android/java/org/chromium/chrome/browser/settings/BravePreferenceDialogFragment.java", "../../brave/android/java/org/chromium/chrome/browser/settings/BravePreferenceFragment.java", "../../brave/android/java/org/chromium/chrome/browser/settings/BraveRewardsPreferences.java", @@ -222,6 +232,7 @@ brave_java_sources = [ "../../brave/android/java/org/chromium/chrome/browser/settings/BraveWebrtcPolicyPreference.java", "../../brave/android/java/org/chromium/chrome/browser/settings/BraveWebrtcPolicyPreferencesFragment.java", "../../brave/android/java/org/chromium/chrome/browser/settings/SearchEngineTabModelSelectorObserver.java", + "../../brave/android/java/org/chromium/chrome/browser/settings/SearchPreference.java", "../../brave/android/java/org/chromium/chrome/browser/settings/VpnCalloutPreference.java", "../../brave/android/java/org/chromium/chrome/browser/settings/developer/BraveQAPreferences.java", "../../brave/android/java/org/chromium/chrome/browser/settings/developer/BraveRewardsDebugPreferences.java", @@ -302,6 +313,10 @@ if (enable_brave_referrals) { brave_java_sources += [ "../../brave/android/java/org/chromium/chrome/browser/util/BraveReferrer.java" ] } +if (brave_news_enabled) { + brave_java_sources += [ "../../brave/android/java/org/chromium/chrome/browser/brave_news/BraveNewsControllerFactory.java" ] +} + brave_browser_ui_android_appmenu_internal_sources = [ "//brave/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/BraveAppMenu.java" ] brave_browser_ui_android_toolbar_sources = [ diff --git a/android/java/org/chromium/chrome/browser/BraveFeatureList.java b/android/java/org/chromium/chrome/browser/BraveFeatureList.java index 9c11714ef68..0e0a468da96 100644 --- a/android/java/org/chromium/chrome/browser/BraveFeatureList.java +++ b/android/java/org/chromium/chrome/browser/BraveFeatureList.java @@ -12,6 +12,7 @@ import org.chromium.base.annotations.NativeMethods; public abstract class BraveFeatureList { public static final String BRAVE_REWARDS = "BraveRewards"; public static final String NATIVE_BRAVE_WALLET = "NativeBraveWallet"; + public static final String BRAVE_NEWS = "BraveNews"; public static final String USE_DEV_UPDATER_URL = "UseDevUpdaterUrl"; public static final String FORCE_WEB_CONTENTS_DARK_MODE = "WebContentsForceDark"; public static final String ENABLE_FORCE_DARK = "enable-force-dark"; diff --git a/android/java/org/chromium/chrome/browser/app/BraveActivity.java b/android/java/org/chromium/chrome/browser/app/BraveActivity.java index dda8f1c481b..2138b1622a8 100644 --- a/android/java/org/chromium/chrome/browser/app/BraveActivity.java +++ b/android/java/org/chromium/chrome/browser/app/BraveActivity.java @@ -17,6 +17,7 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.graphics.drawable.BitmapDrawable; import android.net.ConnectivityManager; import android.net.Network; import android.net.NetworkCapabilities; @@ -31,6 +32,9 @@ import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.RelativeLayout; +import android.widget.ScrollView; import androidx.annotation.NonNull; import androidx.core.app.NotificationCompat; @@ -61,6 +65,7 @@ import org.chromium.base.task.TaskTraits; import org.chromium.chrome.R; import org.chromium.chrome.browser.ApplicationLifetime; import org.chromium.chrome.browser.BraveConfig; +import org.chromium.chrome.browser.BraveFeatureList; import org.chromium.chrome.browser.BraveHelper; import org.chromium.chrome.browser.BraveRelaunchUtils; import org.chromium.chrome.browser.BraveRewardsHelper; @@ -74,23 +79,27 @@ import org.chromium.chrome.browser.LaunchIntentDispatcher; import org.chromium.chrome.browser.SetDefaultBrowserActivity; import org.chromium.chrome.browser.bookmarks.BookmarkBridge; import org.chromium.chrome.browser.bookmarks.BookmarkModel; +import org.chromium.chrome.browser.brave_news.models.FeedItemsCard; import org.chromium.chrome.browser.brave_stats.BraveStatsUtil; import org.chromium.chrome.browser.browsing_data.BrowsingDataBridge; import org.chromium.chrome.browser.browsing_data.BrowsingDataType; import org.chromium.chrome.browser.browsing_data.ClearBrowsingDataFragmentAdvanced; import org.chromium.chrome.browser.browsing_data.TimePeriod; +import org.chromium.chrome.browser.compositor.CompositorViewHolder; import org.chromium.chrome.browser.compositor.layouts.Layout; import org.chromium.chrome.browser.compositor.layouts.LayoutManagerChrome; import org.chromium.chrome.browser.compositor.layouts.LayoutManagerImpl; import org.chromium.chrome.browser.compositor.layouts.phone.StackLayout; import org.chromium.chrome.browser.crypto_wallet.activities.BraveWalletActivity; import org.chromium.chrome.browser.dependency_injection.ChromeActivityComponent; +import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.flags.ChromeSwitches; import org.chromium.chrome.browser.fullscreen.BrowserControlsManager; import org.chromium.chrome.browser.informers.BraveAndroidSyncDisabledInformer; import org.chromium.chrome.browser.notifications.BraveSetDefaultBrowserNotificationService; import org.chromium.chrome.browser.notifications.retention.RetentionNotificationUtil; import org.chromium.chrome.browser.ntp.NewTabPage; +import org.chromium.chrome.browser.ntp_background_images.util.NewTabPageListener; import org.chromium.chrome.browser.onboarding.BraveTalkOptInPopupListener; import org.chromium.chrome.browser.onboarding.OnboardingActivity; import org.chromium.chrome.browser.onboarding.OnboardingPrefManager; @@ -142,6 +151,7 @@ import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; /** * Brave's extension for ChromeActivity @@ -192,6 +202,8 @@ public abstract class BraveActivity extends C private String mPurchaseToken = ""; private String mProductId = ""; private boolean mIsVerification; + public CompositorViewHolder compositorView; + public View inflatedSettingsBarLayout; @SuppressLint("VisibleForTests") public BraveActivity() { @@ -217,6 +229,8 @@ public abstract class BraveActivity extends C super.onPauseWithNative(); } + private NewTabPageListener newTabPageListener; + @Override public boolean onMenuOrKeyboardAction(int id, boolean fromMenu) { final TabImpl currentTab = (TabImpl) getActivityTab(); @@ -317,7 +331,6 @@ public abstract class BraveActivity extends C @Override public void initializeState() { super.initializeState(); - if (isNoRestoreState()) { CommandLine.getInstance().appendSwitch(ChromeSwitches.NO_RESTORE_STATE); } @@ -334,9 +347,30 @@ public abstract class BraveActivity extends C this, dataTypesArray, TimePeriod.ALL_TIME); } + setLoadedFeed(false); + setNewsItemsFeedCards(null); BraveSearchEngineUtils.initializeBraveSearchEngineStates(getTabModelSelector()); } + public boolean loadedFeed; + public CopyOnWriteArrayList newsItemsFeedCards; + + public boolean isLoadedFeed() { + return loadedFeed; + } + + public void setLoadedFeed(boolean loadedFeed) { + this.loadedFeed = loadedFeed; + } + + public CopyOnWriteArrayList getNewsItemsFeedCards() { + return newsItemsFeedCards; + } + + public void setNewsItemsFeedCards(CopyOnWriteArrayList newsItemsFeedCards) { + this.newsItemsFeedCards = newsItemsFeedCards; + } + @Override public void onBrowsingDataCleared() {} @@ -357,7 +391,6 @@ public abstract class BraveActivity extends C @Override public void onPause() { super.onPause(); - Tab tab = getActivityTab(); if (tab == null) return; @@ -389,7 +422,6 @@ public abstract class BraveActivity extends C @Override public void finishNativeInitialization() { super.finishNativeInitialization(); - if (SharedPreferencesManager.getInstance().readBoolean( BravePreferenceKeys.BRAVE_DOUBLE_RESTART, false)) { SharedPreferencesManager.getInstance().writeBoolean( @@ -515,6 +547,23 @@ public abstract class BraveActivity extends C } checkSetDefaultBrowserModal(); checkFingerPrintingOnUpgrade(); + compositorView = null; + inflatedSettingsBarLayout = null; + + if (ChromeFeatureList.isEnabled(BraveFeatureList.BRAVE_NEWS)) { + Tab tab = getActivityTab(); + if (tab != null) { + // if it's new tab add the brave news settings bar to the layout + if (tab != null && tab.getUrl().getSpec() != null + && UrlUtilities.isNTPUrl(tab.getUrl().getSpec()) + && BravePrefServiceBridge.getInstance().getNewsOptIn()) { + inflateNewsSettingsBar(); + } else { + removeSettingsBar(); + } + } + } + if (BraveVpnUtils.isBraveVpnFeatureEnable()) { ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); @@ -584,6 +633,74 @@ public abstract class BraveActivity extends C getSupportFragmentManager(), "BraveVpnCalloutDialogFragment"); } + public void inflateNewsSettingsBar() { + // get the main compositor view that we'll use to manipulate the views + compositorView = findViewById(R.id.compositor_view_holder); + ViewGroup controlContainer = findViewById(R.id.control_container); + if (findViewById(R.id.news_settings_bar) != null) { + return; + } + if (compositorView != null && controlContainer != null) { + int[] coords = {0, 0}; + controlContainer.getLocationOnScreen(coords); + int absoluteTop = coords[1]; + int absoluteBottom = coords[1] + controlContainer.getHeight(); + + LayoutInflater inflater = LayoutInflater.from(this); + // inflate the settings bar layout + View inflatedSettingsBarLayout = + inflater.inflate(R.layout.brave_news_settings_bar_layout, null); + RelativeLayout newContentButtonLayout = + (RelativeLayout) inflater.inflate(R.layout.brave_news_load_new_content, null); + // add the bar to the layout stack + compositorView.addView(inflatedSettingsBarLayout, 2); + compositorView.addView(newContentButtonLayout, 3); + inflatedSettingsBarLayout.setAlpha(0f); + FrameLayout.LayoutParams inflatedLayoutParams = + new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, 100); + + FrameLayout.LayoutParams newContentButtonLayoutParams = new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT); + + // position bellow the control_container element (nevigation bar) with 25dp compensation + inflatedLayoutParams.setMargins(0, controlContainer.getBottom() - 5, 0, 0); + newContentButtonLayoutParams.setMargins(0, 400, 0, 0); + newContentButtonLayoutParams.gravity = Gravity.CENTER_HORIZONTAL; + newContentButtonLayout.setGravity(Gravity.CENTER_HORIZONTAL); + inflatedSettingsBarLayout.setLayoutParams(inflatedLayoutParams); + newContentButtonLayout.setLayoutParams(newContentButtonLayoutParams); + + inflatedSettingsBarLayout.setVisibility(View.VISIBLE); + + compositorView.invalidate(); + } + } + + public void removeSettingsBar() { + CompositorViewHolder compositorView = findViewById(R.id.compositor_view_holder); + + if (compositorView != null) { + View settingsBar = compositorView.getChildAt(2); + if (settingsBar != null) { + if (settingsBar.getId() == R.id.news_settings_bar) { + compositorView.removeView(settingsBar); + } + } + } + } + + // Sets NTP background + public void setBackground(BitmapDrawable bgWallpaper) { + CompositorViewHolder compositorView = findViewById(R.id.compositor_view_holder); + + ViewGroup root = (ViewGroup) compositorView.getChildAt(1); + ScrollView scrollView = (ScrollView) root.getChildAt(0); + scrollView.setId(View.generateViewId()); + + //@TODO alex use Glide to set the image? + scrollView.setBackground(bgWallpaper); + } + private void checkFingerPrintingOnUpgrade() { if (!PackageUtils.isFirstInstall(this) && SharedPreferencesManager.getInstance().readInt( @@ -895,7 +1012,6 @@ public abstract class BraveActivity extends C public Tab openNewOrSelectExistingTab(String url) { TabModel tabModel = getCurrentTabModel(); int tabRewardsIndex = TabModelUtils.getTabIndexByUrl(tabModel, url); - Tab tab = selectExistingTab(url); if (tab != null) { return tab; diff --git a/android/java/org/chromium/chrome/browser/appmenu/BraveTabbedAppMenuPropertiesDelegate.java b/android/java/org/chromium/chrome/browser/appmenu/BraveTabbedAppMenuPropertiesDelegate.java index 1f2f77f4723..14b5888ec66 100644 --- a/android/java/org/chromium/chrome/browser/appmenu/BraveTabbedAppMenuPropertiesDelegate.java +++ b/android/java/org/chromium/chrome/browser/appmenu/BraveTabbedAppMenuPropertiesDelegate.java @@ -135,6 +135,13 @@ public class BraveTabbedAppMenuPropertiesDelegate extends TabbedAppMenuPropertie braveWallet.setVisible(false); } } + if (ChromeFeatureList.isEnabled(BraveFeatureList.BRAVE_NEWS)) { + MenuItem braveNews = + menu.add(Menu.NONE, R.id.brave_news_id, 0, R.string.brave_news_title); + if (shouldShowIconBeforeItem()) { + braveNews.setIcon(AppCompatResources.getDrawable(mContext, R.drawable.ic_news)); + } + } MenuItem exit = menu.add(Menu.NONE, R.id.exit_id, 0, R.string.menu_exit); if (shouldShowIconBeforeItem()) { exit.setIcon(AppCompatResources.getDrawable(mContext, R.drawable.brave_menu_exit)); diff --git a/android/java/org/chromium/chrome/browser/brave_news/BraveNewsAdapterFeedCard.java b/android/java/org/chromium/chrome/browser/brave_news/BraveNewsAdapterFeedCard.java new file mode 100644 index 00000000000..b5b2a857d18 --- /dev/null +++ b/android/java/org/chromium/chrome/browser/brave_news/BraveNewsAdapterFeedCard.java @@ -0,0 +1,105 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.chromium.chrome.browser.brave_news; + +import android.app.Activity; +import android.content.Context; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import org.chromium.brave_news.mojom.BraveNewsController; +import org.chromium.chrome.R; +import org.chromium.chrome.browser.brave_news.BraveNewsUtils; +import org.chromium.chrome.browser.brave_news.models.FeedItemCard; +import org.chromium.chrome.browser.brave_news.models.FeedItemsCard; + +import java.util.concurrent.CopyOnWriteArrayList; + +public class BraveNewsAdapterFeedCard + extends RecyclerView.Adapter { + private LayoutInflater mInflater; + private Activity mActivity; + private View mView; + + public CopyOnWriteArrayList mNewsItems; + private FeedItemsCard mNewsItem; + private ViewHolder mHolder; + private final String TAG = "BN"; + + private BraveNewsController mBraveNewsController; + + public BraveNewsAdapterFeedCard(Activity activity, + CopyOnWriteArrayList newsItems, + BraveNewsController braveNewsController) { + this.mInflater = LayoutInflater.from(activity); + this.mActivity = activity; + this.mNewsItems = newsItems; + this.mBraveNewsController = braveNewsController; + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + mView = mInflater.inflate(R.layout.brave_news_row, parent, false); + mHolder = new ViewHolder(mView); + + return mHolder; + } + + @Override + public void onBindViewHolder( + @NonNull BraveNewsAdapterFeedCard.ViewHolder holder, int position) { + LinearLayout.LayoutParams params1; + if (mNewsItems != null) { + mNewsItem = mNewsItems.get(position); + try { + if (mBraveNewsController != null) { + new CardBuilderFeedCard(mBraveNewsController, mHolder.linearLayout, mActivity, + position, mNewsItems.get(position), + mNewsItems.get(position).getCardType()); + } + + } catch (Exception e) { + Log.e(TAG, "crashinvestigation onBindViewHolder e: " + e); + } + } + } + + @Override + public int getItemCount() { + return mNewsItems.size(); + } + + public class ViewHolderWidget extends RecyclerView.ViewHolder { + ViewHolderWidget(View itemView) { + super(itemView); + } + } + + public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + LinearLayout linearLayout; + + ViewHolder(View itemView) { + super(itemView); + + linearLayout = itemView.findViewById(R.id.card_layout); + itemView.setOnClickListener(this); + } + + @Override + public void onClick(View view) {} + } + + public FeedItemsCard getItem(int id) { + return mNewsItems.get(id); + } +} diff --git a/android/java/org/chromium/chrome/browser/brave_news/BraveNewsBottomSheetDialogFragment.java b/android/java/org/chromium/chrome/browser/brave_news/BraveNewsBottomSheetDialogFragment.java new file mode 100644 index 00000000000..e3e446e5fb4 --- /dev/null +++ b/android/java/org/chromium/chrome/browser/brave_news/BraveNewsBottomSheetDialogFragment.java @@ -0,0 +1,124 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.chromium.chrome.browser.brave_news; + +import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; + +import org.chromium.brave_news.mojom.BraveNewsController; +import org.chromium.brave_news.mojom.UserEnabled; +import org.chromium.chrome.R; +import org.chromium.chrome.browser.preferences.BravePreferenceKeys; +import org.chromium.chrome.browser.preferences.SharedPreferencesManager; +import org.chromium.chrome.browser.util.TabUtils; + +public class BraveNewsBottomSheetDialogFragment extends BottomSheetDialogFragment { + private String mUrl; + private String mPublisherId; + private String mPublisherName; + private BraveNewsController mBraveNewsController; + private Context mContext; + + public static BraveNewsBottomSheetDialogFragment newInstance() { + return new BraveNewsBottomSheetDialogFragment(); + } + + public void setController(BraveNewsController controller) { + mBraveNewsController = controller; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setStyle(STYLE_NORMAL, R.style.AppBottomSheetDialogTheme); + } + + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + if (getArguments() != null) { + mUrl = getArguments().getString("url"); + mPublisherId = getArguments().getString("publisherId"); + mPublisherName = getArguments().getString("publisherName"); + } + View view = inflater.inflate(R.layout.brave_news_card_menu, container, false); + return view; + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + mContext = context; + } + + @Override + public void onDetach() { + super.onDetach(); + mContext = null; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + TextView title = view.findViewById(R.id.item_menu_title_text); + Button newTab = view.findViewById(R.id.new_tab); + Button privateTab = view.findViewById(R.id.new_private_tab); + Button disable = view.findViewById(R.id.disable_content); + + title.setText(mUrl); + StringBuilder disableText = new StringBuilder(); + disableText.append( + getResources().getString(R.string.brave_news_disable_content, mPublisherName)); + disable.setText(disableText); + + newTab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + TabUtils.openUrlInNewTab(false, mUrl); + dismiss(); + } + }); + + privateTab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + TabUtils.openUrlInNewTab(true, mUrl); + dismiss(); + } + }); + + disable.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mBraveNewsController != null) { + // Removes the news source from the fetch list by setting a UserEnabled.DISABLED + // prop for the publisher in question + SharedPreferencesManager.getInstance().writeBoolean( + BravePreferenceKeys.BRAVE_NEWS_CHANGE_SOURCE, true); + mBraveNewsController.setPublisherPref(mPublisherId, UserEnabled.DISABLED); + } + dismiss(); + Toast.makeText(mContext, + getResources().getString( + R.string.brave_news_disabled_content, mPublisherName), + Toast.LENGTH_SHORT) + .show(); + } + }); + } +} diff --git a/android/java/org/chromium/chrome/browser/brave_news/BraveNewsControllerFactory.java b/android/java/org/chromium/chrome/browser/brave_news/BraveNewsControllerFactory.java new file mode 100644 index 00000000000..1b426813ad8 --- /dev/null +++ b/android/java/org/chromium/chrome/browser/brave_news/BraveNewsControllerFactory.java @@ -0,0 +1,53 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.chromium.chrome.browser.brave_news; + +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.NativeMethods; +import org.chromium.brave_news.mojom.BraveNewsController; +import org.chromium.mojo.bindings.ConnectionErrorHandler; +import org.chromium.mojo.bindings.Interface; +import org.chromium.mojo.bindings.Interface.Proxy.Handler; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.impl.CoreImpl; + +@JNINamespace("chrome::android") +public class BraveNewsControllerFactory { + private static final Object sLock = new Object(); + private static BraveNewsControllerFactory sInstance; + + public static BraveNewsControllerFactory getInstance() { + synchronized (sLock) { + if (sInstance == null) { + sInstance = new BraveNewsControllerFactory(); + } + } + return sInstance; + } + + private BraveNewsControllerFactory() {} + + public BraveNewsController getBraveNewsController( + ConnectionErrorHandler connectionErrorHandler) { + int nativeHandle = BraveNewsControllerFactoryJni.get().getInterfaceToBraveNewsController(); + MessagePipeHandle handle = wrapNativeHandle(nativeHandle); + BraveNewsController braveNewsController = + BraveNewsController.MANAGER.attachProxy(handle, 0); + Handler handler = ((Interface.Proxy) braveNewsController).getProxyHandler(); + handler.setErrorHandler(connectionErrorHandler); + + return braveNewsController; + } + + private MessagePipeHandle wrapNativeHandle(int nativeHandle) { + return CoreImpl.getInstance().acquireNativeHandle(nativeHandle).toMessagePipeHandle(); + } + + @NativeMethods + interface Natives { + int getInterfaceToBraveNewsController(); + } +} diff --git a/android/java/org/chromium/chrome/browser/brave_news/BraveNewsUtils.java b/android/java/org/chromium/chrome/browser/brave_news/BraveNewsUtils.java new file mode 100644 index 00000000000..638ad2e27b0 --- /dev/null +++ b/android/java/org/chromium/chrome/browser/brave_news/BraveNewsUtils.java @@ -0,0 +1,82 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.chromium.chrome.browser.brave_news; + +import android.content.Context; + +import org.chromium.base.Log; +import org.chromium.brave_news.mojom.Article; +import org.chromium.brave_news.mojom.CardType; +import org.chromium.brave_news.mojom.Deal; +import org.chromium.brave_news.mojom.DisplayAd; +import org.chromium.brave_news.mojom.FeedItem; +import org.chromium.brave_news.mojom.FeedItemMetadata; +import org.chromium.brave_news.mojom.PromotedArticle; +import org.chromium.brave_news.mojom.Publisher; +import org.chromium.chrome.browser.brave_news.models.FeedItemCard; +import org.chromium.chrome.browser.brave_news.models.FeedItemsCard; + +public class BraveNewsUtils { + public static final int BRAVE_NEWS_VIEWD_CARD_TIME = 100; // milliseconds + + public static String getPromotionIdItem(FeedItemsCard items) { + String creativeInstanceId = "null"; + if (items.getFeedItems() != null) { + for (FeedItemCard itemCard : items.getFeedItems()) { + FeedItem item = itemCard.getFeedItem(); + FeedItemMetadata itemMetaData = new FeedItemMetadata(); + if (item.which() == FeedItem.Tag.PromotedArticle) { + PromotedArticle promotedArticle = item.getPromotedArticle(); + creativeInstanceId = promotedArticle.creativeInstanceId; + break; + } + } + } + + return creativeInstanceId; + } + + // method for logging news object. works by putting Log.d in the desired places of the parsing + // of the object + public static void logFeedItem(FeedItemsCard items, String id) { + if (items != null) { + if (items.getCardType() == CardType.DISPLAY_AD) { + DisplayAd displayAd = items.getDisplayAd(); + if (displayAd != null) { + Log.d("bn", id + " DISPLAY_AD title: " + displayAd.title); + } + } else { + if (items.getFeedItems() != null) { + int index = 0; + for (FeedItemCard itemCard : items.getFeedItems()) { + if (index > 50) { + return; + } + FeedItem item = itemCard.getFeedItem(); + + switch (item.which()) { + case FeedItem.Tag.Article: + Article article = item.getArticle(); + FeedItemMetadata articleData = article.data; + break; + case FeedItem.Tag.PromotedArticle: + PromotedArticle promotedArticle = item.getPromotedArticle(); + FeedItemMetadata promotedArticleData = promotedArticle.data; + String creativeInstanceId = promotedArticle.creativeInstanceId; + break; + case FeedItem.Tag.Deal: + Deal deal = item.getDeal(); + FeedItemMetadata dealData = deal.data; + String offersCategory = deal.offersCategory; + break; + } + index++; + } + } + } + } + } +} diff --git a/android/java/org/chromium/chrome/browser/brave_news/CardBuilderFeedCard.java b/android/java/org/chromium/chrome/browser/brave_news/CardBuilderFeedCard.java new file mode 100644 index 00000000000..7d99eff464b --- /dev/null +++ b/android/java/org/chromium/chrome/browser/brave_news/CardBuilderFeedCard.java @@ -0,0 +1,1421 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.chromium.chrome.browser.brave_news; + +import android.app.Activity; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Typeface; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.LayerDrawable; +import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.shapes.RoundRectShape; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.DisplayMetrics; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TableLayout; +import android.widget.TableRow; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import androidx.core.content.ContextCompat; +import androidx.core.widget.NestedScrollView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.Priority; +import com.bumptech.glide.load.DataSource; +import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.bumptech.glide.load.engine.GlideException; +import com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners; +import com.bumptech.glide.load.resource.bitmap.RoundedCorners; +import com.bumptech.glide.request.RequestListener; +import com.bumptech.glide.request.RequestOptions; +import com.bumptech.glide.request.target.CustomTarget; +import com.bumptech.glide.request.target.Target; +import com.bumptech.glide.request.transition.Transition; + +import org.chromium.base.Log; +import org.chromium.brave_news.mojom.Article; +import org.chromium.brave_news.mojom.BraveNewsController; +import org.chromium.brave_news.mojom.CardType; +import org.chromium.brave_news.mojom.Deal; +import org.chromium.brave_news.mojom.DisplayAd; +import org.chromium.brave_news.mojom.FeedItem; +import org.chromium.brave_news.mojom.FeedItemMetadata; +import org.chromium.brave_news.mojom.Image; +import org.chromium.brave_news.mojom.PromotedArticle; +import org.chromium.chrome.R; +import org.chromium.chrome.browser.app.BraveActivity; +import org.chromium.chrome.browser.app.ChromeActivity; +import org.chromium.chrome.browser.brave_news.BraveNewsBottomSheetDialogFragment; +import org.chromium.chrome.browser.brave_news.models.FeedItemCard; +import org.chromium.chrome.browser.brave_news.models.FeedItemsCard; +import org.chromium.chrome.browser.ntp_background_images.util.NTPUtil; +import org.chromium.chrome.browser.preferences.BravePreferenceKeys; +import org.chromium.chrome.browser.preferences.SharedPreferencesManager; +import org.chromium.chrome.browser.util.ConfigurationUtils; +import org.chromium.chrome.browser.util.TabUtils; +import org.chromium.ui.base.DeviceFormFactor; +import org.chromium.url.mojom.Url; + +import java.util.List; + +public class CardBuilderFeedCard { + private final int CARD_LAYOUT = 7; + private final int BUTTON_LAYOUT = 8; + private final int ROUNDED_TOP_LAYOUT = 9; + + private final int TITLE = 10; + private final int DESC = 11; + private final int URL = 12; + private final int PUBLISHER = 13; + private final int TIME = 14; + private final int CATEGORY = 15; + private final int DEALS = 16; + + private LinearLayout mLinearLayout; + private Activity mActivity; + private FeedItemsCard mNewsItem; + private BraveNewsController mBraveNewsController; + private int mType; + private int mPosition; + private View view; + private int mHorizontalMargin; + private int mDeviceWidth; + private boolean mIsPromo; + private String mCreativeInstanceId; + private String mOffersCategory; + + private final String TAG = "BN"; + private final int MARGIN_VERTICAL = 10; + private final String BRAVE_OFFERS_URL = "offers.brave.com"; + + public CardBuilderFeedCard(BraveNewsController braveNewsController, LinearLayout layout, + Activity activity, int position, FeedItemsCard newsItem, int type) { + mLinearLayout = layout; + mActivity = activity; + mPosition = position; + mType = type; + mNewsItem = newsItem; + mBraveNewsController = braveNewsController; + + mDeviceWidth = ConfigurationUtils.getDisplayMetrics(activity).get("width"); + + mIsPromo = false; + mCreativeInstanceId = ""; + mOffersCategory = ""; + + boolean isTablet = ConfigurationUtils.isTablet(activity); + boolean isLandscape = ConfigurationUtils.isLandscape(activity); + + mHorizontalMargin = isTablet + ? isLandscape ? (int) (0.20 * mDeviceWidth) : (int) (0.10 * mDeviceWidth) + : 40; + try { + createCard(mType, mPosition); + } catch (Exception e) { + Log.e(TAG, "Exception createCard:" + e.getMessage()); + } + } + + public void initItems() {} + + public void removeCard(LinearLayout layout) { + layout.removeAllViews(); + layout.setVisibility(View.GONE); + layout.invalidate(); + } + + private Url getImage(FeedItemMetadata itemMetaData) { + switch (itemMetaData.image.which()) { + case Image.Tag.PaddedImageUrl: + return itemMetaData.image.getPaddedImageUrl(); + case Image.Tag.ImageUrl: + return itemMetaData.image.getImageUrl(); + default: + return null; + } + } + + private GradientDrawable roundedBackground() { + GradientDrawable shape = new GradientDrawable(); + shape.setCornerRadius(15); + shape.setColor(mActivity.getResources().getColor(R.color.card_background)); + + return shape; + } + + public LinearLayout createCard(int type, int position) { + TableLayout tableLayoutTopNews = new TableLayout(mActivity); + + TableLayout.LayoutParams tableParamsTopNews = new TableLayout.LayoutParams( + TableLayout.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + TableLayout.LayoutParams tableParamsRow1 = new TableLayout.LayoutParams( + TableLayout.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + TableLayout.LayoutParams rowTableParams = new TableLayout.LayoutParams( + TableLayout.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + int displayHeight = mActivity.getResources().getDisplayMetrics().heightPixels; + TableRow rowTop = new TableRow(mActivity); + TextView topText = new TextView(mActivity); + + TableRow row1 = new TableRow(mActivity); + TableRow row2 = new TableRow(mActivity); + TableRow row3 = new TableRow(mActivity); + + LinearLayout layout1 = new LinearLayout(mActivity); + LinearLayout layout2 = new LinearLayout(mActivity); + LinearLayout layout3 = new LinearLayout(mActivity); + + LinearLayout.LayoutParams linearLayoutParams = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); + linearLayoutParams.setMargins(mHorizontalMargin, 0, mHorizontalMargin, 7 * MARGIN_VERTICAL); + + try { + switch (type) { + case CardType.HEADLINE: + case CardType.PROMOTED_ARTICLE: + addElementsToSingleLayout(mLinearLayout, 0, type); + mLinearLayout.setBackground( + makeRound(CARD_LAYOUT, R.color.card_background, 30)); + break; + case CardType.DISPLAY_AD: + FrameLayout adLayoutUp = new FrameLayout(mActivity); + ImageView adImage = new ImageView(mActivity); + TextView adLogo = new TextView(mActivity); + + LinearLayout adLayoutDown = new LinearLayout(mActivity); + TextView adTitle = new TextView(mActivity); + TextView adDesc = new TextView(mActivity); + Button adButton = new Button(mActivity); + + TableRow.LayoutParams adLayoutParams = new TableRow.LayoutParams( + TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.MATCH_PARENT); + TableRow.LayoutParams adLayoutDownParams = new TableRow.LayoutParams( + TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.MATCH_PARENT); + + FrameLayout.LayoutParams adImageParams = + new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, + FrameLayout.LayoutParams.WRAP_CONTENT); + + TableRow.LayoutParams adButtonParams = new TableRow.LayoutParams( + TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.MATCH_PARENT); + + tableLayoutTopNews.setLayoutParams( + new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT)); + mLinearLayout.addView(tableLayoutTopNews); + + linearLayoutParams.height = LinearLayout.LayoutParams.WRAP_CONTENT; + linearLayoutParams.setMargins( + mHorizontalMargin, 0, mHorizontalMargin, 5 * MARGIN_VERTICAL); + mLinearLayout.setLayoutParams(linearLayoutParams); + + mLinearLayout.setBackground(roundedBackground()); + + try { + mBraveNewsController.getDisplayAd(adData -> { + if (adData != null) { + NTPUtil.setsCurrentDisplayAd(adData); + + FrameLayout.LayoutParams adLogoParams = + new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.WRAP_CONTENT); + TableRow.LayoutParams adItemsParams = new TableRow.LayoutParams( + TableRow.LayoutParams.MATCH_PARENT, + TableRow.LayoutParams.WRAP_CONTENT); + + rowTableParams.setMargins(50, 0, 50, 0); + rowTableParams.width = TableLayout.LayoutParams.MATCH_PARENT; + rowTableParams.height = TableLayout.LayoutParams.WRAP_CONTENT; + rowTableParams.gravity = Gravity.CENTER_HORIZONTAL; + rowTop.setGravity(Gravity.CENTER_HORIZONTAL); + rowTop.setLayoutParams(rowTableParams); + tableLayoutTopNews.addView(rowTop); + + adLayoutParams.width = TableRow.LayoutParams.MATCH_PARENT; + adLayoutParams.height = TableRow.LayoutParams.WRAP_CONTENT; + adLayoutParams.bottomMargin = 2 * MARGIN_VERTICAL; + adLayoutUp.setLayoutParams(adLayoutParams); + rowTop.addView(adLayoutUp); + + adImageParams.width = FrameLayout.LayoutParams.WRAP_CONTENT; + adImageParams.height = FrameLayout.LayoutParams.WRAP_CONTENT; + setDisplayAdImage(adImage, adData.image, 1); + adLayoutUp.addView(adImage); + adLayoutUp.addView(adLogo); + + adLogoParams.width = FrameLayout.LayoutParams.WRAP_CONTENT; + adLogoParams.height = FrameLayout.LayoutParams.WRAP_CONTENT; + adLogoParams.topMargin = 30; + adLogoParams.gravity = Gravity.END; + adLogo.setGravity(Gravity.END); + adLogo.setCompoundDrawablesWithIntrinsicBounds( + R.drawable.ic_rewards, 0, 0, 0); + adLogo.setText(R.string.brave_news_ad); + adLogo.setTextColor(R.color.brave_theme_color); + GradientDrawable gd = new GradientDrawable(); + gd.setColor(mActivity.getResources().getColor( + android.R.color.transparent)); + gd.setCornerRadius(15); + gd.setStroke(1, R.color.brave_theme_color); + adLogo.setBackground(gd); + adLogo.setPadding(5, 5, 10, 5); + adLogo.setLayoutParams(adLogoParams); + + rowTableParams.setMargins(50, 0, 50, 0); + rowTableParams.width = TableLayout.LayoutParams.MATCH_PARENT; + rowTableParams.height = TableLayout.LayoutParams.WRAP_CONTENT; + row1.setLayoutParams(rowTableParams); + // row2.setPadding(30, 0, 30, 0); + rowTableParams.bottomMargin = 3 * MARGIN_VERTICAL; + row2.setLayoutParams(rowTableParams); + tableLayoutTopNews.addView(row1); + tableLayoutTopNews.addView(row2); + + row1.addView(adTitle); + row2.addView(adDesc); + row2.addView(adButton); + + adItemsParams.width = LinearLayout.LayoutParams.MATCH_PARENT; + adItemsParams.height = LinearLayout.LayoutParams.MATCH_PARENT; + adTitle.setTextSize(17); + adItemsParams.bottomMargin = 2 * MARGIN_VERTICAL; + adTitle.setTypeface(null, Typeface.BOLD); + adTitle.setMaxLines(5); + adTitle.setTextColor( + mActivity.getResources().getColor(R.color.news_text_color)); + adTitle.setEllipsize(TextUtils.TruncateAt.END); + adTitle.setLayoutParams(adItemsParams); + + adItemsParams = new TableRow.LayoutParams( + TableRow.LayoutParams.WRAP_CONTENT, + TableRow.LayoutParams.MATCH_PARENT); + adItemsParams.gravity = Gravity.CENTER_VERTICAL; + adDesc.setGravity(Gravity.CENTER_VERTICAL); + adItemsParams.weight = 1; + adItemsParams.width = 0; + adDesc.setLayoutParams(adItemsParams); + adTitle.setText(adData.title); + + adDesc.setTextColor( + mActivity.getResources().getColor(R.color.news_time_color)); + adDesc.setTextSize(12); + adDesc.setText(adData.description); + + adButtonParams.width = TableRow.LayoutParams.WRAP_CONTENT; + adButtonParams.height = 80; // TableRow.LayoutParams.WRAP_CONTENT; + adButton.setPadding(30, 0, 30, 0); + adButton.setTextSize(13); + adButton.setAllCaps(false); + GradientDrawable adButtonBG = new GradientDrawable(); + adButtonBG.setColor(mActivity.getResources().getColor( + android.R.color.transparent)); + adButtonBG.setCornerRadius(55); + adButtonBG.setStroke(1, + mActivity.getResources().getColor(R.color.news_text_color)); + adButton.setBackground(adButtonBG); + adButton.setText(adData.ctaText); + adButton.setTextColor( + mActivity.getResources().getColor(R.color.news_text_color)); + adButton.setLayoutParams(adButtonParams); + adButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mBraveNewsController.onDisplayAdVisit( + adData.uuid, adData.creativeInstanceId); + SharedPreferencesManager.getInstance().writeInt( + Integer.toString(BraveActivity.getBraveActivity() + .getActivityTab() + .getId()), + position); + TabUtils.openUrlInSameTab(adData.targetUrl.url); + } + }); + } + }); + + } catch (Exception e) { + Log.e(TAG, "displayad Exception" + e.getMessage()); + } + break; + case CardType.DEALS: + + View lineSeparator = new View(mActivity); + LinearLayout moreOffersLayout = new LinearLayout(mActivity); + TextView moreOffersText = new TextView(mActivity); + ImageView moreOffersArrow = new ImageView(mActivity); + + LinearLayout.LayoutParams lineSeparatorParams = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, 2); + LinearLayout.LayoutParams moreOffersLayoutParams = + new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, 120); + LinearLayout.LayoutParams moreOffersTextParams = new LinearLayout.LayoutParams( + 0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f); + LinearLayout.LayoutParams moreOffersArrowParams = + new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + + mLinearLayout.setOrientation(LinearLayout.VERTICAL); + + linearLayoutParams.height = LinearLayout.LayoutParams.WRAP_CONTENT; + linearLayoutParams.setMargins( + mHorizontalMargin, 0, mHorizontalMargin, 5 * MARGIN_VERTICAL); + mLinearLayout.setLayoutParams(linearLayoutParams); + + tableLayoutTopNews.setLayoutParams( + new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT)); + + tableParamsTopNews.setMargins(50, 5 * MARGIN_VERTICAL, 50, 5 * MARGIN_VERTICAL); + tableParamsTopNews.weight = 1; + tableParamsTopNews.height = TableLayout.LayoutParams.WRAP_CONTENT; + + row1.setPadding(50, 0, 50, 0); + + mLinearLayout.addView(tableLayoutTopNews); + tableLayoutTopNews.addView(rowTop); + rowTop.addView(topText); + + // adds the More Offers bottom layout + lineSeparator.setBackgroundColor( + mActivity.getResources().getColor(R.color.news_time_color)); + lineSeparator.setLayoutParams(lineSeparatorParams); + moreOffersLayout.setOrientation(LinearLayout.HORIZONTAL); + moreOffersLayoutParams.setMargins(50, 0, 50, 0); + moreOffersLayout.setPadding(50, 0, 50, 0); + moreOffersLayout.setLayoutParams(moreOffersLayoutParams); + + moreOffersTextParams.gravity = Gravity.CENTER_VERTICAL; + moreOffersText.setText(mActivity.getResources().getString( + R.string.brave_news_more_offers_title)); + moreOffersText.setTextSize(13); + moreOffersText.setTypeface(null, Typeface.BOLD); + moreOffersText.setTextColor( + mActivity.getResources().getColor(R.color.news_text_color)); + moreOffersText.setLayoutParams(moreOffersTextParams); + + moreOffersArrow.setImageResource(R.drawable.ic_chevron_right); + moreOffersArrow.setColorFilter( + ContextCompat.getColor(mActivity, R.color.news_text_color)); + moreOffersArrowParams.gravity = Gravity.CENTER_VERTICAL; + moreOffersArrow.setLayoutParams(moreOffersArrowParams); + + moreOffersLayout.addView(moreOffersText); + moreOffersLayout.addView(moreOffersArrow); + moreOffersLayout.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + SharedPreferencesManager.getInstance().writeInt( + Integer.toString(BraveActivity.getBraveActivity() + .getActivityTab() + .getId()), + position); + TabUtils.openUrlInSameTab(BRAVE_OFFERS_URL); + } + }); + + // adds the items to the view layout + tableLayoutTopNews.addView(row1); + tableLayoutTopNews.addView(lineSeparator); + tableLayoutTopNews.addView(moreOffersLayout); + + row1.addView(layout1); + row1.addView(layout2); + row1.addView(layout3); + + rowTop.setLayoutParams(tableParamsTopNews); + // topText.setText("DEALS OF THE DAY"); + setTextFromFeed(topText, DEALS, 0); + topText.setTextSize(17); + topText.setTextColor( + mActivity.getResources().getColor(R.color.news_text_color)); + topText.setTypeface(null, Typeface.BOLD); + + layout1.setOrientation(LinearLayout.VERTICAL); + layout2.setOrientation(LinearLayout.VERTICAL); + layout3.setOrientation(LinearLayout.VERTICAL); + + // adds each of the 3 cards + addElementsToSingleLayout(layout1, 0, type); + addElementsToSingleLayout(layout2, 1, type); + addElementsToSingleLayout(layout3, 2, type); + + mLinearLayout.setBackground( + makeRound(CARD_LAYOUT, R.color.card_background, 30)); + + break; + case CardType.PUBLISHER_GROUP: // THREE_ROWS_HEADLINES: + + mLinearLayout.setOrientation(LinearLayout.VERTICAL); + tableLayoutTopNews.setLayoutParams( + new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.MATCH_PARENT)); + tableParamsTopNews.setMargins(30, 40, 30, 40); + linearLayoutParams.height = LinearLayout.LayoutParams.WRAP_CONTENT; + linearLayoutParams.setMargins( + mHorizontalMargin, 0, mHorizontalMargin, 5 * MARGIN_VERTICAL); + mLinearLayout.setLayoutParams(linearLayoutParams); + + mLinearLayout.addView(tableLayoutTopNews); + tableLayoutTopNews.addView(rowTop); + rowTop.addView(topText); + + tableLayoutTopNews.addView(row1); + tableLayoutTopNews.addView(row2); + tableLayoutTopNews.addView(row3); + rowTop.setPadding(0, 0, 30, 0); + + rowTop.setLayoutParams(tableParamsTopNews); + + setTextFromFeed(topText, PUBLISHER, 0); + topText.setTextSize(20); + topText.setTextColor( + mActivity.getResources().getColor(R.color.news_text_color)); + topText.setTypeface(null, Typeface.BOLD); + + addElementsToSingleLayout(row1, 0, type); + + row2.setPadding(5, 5, 5, 5); + addElementsToSingleLayout(row2, 1, type); + + TableRow.LayoutParams row3Params = new TableRow.LayoutParams( + TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.MATCH_PARENT); + row3Params.bottomMargin = 5 * MARGIN_VERTICAL; + row3.setPadding(5, 5, 5, 5); + addElementsToSingleLayout(row3, 2, type); + + mLinearLayout.setPadding(30, 10, 40, 10); + mLinearLayout.setBackground( + makeRound(CARD_LAYOUT, R.color.card_background, 30)); + break; + case CardType.CATEGORY_GROUP: // TOP_NEWS: + /*3 rows + + TOP NEWS + + Title --------- + Description | | + | | + --------- + + Title --------- + Description | | + | | + -------- + + + Title --------- + Description | | + | | + -------- + + + */ + + mLinearLayout.setOrientation(LinearLayout.VERTICAL); + linearLayoutParams.height = LinearLayout.LayoutParams.WRAP_CONTENT; + float dpHeight = + ConfigurationUtils.getDpDisplayMetrics(mActivity).get("height"); + + LinearLayout.LayoutParams params = + new TableLayout.LayoutParams(TableLayout.LayoutParams.MATCH_PARENT, + TableLayout.LayoutParams.WRAP_CONTENT); + + params.setMargins(mHorizontalMargin, 0, mHorizontalMargin, 5 * MARGIN_VERTICAL); + int height = mActivity.getResources().getDisplayMetrics().heightPixels; + mLinearLayout.setLayoutParams(params); + + mLinearLayout.addView(tableLayoutTopNews); + tableLayoutTopNews.addView(rowTop); + rowTop.addView(topText); + + tableLayoutTopNews.addView(row1); + tableLayoutTopNews.addView(row2); + tableLayoutTopNews.addView(row3); + + mLinearLayout.setPadding(30, 30, 30, 10); + tableParamsTopNews.setMargins(30, 2 * MARGIN_VERTICAL, 30, 4 * MARGIN_VERTICAL); + tableParamsTopNews.weight = 3; + rowTop.setLayoutParams(tableParamsTopNews); + + setTextFromFeed(topText, CATEGORY, 0); + topText.setTextSize(26); + topText.setTextColor( + mActivity.getResources().getColor(R.color.news_text_color)); + topText.setTypeface(null, Typeface.BOLD); + + rowTableParams.bottomMargin = 2 * MARGIN_VERTICAL; + row1.setLayoutParams(rowTableParams); + row2.setLayoutParams(rowTableParams); + rowTableParams.bottomMargin = 4 * MARGIN_VERTICAL; + row3.setLayoutParams(rowTableParams); + + addElementsToSingleLayout(row1, 0, type); + addElementsToSingleLayout(row2, 1, type); + addElementsToSingleLayout(row3, 2, type); + mLinearLayout.setBackground( + makeRound(CARD_LAYOUT, R.color.card_background, 30)); + + break; + case CardType.HEADLINE_PAIRED: // HEADLINEPAIR: + /*headlinepair + + Image Image + --------- --------- + | | | | + | | | | + --------- --------- + Title Title + Description Description + + */ + + mLinearLayout.setOrientation(LinearLayout.HORIZONTAL); + + linearLayoutParams.height = LinearLayout.LayoutParams.WRAP_CONTENT; + linearLayoutParams.setMargins( + mHorizontalMargin - 20, 0, mHorizontalMargin, 5 * MARGIN_VERTICAL); + mLinearLayout.setLayoutParams(linearLayoutParams); + + LinearLayout.LayoutParams cellParams = + new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT, 1f); + cellParams.setMargins(0, 0, 20, 0); + + cellParams.height = LinearLayout.LayoutParams.MATCH_PARENT; + LinearLayout layoutLeft = new LinearLayout(mActivity); + LinearLayout layoutRight = new LinearLayout(mActivity); + mLinearLayout.addView(layoutLeft); + layoutLeft.setLayoutParams(cellParams); + layoutLeft.setOrientation(LinearLayout.VERTICAL); + + addElementsToSingleLayout(layoutLeft, 0, type); + + mLinearLayout.addView(layoutRight); + cellParams.setMargins(20, 0, 0, 0); + + cellParams.height = LinearLayout.LayoutParams.MATCH_PARENT; + layoutRight.setLayoutParams(cellParams); + layoutRight.setOrientation(LinearLayout.VERTICAL); + + addElementsToSingleLayout(layoutRight, 1, type); + + layoutLeft.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); + layoutRight.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); + + int maxHeight = Math.max( + layoutLeft.getMeasuredHeight(), layoutRight.getMeasuredHeight()); + + if (maxHeight > layoutLeft.getMeasuredHeight()) { + cellParams.height = maxHeight; + layoutLeft.setLayoutParams(cellParams); + } else if (maxHeight > layoutRight.getMeasuredHeight()) { + cellParams.height = maxHeight; + layoutRight.setLayoutParams(cellParams); + } + + break; + } + } catch (Exception e) { + Log.e(TAG, "crashinvestigation exception:" + e.getMessage()); + } + + return mLinearLayout; + } + + private void addElementsToSingleLayout(ViewGroup view, int index, int itemType) { + ImageView image = new ImageView(mActivity); + ImageView logo = new ImageView(mActivity); + TextView title = new TextView(mActivity); + TextView publisher = new TextView(mActivity); + TextView source = new TextView(mActivity); + TextView desc = new TextView(mActivity); + + LinearLayout.LayoutParams linearLayoutParams = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT); + LinearLayout.LayoutParams publisherParams = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT, 1f); + LinearLayout.LayoutParams imageParams = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT, 1f); + LinearLayout.LayoutParams titleParams = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT, 1f); + LinearLayout.LayoutParams sourceParams = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT, 1f); + LinearLayout.LayoutParams descParams = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT, 1f); + LinearLayout.LayoutParams logoParams = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT, 1f); + + TableLayout.LayoutParams tableParamsTopNews = new TableLayout.LayoutParams( + TableLayout.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + + TableRow.LayoutParams linearLayoutRowParams = new TableRow.LayoutParams( + TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.MATCH_PARENT); + TableRow.LayoutParams linearLayoutRowParams1 = new TableRow.LayoutParams( + TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.MATCH_PARENT); + TableRow.LayoutParams sourceRowParams = new TableRow.LayoutParams( + TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.MATCH_PARENT); + TableRow.LayoutParams titleRowParams = new TableRow.LayoutParams( + TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.MATCH_PARENT); + TableRow.LayoutParams descRowParams = new TableRow.LayoutParams( + TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.MATCH_PARENT); + TableRow.LayoutParams imageRowParams = new TableRow.LayoutParams( + TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.MATCH_PARENT); + + LinearLayout layoutSingleCard = new LinearLayout(mActivity); + publisher.setLetterSpacing(0.03f); + title.setLetterSpacing(0.02f); + + try { + LinearLayout layout = (LinearLayout) view; + switch (itemType) { + case CardType.DEALS: + LinearLayout layoutDeals = (LinearLayout) view; + layoutDeals.setOrientation(LinearLayout.VERTICAL); + linearLayoutRowParams1.width = 0; + linearLayoutRowParams1.weight = 1; + + linearLayoutRowParams1.setMargins(0, 0, 30, 0); + layoutDeals.setLayoutParams(linearLayoutRowParams1); + + setImage(image, "paired", index); + imageParams.height = 230; + image.setBackground(roundedBackground()); + + image.setScaleType(ImageView.ScaleType.CENTER_CROP); + image.setLayoutParams(imageParams); + layoutDeals.addView(image); + + titleParams.height = LinearLayout.LayoutParams.WRAP_CONTENT; + + title.setTextSize(14); + title.setTypeface(null, Typeface.BOLD); + title.setPadding(0, 10, 0, 0); + title.setLayoutParams(titleParams); + title.setMaxLines(2); + title.setEllipsize(TextUtils.TruncateAt.END); + layoutDeals.addView(title); + + descParams.height = LinearLayout.LayoutParams.WRAP_CONTENT; + + setTextFromFeed(desc, DESC, index); + desc.setTextSize(12); + desc.setMaxLines(3); + desc.setEllipsize(TextUtils.TruncateAt.END); + + desc.setLayoutParams(descParams); + + descParams.bottomMargin = MARGIN_VERTICAL; + desc.setPadding(0, 0, 30, 30); + layoutDeals.addView(desc); + + break; + case CardType.HEADLINE: + layout.setOrientation(LinearLayout.VERTICAL); + int topPosition = 0; + if (mPosition == 0) { + topPosition = 200; + } + linearLayoutParams = + new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + linearLayoutParams.setMargins( + mHorizontalMargin, topPosition, mHorizontalMargin, 5 * MARGIN_VERTICAL); + + layout.setBackground(makeRound(CARD_LAYOUT, R.color.card_background, 30)); + layout.setLayoutParams(linearLayoutParams); + + layout.addView(image); + layout.addView(title); + layout.addView(desc); + layout.addView(publisher); + + imageParams.height = 500; // LinearLayout.LayoutParams.WRAP_CONTENT; + + setImage(image, "image", index); + + imageParams.bottomMargin = 2 * MARGIN_VERTICAL; + image.setLayoutParams(imageParams); + + titleParams.height = LinearLayout.LayoutParams.WRAP_CONTENT; + title.setTextSize(17); + title.setTypeface(null, Typeface.BOLD); + title.setMaxLines(5); + title.setEllipsize(TextUtils.TruncateAt.END); + title.setPadding(50, 0, 50, 0); + titleParams.topMargin = 2 * MARGIN_VERTICAL; + titleParams.bottomMargin = 1 * MARGIN_VERTICAL; + title.setLayoutParams(titleParams); + + descParams.height = LinearLayout.LayoutParams.WRAP_CONTENT; + + setTextFromFeed(desc, TIME, index); + desc.setTextSize(11); + desc.setLayoutParams(descParams); + desc.setPadding(50, 0, 50, 30); + + setTextFromFeed(publisher, PUBLISHER, 0); + + publisher.setTextColor( + mActivity.getResources().getColor(R.color.news_text_color)); + publisher.setTextSize(12); + publisherParams.height = LinearLayout.LayoutParams.WRAP_CONTENT; + + publisherParams.bottomMargin = 10; + publisher.setPadding(50, 0, 50, 0); + + publisherParams.bottomMargin = 4 * MARGIN_VERTICAL; + ; + publisher.setLayoutParams(publisherParams); + + break; + case CardType.PROMOTED_ARTICLE: + layout.setOrientation(LinearLayout.VERTICAL); + linearLayoutParams = + new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + linearLayoutParams.setMargins( + mHorizontalMargin, 0, mHorizontalMargin, 5 * MARGIN_VERTICAL); + layout.setLayoutParams(linearLayoutParams); + + TextView promoted = new TextView(mActivity); + LinearLayout promotedLogoLayout = new LinearLayout(mActivity); + + imageParams = + new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + titleParams = + new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + descParams = + new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + publisherParams = + new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + + layout.addView(image); + layout.addView(title); + layout.addView(desc); + layout.addView(promotedLogoLayout); + + imageParams.height = 400; + + imageParams.bottomMargin = 2 * MARGIN_VERTICAL; + image.setLayoutParams(imageParams); + + setImage(image, "image", index); + image.setScaleType(ImageView.ScaleType.CENTER_CROP); + + titleParams.height = LinearLayout.LayoutParams.WRAP_CONTENT; + + titleParams.rightMargin = 20; + titleParams.bottomMargin = 2 * MARGIN_VERTICAL; + title.setTextSize(17); + title.setTypeface(null, Typeface.BOLD); + title.setMaxLines(5); + titleParams.bottomMargin = MARGIN_VERTICAL; + title.setEllipsize(TextUtils.TruncateAt.END); + title.setPadding(50, 30, 50, 0); + title.setLayoutParams(titleParams); + + descParams.weight = LinearLayout.LayoutParams.WRAP_CONTENT; + + setTextFromFeed(desc, TIME, index); + desc.setTextSize(11); + desc.setLayoutParams(descParams); + desc.setPadding(50, 0, 50, 30); + + LinearLayout.LayoutParams promotedParams = + new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.MATCH_PARENT); + publisherParams = new LinearLayout.LayoutParams( + 0, LinearLayout.LayoutParams.MATCH_PARENT, 1f); + LinearLayout.LayoutParams promotedLayoutParams = + new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + promotedLayoutParams.height = LinearLayout.LayoutParams.WRAP_CONTENT; + promotedLogoLayout.setPadding(50, 0, 50, 0); + + promotedParams.width = 0; + promotedParams.weight = 1; + + promotedLogoLayout.setOrientation(LinearLayout.HORIZONTAL); + promoted.setPadding(20, 0, 0, 0); + promoted.setGravity(Gravity.CENTER); + promotedParams.gravity = Gravity.CENTER_VERTICAL | Gravity.END; + promotedLayoutParams.gravity = Gravity.CENTER; + publisher.setGravity(Gravity.CENTER_VERTICAL); + publisherParams.gravity = Gravity.CENTER_VERTICAL; + + promoted.setCompoundDrawablesWithIntrinsicBounds( + R.drawable.ic_promoted, 0, 0, 0); + StringBuilder promotedTitle = new StringBuilder(" " + + mActivity.getResources().getString( + R.string.brave_news_promoted_title)); + promoted.setText( + mActivity.getResources().getString(R.string.brave_news_promoted_title)); + + promoted.setTextColor( + mActivity.getResources().getColor(R.color.news_text_color)); + promoted.setTextSize(12); + promoted.setLayoutParams(promotedParams); + promotedLayoutParams.bottomMargin = 4 * MARGIN_VERTICAL; + + promoted.setBackground( + makeRound(BUTTON_LAYOUT, R.color.news_promoted_background_color, 15)); + promotedLogoLayout.setLayoutParams(promotedLayoutParams); + + setTextFromFeed(publisher, PUBLISHER, 0); + publisher.setTextColor( + mActivity.getResources().getColor(R.color.news_text_color)); + publisher.setTextSize(12); + publisherParams.width = 0; + publisherParams.weight = 2; + + publisher.setLayoutParams(publisherParams); + promotedLogoLayout.addView(publisher); + promotedLogoLayout.addView(promoted); + + break; + case CardType.HEADLINE_PAIRED: + + layout.setOrientation(LinearLayout.VERTICAL); + + imageParams = + new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + titleParams = + new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + descParams = + new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + publisherParams = + new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + + int height = (int) (mDeviceWidth * 0.3); + + imageParams.height = height; + image.setLayoutParams(imageParams); + setImage(image, "paired", index); + + image.setScaleType(ImageView.ScaleType.CENTER_CROP); + imageParams.bottomMargin = MARGIN_VERTICAL; + layout.addView(image); + + title.setLayoutParams(titleParams); + title.setTextSize(13); + title.setTypeface(null, Typeface.BOLD); + title.setMaxLines(5); + titleParams.bottomMargin = MARGIN_VERTICAL; + title.setEllipsize(TextUtils.TruncateAt.END); + title.setPadding(50, 0, 50, 0); + layout.addView(title); + + descParams.weight = 1; + descParams.height = 0; + desc.setTextSize(11); + setTextFromFeed(desc, TIME, index); + desc.setLayoutParams(descParams); + desc.setPadding(50, 0, 50, 0); + descParams.bottomMargin = MARGIN_VERTICAL; + layout.addView(desc); + + setTextFromFeed(publisher, PUBLISHER, index); + publisher.setTextColor( + mActivity.getResources().getColor(R.color.news_text_color)); + publisher.setTextSize(12); + + publisherParams.gravity = Gravity.BOTTOM; + publisher.setGravity(Gravity.BOTTOM); + publisherParams.setMargins(0, 0, 0, 4 * MARGIN_VERTICAL); + publisher.setPadding(50, 0, 50, 0); + publisher.setLayoutParams(publisherParams); + layout.addView(publisher); + + layout.setBackground(makeRound(CARD_LAYOUT, R.color.card_background, 30)); + + break; + + case CardType.PUBLISHER_GROUP: // 3 numbered rows no photos + + TableLayout.LayoutParams layoutRowParams = + new TableLayout.LayoutParams(TableLayout.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT); + TableRow layoutRow = (TableRow) view; + TextView no = new TextView(mActivity); + + layoutRow.removeAllViews(); + layoutSingleCard.removeAllViews(); + + layoutRowParams.setMargins(0, 20, 0, 20); + layoutRowParams.weight = 1; + layoutRowParams.height = 0; + layoutRow.setPadding(30, 0, 30, 0); + layoutRow.setLayoutParams(layoutRowParams); + + TableRow.LayoutParams noParams = new TableRow.LayoutParams( + TableRow.LayoutParams.WRAP_CONTENT, TableRow.LayoutParams.WRAP_CONTENT); + TableRow.LayoutParams layoutSingleCardParams = new TableRow.LayoutParams( + TableRow.LayoutParams.WRAP_CONTENT, TableRow.LayoutParams.WRAP_CONTENT); + + no.setText(String.valueOf(index + 1)); + no.setTextColor(mActivity.getResources().getColor(R.color.news_text_color)); + noParams.width = 0; + noParams.weight = 1; + noParams.gravity = Gravity.CENTER_VERTICAL; + no.setLayoutParams(noParams); + + layoutRow.addView(no); + + layoutSingleCard.setOrientation(LinearLayout.VERTICAL); + layoutSingleCardParams.width = 0; + layoutSingleCardParams.weight = 7; + + layoutSingleCard.setLayoutParams(layoutSingleCardParams); + + titleRowParams.height = LinearLayout.LayoutParams.WRAP_CONTENT; + + title.setTextSize(15); + titleRowParams.bottomMargin = MARGIN_VERTICAL; + title.setTypeface(null, Typeface.BOLD); + title.setLayoutParams(titleRowParams); + title.setMaxLines(5); + title.setEllipsize(TextUtils.TruncateAt.END); + layoutSingleCard.addView(title); + + descRowParams.height = LinearLayout.LayoutParams.WRAP_CONTENT; + + setTextFromFeed(desc, TIME, index); + descRowParams.bottomMargin = 20; + desc.setTextSize(12); + desc.setLayoutParams(descRowParams); + layoutSingleCard.addView(desc); + + layoutRow.addView(layoutSingleCard); + break; + case CardType.CATEGORY_GROUP: // TOP_NEWS + TableRow layoutRowPhotos = (TableRow) view; + + tableParamsTopNews.setMargins(0, 0, 0, 5 * MARGIN_VERTICAL); + tableParamsTopNews.weight = 1; + tableParamsTopNews.height = 0; + layoutRowPhotos.setPadding(30, 0, 30, 0); + layoutRowPhotos.setLayoutParams(tableParamsTopNews); + + layoutSingleCard.setOrientation(LinearLayout.VERTICAL); + linearLayoutRowParams.width = 0; + linearLayoutRowParams.weight = 2; + linearLayoutRowParams.gravity = Gravity.CENTER_VERTICAL; + layoutSingleCard.setGravity(Gravity.CENTER_VERTICAL); + layoutSingleCard.setLayoutParams(linearLayoutRowParams); + + source.setLines(1); + sourceRowParams.gravity = Gravity.BOTTOM; + source.setPadding(0, 0, 0, 0); + sourceRowParams.height = TableRow.LayoutParams.WRAP_CONTENT; + sourceRowParams.setMargins(0, 0, 0, MARGIN_VERTICAL); + source.setLayoutParams(sourceRowParams); + source.setTextSize(12); + source.setTypeface(null, Typeface.BOLD); + setTextFromFeed(source, PUBLISHER, index); + source.setTextColor(mActivity.getResources().getColor(R.color.news_time_color)); + layoutSingleCard.addView(source); + + titleRowParams.height = LinearLayout.LayoutParams.WRAP_CONTENT; + + titleRowParams.rightMargin = 20; + title.setTextSize(13); + title.setTypeface(null, Typeface.BOLD); + titleRowParams.bottomMargin = MARGIN_VERTICAL; + title.setLayoutParams(titleRowParams); + title.setMaxLines(4); + title.setEllipsize(TextUtils.TruncateAt.END); + layoutSingleCard.addView(title); + + descRowParams.height = LinearLayout.LayoutParams.WRAP_CONTENT; + + desc.setPadding(0, 0, 0, 0); + desc.setTextSize(11); + descRowParams.bottomMargin = 0; + setTextFromFeed(desc, TIME, index); + desc.setLayoutParams(descRowParams); + layoutSingleCard.addView(desc); + + layoutRowPhotos.addView(layoutSingleCard); + + imageRowParams.width = 0; + imageRowParams.height = 300; + + imageRowParams.weight = 1; + imageRowParams.setMargins(0, 0, 0, 0); + image.setScaleType(ImageView.ScaleType.CENTER_CROP); + + setImage(image, "paired", index); + + image.setLayoutParams(imageRowParams); + layoutRowPhotos.addView(image); + + break; + default: + + setTextFromFeed(title, TITLE, index); + setTextFromFeed(desc, DESC, index); + layout.addView(image); + layout.addView(title); + layout.addView(desc); + break; + } + + title.setTextColor(mActivity.getResources().getColor(R.color.news_text_color)); + desc.setTextColor(mActivity.getResources().getColor(R.color.news_time_color)); + + setTextFromFeed(title, TITLE, index); + + final FeedItemMetadata itemData = getItemData(index); + + view.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + // @TODO alex refactor this with listener in BraveNewTabPageLayout + SharedPreferencesManager.getInstance().writeInt( + Integer.toString( + BraveActivity.getBraveActivity().getActivityTab().getId()), + mPosition); + TabUtils.openUrlInSameTab(itemData.url.url); + } + }); + + title.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + // @TODO alex refactor this with listener in BraveNewTabPageLayout + SharedPreferencesManager.getInstance().writeInt( + Integer.toString( + BraveActivity.getBraveActivity().getActivityTab().getId()), + mPosition); + TabUtils.openUrlInSameTab(itemData.url.url); + } + }); + + title.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + showBottomSheetDialog( + itemData.url.url, itemData.publisherId, itemData.publisherName); + return true; + } + }); + + } catch (Exception e) { + Log.e(TAG, "Exception addElementsToSingleLayout: " + e.getMessage()); + } + } + + private void showBottomSheetDialog(String urlString, String publisherId, String publisherName) { + BraveNewsBottomSheetDialogFragment bottomSheetDialog = + BraveNewsBottomSheetDialogFragment.newInstance(); + Bundle bundle = new Bundle(); + bundle.putString("url", urlString); + bundle.putString("publisherId", publisherId); + bundle.putString("publisherName", publisherName); + bottomSheetDialog.setArguments(bundle); + bottomSheetDialog.setController(mBraveNewsController); + + bottomSheetDialog.show(((ChromeActivity) mActivity).getSupportFragmentManager(), + "brave_news_bottom_sheet_dialog_fragment"); + } + + private void setTextFromFeed(TextView textView, int type, int index) { + try { + FeedItemMetadata itemData = getItemData(index); + if (itemData != null) { + setText(itemData, textView, type); + setListeners(textView, itemData.url.url, mCreativeInstanceId, mIsPromo); + } + + } catch (Exception e) { + Log.e(TAG, " exceptionscaught e:" + e.getMessage()); + } + } + + private FeedItemMetadata getItemData(int index) { + FeedItemMetadata itemData = null; + try { + List feedItemsCard = mNewsItem.getFeedItems(); + if (feedItemsCard == null) { + return null; + } + FeedItemCard feedItemCard = feedItemsCard.get(index); + FeedItem feedItem = feedItemCard.getFeedItem(); + + switch (feedItem.which()) { + case FeedItem.Tag.Article: + + Article article = feedItem.getArticle(); + FeedItemMetadata articleData = article.data; + itemData = article.data; + + break; + case FeedItem.Tag.PromotedArticle: + PromotedArticle promotedArticle = feedItem.getPromotedArticle(); + FeedItemMetadata promotedArticleData = promotedArticle.data; + mCreativeInstanceId = promotedArticle.creativeInstanceId; + itemData = promotedArticle.data; + + mIsPromo = true; + break; + case FeedItem.Tag.Deal: + Deal deal = feedItem.getDeal(); + FeedItemMetadata dealData = deal.data; + mOffersCategory = deal.offersCategory; + itemData = deal.data; + + break; + } + } catch (Exception e) { + Log.e(TAG, " exceptionscaught getItemData e:" + e.getMessage()); + } + + final FeedItemMetadata itemDataFinal = itemData; + + return itemDataFinal; + } + + private void setListeners( + View view, String urlString, String creativeInstanceId, boolean isPromo) { + DisplayAd displayAd = mNewsItem.getDisplayAd(); + view.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (isPromo) { + // Updates the no. of promotion cards visited + mBraveNewsController.onPromotedItemVisit( + mNewsItem.getUuid(), creativeInstanceId); + } else if (displayAd != null) { + // Updates the no. of ads cards visited + mBraveNewsController.onDisplayAdVisit( + displayAd.uuid, displayAd.creativeInstanceId); + } else { + // Brave News updates the no. of "normal" cards visited + int visitedNewsCardsCount = SharedPreferencesManager.getInstance().readInt( + BravePreferenceKeys.BRAVE_NEWS_CARDS_VISITED); + visitedNewsCardsCount++; + SharedPreferencesManager.getInstance().writeInt( + BravePreferenceKeys.BRAVE_NEWS_CARDS_VISITED, visitedNewsCardsCount); + if (visitedNewsCardsCount > 0) { + mBraveNewsController.onSessionCardVisitsCountChanged( + (short) visitedNewsCardsCount); + } + } + } + }); + } + + private void setText(FeedItemMetadata itemData, TextView textView, int type) { + switch (type) { + case TITLE: + textView.setText(itemData.title); + break; + case DESC: + textView.setText(itemData.description); + break; + case PUBLISHER: + textView.setText(itemData.publisherName); + break; + case TIME: + textView.setText(itemData.relativeTimeDescription); + break; + case DEALS: + textView.setText(mOffersCategory); + break; + case CATEGORY: + textView.setText(itemData.categoryName); + break; + } + } + + private void setDisplayAdImage(ImageView imageView, Image adDataImage, int index) { + Url imageUrlTemp = null; + + switch (adDataImage.which()) { + case Image.Tag.PaddedImageUrl: + imageUrlTemp = adDataImage.getPaddedImageUrl(); + break; + case Image.Tag.ImageUrl: + imageUrlTemp = adDataImage.getImageUrl(); + break; + } + + final Url adImageUrl = imageUrlTemp; + mBraveNewsController.getImageData(adImageUrl, imageData -> { + if (imageData != null) { + Bitmap decodedByte = BitmapFactory.decodeByteArray(imageData, 0, imageData.length); + Glide.with(mActivity) + .asBitmap() + .load(decodedByte) + .priority(Priority.IMMEDIATE) + .diskCacheStrategy(DiskCacheStrategy.ALL) + .into(new CustomTarget() { + @Override + public void onResourceReady(@NonNull Bitmap resource, + @Nullable Transition transition) { + imageView.setImageBitmap(resource); + } + @Override + public void onLoadCleared(@Nullable Drawable placeholder) {} + }); + imageView.setClipToOutline(true); + } + }); + } + + private void setImage(ImageView imageView, String type, int index) { + List feedItemsCard = mNewsItem.getFeedItems(); + if (feedItemsCard != null) { + FeedItemCard feedItemCard = feedItemsCard.get(index); + FeedItem item = feedItemCard.getFeedItem(); + + FeedItemMetadata itemMetaData = new FeedItemMetadata(); + switch (item.which()) { + case FeedItem.Tag.Article: + Article article = item.getArticle(); + itemMetaData = article.data; + break; + case FeedItem.Tag.PromotedArticle: + PromotedArticle promotedArticle = item.getPromotedArticle(); + itemMetaData = promotedArticle.data; + break; + case FeedItem.Tag.Deal: + Deal deal = item.getDeal(); + itemMetaData = deal.data; + break; + } + + Url itemImageUrl = getImage(itemMetaData); + mBraveNewsController.getImageData(itemImageUrl, imageData -> { + if (imageData != null) { + Bitmap decodedByte = + BitmapFactory.decodeByteArray(imageData, 0, imageData.length); + if (!type.equals("paired")) { + Glide.with(mActivity) + .asBitmap() + .load(decodedByte) + .placeholder(new ColorDrawable( + mActivity.getResources().getColor(R.color.card_background))) + .diskCacheStrategy(DiskCacheStrategy.ALL) + .apply(RequestOptions + .bitmapTransform( + new GranularRoundedCorners(30, 30, 0, 0)) + .diskCacheStrategy(DiskCacheStrategy.ALL)) + .into(new CustomTarget() { + @Override + public void onResourceReady(@NonNull Bitmap resource, + @Nullable Transition transition) { + imageView.setImageBitmap(resource); + } + @Override + public void onLoadCleared(@Nullable Drawable placeholder) {} + }); + imageView.setAdjustViewBounds(true); + imageView.setClipToOutline(true); + } else { + imageView.setImageBitmap(decodedByte); + } + + if (!type.equals("paired")) { + LinearLayout.LayoutParams imageParams = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.MATCH_PARENT, 1f); + imageParams.height = LinearLayout.LayoutParams.WRAP_CONTENT; + imageView.setLayoutParams(imageParams); + } + } + }); + } + } + + // used for logging the Glide implementation + private RequestListener createLoggerListener(final String name) { + return new RequestListener() { + @Override + public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, + boolean isFirstResource) { + return false; + } + @Override + public boolean onResourceReady(Drawable resource, Object model, Target target, + DataSource dataSource, boolean isFirstResource) { + if (resource instanceof BitmapDrawable) { + Bitmap bitmap = ((BitmapDrawable) resource).getBitmap(); + // Log.d(TAG,String.format(Locale.getDefault(), "Ready %s bitmap %,d bytes, + // size: %d x %d", + // name, + // bitmap.getByteCount(), + // bitmap.getWidth(), + // bitmap.getHeight())); + } + return false; + } + }; + } + + private LayerDrawable makeRound(int type, int background, int radius) { + float[] outerRadii = + new float[] {radius, radius, radius, radius, radius, radius, radius, radius}; + float[] innerRadii = + new float[] {radius, radius, radius, radius, radius, radius, radius, radius}; + + // Set the shape background card_background + ShapeDrawable backgroundShape = null; + new ShapeDrawable(new RoundRectShape(outerRadii, null, innerRadii)); + + if (type == BUTTON_LAYOUT) { + backgroundShape = new ShapeDrawable(new RoundRectShape(outerRadii, null, innerRadii)); + backgroundShape.getPaint().setColor( + mActivity.getResources().getColor(background)); // background color + backgroundShape.getPaint().setStyle(Paint.Style.FILL); // Define background + backgroundShape.getPaint().setAntiAlias(true); + backgroundShape.setPadding(10, 10, 10, 10); + + } else if (type == CARD_LAYOUT) { + backgroundShape = new ShapeDrawable(new RoundRectShape(outerRadii, null, innerRadii)); + backgroundShape.getPaint().setColor( + mActivity.getResources().getColor(background)); // background color + backgroundShape.getPaint().setStyle(Paint.Style.FILL); // Define background + } else if (type == ROUNDED_TOP_LAYOUT) { + backgroundShape = new ShapeDrawable(new RoundRectShape(outerRadii, null, null)); + backgroundShape.getPaint().setColor( + mActivity.getResources().getColor(background)); // background color + backgroundShape.getPaint().setStyle(Paint.Style.FILL); // Define background + } + + // Initialize an array of drawables + Drawable[] drawables = new Drawable[] {backgroundShape}; + // Initialize a layer drawable object + LayerDrawable layerDrawable = new LayerDrawable(drawables); + return layerDrawable; + } +} diff --git a/android/java/org/chromium/chrome/browser/brave_news/models/FeedItemCard.java b/android/java/org/chromium/chrome/browser/brave_news/models/FeedItemCard.java new file mode 100644 index 00000000000..3234a27929f --- /dev/null +++ b/android/java/org/chromium/chrome/browser/brave_news/models/FeedItemCard.java @@ -0,0 +1,49 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.chromium.chrome.browser.brave_news.models; + +import org.chromium.brave_news.mojom.FeedItem; + +import java.util.Arrays; + +public class FeedItemCard { + private FeedItem feedItem; + private int cardType; + private byte[] imageByte; + + public FeedItemCard() {} + + public FeedItem getFeedItem() { + return feedItem; + } + + public void setFeedItem(FeedItem feedItem) { + this.feedItem = feedItem; + } + + public int getCardType() { + return cardType; + } + + public void setCardType(int cardType) { + this.cardType = cardType; + } + + public byte[] getImageByte() { + return imageByte; + } + + public void setImageByte(byte[] imageByte) { + this.imageByte = imageByte; + } + + @Override + public String toString() { + return "FeedItemCard{" + + "feedItem=" + feedItem + ", cardType=" + cardType + ", imageByte='" + + Arrays.toString(imageByte) + '\'' + '}'; + } +} diff --git a/android/java/org/chromium/chrome/browser/brave_news/models/FeedItemsCard.java b/android/java/org/chromium/chrome/browser/brave_news/models/FeedItemsCard.java new file mode 100644 index 00000000000..3a59354b7e7 --- /dev/null +++ b/android/java/org/chromium/chrome/browser/brave_news/models/FeedItemsCard.java @@ -0,0 +1,79 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.chromium.chrome.browser.brave_news.models; + +import org.chromium.brave_news.mojom.DisplayAd; + +import java.util.Arrays; +import java.util.List; + +public class FeedItemsCard { + private List feedItems; + private int cardType; + private byte[] imageByte; + private String uuid; + private boolean viewStatSent; + private DisplayAd displayAd; + + public FeedItemsCard() { + this.viewStatSent = false; + } + + public List getFeedItems() { + return feedItems; + } + + public void setFeedItems(List feedItems) { + this.feedItems = feedItems; + } + + public int getCardType() { + return cardType; + } + + public void setCardType(int cardType) { + this.cardType = cardType; + } + + public byte[] getImageByte() { + return imageByte; + } + + public void setImageByte(byte[] imageByte) { + this.imageByte = imageByte; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public boolean isViewStatSent() { + return viewStatSent; + } + + public void setViewStatSent(boolean viewStatSent) { + this.viewStatSent = viewStatSent; + } + + public DisplayAd getDisplayAd() { + return displayAd; + } + + public void setDisplayAd(DisplayAd displayAd) { + this.displayAd = displayAd; + } + + @Override + public String toString() { + return "FeedItemsCard{" + + "feedItems=" + feedItems + ", cardType=" + cardType + ", displayAd=" + displayAd + + ", imageByte=" + Arrays.toString(imageByte) + '}'; + } +} diff --git a/android/java/org/chromium/chrome/browser/ntp/BraveNewTabPageLayout.java b/android/java/org/chromium/chrome/browser/ntp/BraveNewTabPageLayout.java index 3b9221221ab..ed07532d569 100644 --- a/android/java/org/chromium/chrome/browser/ntp/BraveNewTabPageLayout.java +++ b/android/java/org/chromium/chrome/browser/ntp/BraveNewTabPageLayout.java @@ -11,23 +11,24 @@ import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.content.res.ColorStateList; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.Point; +import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.net.Uri; import android.os.Build; -import android.os.Bundle; import android.os.Handler; import android.text.Html; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.TextUtils; import android.util.AttributeSet; -import android.util.Pair; +import android.util.DisplayMetrics; import android.view.ContextMenu; import android.view.Display; import android.view.Gravity; @@ -41,13 +42,17 @@ import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ProgressBar; +import android.widget.RelativeLayout; +import android.widget.ScrollView; import android.widget.TextView; -import androidx.annotation.Nullable; +import androidx.annotation.NonNull; import androidx.cardview.widget.CardView; import androidx.core.widget.ImageViewCompat; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import androidx.viewpager.widget.ViewPager; import com.airbnb.lottie.LottieAnimationView; @@ -55,22 +60,36 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton; import org.json.JSONException; +import org.chromium.base.ContextUtils; import org.chromium.base.Log; import org.chromium.base.ThreadUtils; -import org.chromium.base.TraceEvent; import org.chromium.base.supplier.Supplier; import org.chromium.base.task.AsyncTask; +import org.chromium.brave_news.mojom.Article; +import org.chromium.brave_news.mojom.BraveNewsController; +import org.chromium.brave_news.mojom.CardType; +import org.chromium.brave_news.mojom.DisplayAd; +import org.chromium.brave_news.mojom.FeedItem; +import org.chromium.brave_news.mojom.FeedItemMetadata; +import org.chromium.brave_news.mojom.FeedPage; +import org.chromium.brave_news.mojom.FeedPageItem; import org.chromium.chrome.R; import org.chromium.chrome.browser.BraveAdsNativeHelper; +import org.chromium.chrome.browser.BraveFeatureList; import org.chromium.chrome.browser.BraveRewardsHelper; -import org.chromium.chrome.browser.ChromeTabbedActivity; import org.chromium.chrome.browser.InternetConnection; import org.chromium.chrome.browser.QRCodeShareDialogFragment; import org.chromium.chrome.browser.app.BraveActivity; +import org.chromium.chrome.browser.brave_news.BraveNewsAdapterFeedCard; +import org.chromium.chrome.browser.brave_news.BraveNewsControllerFactory; +import org.chromium.chrome.browser.brave_news.BraveNewsUtils; +import org.chromium.chrome.browser.brave_news.models.FeedItemCard; +import org.chromium.chrome.browser.brave_news.models.FeedItemsCard; import org.chromium.chrome.browser.brave_stats.BraveStatsUtil; -import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior; +import org.chromium.chrome.browser.compositor.CompositorViewHolder; import org.chromium.chrome.browser.custom_layout.VerticalViewPager; import org.chromium.chrome.browser.explore_sites.ExploreSitesBridge; +import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher; import org.chromium.chrome.browser.local_database.DatabaseHelper; import org.chromium.chrome.browser.local_database.TopSiteTable; @@ -97,35 +116,50 @@ import org.chromium.chrome.browser.offlinepages.RequestCoordinatorBridge; import org.chromium.chrome.browser.onboarding.OnboardingPrefManager; import org.chromium.chrome.browser.preferences.BravePref; import org.chromium.chrome.browser.preferences.BravePrefServiceBridge; +import org.chromium.chrome.browser.preferences.BravePreferenceKeys; +import org.chromium.chrome.browser.preferences.SharedPreferencesManager; import org.chromium.chrome.browser.profiles.Profile; +import org.chromium.chrome.browser.settings.BraveNewsPreferences; +import org.chromium.chrome.browser.settings.SettingsLauncherImpl; import org.chromium.chrome.browser.suggestions.tile.TileGroup; -import org.chromium.chrome.browser.tab.EmptyTabObserver; +import org.chromium.chrome.browser.sync.settings.BraveManageSyncSettings; import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tab.TabAttributes; import org.chromium.chrome.browser.tab.TabImpl; -import org.chromium.chrome.browser.tab.TabObserver; -import org.chromium.chrome.browser.tabmodel.TabModel; +import org.chromium.chrome.browser.util.ConfigurationUtils; import org.chromium.chrome.browser.util.PackageUtils; import org.chromium.chrome.browser.util.TabUtils; +import org.chromium.components.browser_ui.settings.SettingsLauncher; import org.chromium.components.browser_ui.widget.displaystyle.UiConfig; +import org.chromium.components.embedder_support.util.UrlUtilities; import org.chromium.components.user_prefs.UserPrefs; +import org.chromium.mojo.bindings.ConnectionErrorHandler; +import org.chromium.mojo.system.MojoException; import org.chromium.ui.base.WindowAndroid; import org.chromium.ui.widget.Toast; import java.util.ArrayList; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.TreeMap; +import java.util.UUID; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; -public class BraveNewTabPageLayout extends NewTabPageLayout { +public class BraveNewTabPageLayout + extends NewTabPageLayout implements ConnectionErrorHandler { private static final String TAG = "BraveNewTabPageView"; private static final String BRAVE_REF_URL = "https://brave.com/r/"; + private static final int ITEMS_PER_PAGE = 18; + private static final int MINIMUM_VISIBLE_HEIGHT_THRESHOLD = 50; private View mBraveStatsViewFallBackLayout; - private ImageView bgImageView; + private ImageView mBgImageView; private Profile mProfile; + private BraveNewTabPageLayout mNtpContent; + private LinearLayout mParentLayout; private SponsoredTab sponsoredTab; @@ -154,6 +188,51 @@ public class BraveNewTabPageLayout extends NewTabPageLayout { private List widgetList = new ArrayList(); public static final int NTP_WIDGET_STACK_CODE = 3333; + // Brave news + private BraveNewsAdapterFeedCard mAdapterFeedCard; + private FrameLayout mOptinButton; + private TextView mOptinText; + private LinearLayout mOptinLayout; + private TextView mOptinLearnMore; + private ImageView mOptinClose; + private CopyOnWriteArrayList mNewsItemsFeedCard = + new CopyOnWriteArrayList(); + private LinearLayout mContainer; + private RecyclerView mRecyclerView; + private TextView mLoading; + private View mLoadingView; + private View mFeedSpinner; + private ScrollView mParentScrollView; + private ViewGroup imageCreditLayout; + private ViewGroup mSettingsBar; + private ViewGroup mNewContentButton; + private boolean isScrolled; + private NTPImage mNtpImageGlobal; + private boolean mSettingsBarIsClickable; + private BraveNewsController mBraveNewsController; + + private CompositorViewHolder mCompositorView; + + private long mStartCardViewTime; + private long mEndCardViewTime; + private String mCreativeInstanceId; + private String mUuid; + //@TODO alex make an enum + private String mCardType; + private int mItemPosition; + private FeedItemsCard mVisibleCard; + private boolean mIsNewsOn; + private boolean mIsShowOptin; + private boolean mIsShowNewsOn; + private int mmViewedNewsCardsCount; + + private boolean mIsFeedLoaded; + private CopyOnWriteArrayList mExistingNewsFeedObject; + private int mPrevScrollPosition; + private static int mFirstVisibleCard; + private String mFeedHash; + private SharedPreferencesManager.Observer mPreferenceObserver; + public BraveNewTabPageLayout(Context context, AttributeSet attrs) { super(context, attrs); mProfile = Profile.getLastUsedRegularProfile(); @@ -165,6 +244,7 @@ public class BraveNewTabPageLayout extends NewTabPageLayout { @Override protected void onFinishInflate() { super.onFinishInflate(); + ntpWidgetLayout = findViewById(R.id.ntp_widget_layout); indicatorLayout = findViewById(R.id.indicator_layout); ntpWidgetViewPager = findViewById(R.id.ntp_widget_view_pager); @@ -188,6 +268,38 @@ public class BraveNewTabPageLayout extends NewTabPageLayout { }); showWidgetBasedOnOrder(); NTPUtil.showBREBottomBanner(this); + + if (ChromeFeatureList.isEnabled(BraveFeatureList.BRAVE_NEWS)) { + SharedPreferences sharedPreferences = ContextUtils.getAppSharedPreferences(); + + mIsNewsOn = BravePrefServiceBridge.getInstance().getNewsOptIn(); + mIsShowOptin = + sharedPreferences.getBoolean(BraveNewsPreferences.PREF_SHOW_OPTIN, false); + mIsShowNewsOn = BravePrefServiceBridge.getInstance().getShowNews(); + + mFeedHash = ""; + + InitBraveNewsController(); + + mIsFeedLoaded = BraveActivity.getBraveActivity().isLoadedFeed(); + mExistingNewsFeedObject = BraveActivity.getBraveActivity().getNewsItemsFeedCards(); + mPrevScrollPosition = 2; + + if (mIsNewsOn && mIsShowNewsOn && mIsFeedLoaded && mExistingNewsFeedObject != null) { + mNewsItemsFeedCard = mExistingNewsFeedObject; + } + + if (BraveActivity.getBraveActivity() != null && mIsNewsOn) { + Tab tab = BraveActivity.getBraveActivity().getActivityTab(); + if (tab != null && tab.getUrl().getSpec() != null + && UrlUtilities.isNTPUrl(tab.getUrl().getSpec())) { + BraveActivity.getBraveActivity().inflateNewsSettingsBar(); + } else { + // remove settings bar + BraveActivity.getBraveActivity().removeSettingsBar(); + } + } + } } private void showFallBackNTPLayout() { @@ -392,6 +504,32 @@ public class BraveNewTabPageLayout extends NewTabPageLayout { mBadgeAnimationView.setVisibility(View.INVISIBLE); } showWidgets(); + + if (ChromeFeatureList.isEnabled(BraveFeatureList.BRAVE_NEWS)) { + if (mSettingsBar != null) { + mSettingsBar.setVisibility(View.VISIBLE); + mSettingsBar.setAlpha(0f); + } + initNews(); + if (BraveActivity.getBraveActivity() != null && mIsNewsOn) { + Tab tab = BraveActivity.getBraveActivity().getActivityTab(); + if (tab != null && tab.getUrl().getSpec() != null + && UrlUtilities.isNTPUrl(tab.getUrl().getSpec())) { + BraveActivity.getBraveActivity().inflateNewsSettingsBar(); + } else { + BraveActivity.getBraveActivity().removeSettingsBar(); + } + } + mPreferenceObserver = (key) -> { + if (TextUtils.equals(key, BravePreferenceKeys.BRAVE_NEWS_CHANGE_SOURCE) + || TextUtils.equals(key, BravePreferenceKeys.BRAVE_NEWS_PREF_SHOW_NEWS)) { + if (mNewContentButton != null) { + mNewContentButton.setVisibility(View.VISIBLE); + } + } + }; + SharedPreferencesManager.getInstance().addObserver(mPreferenceObserver); + } } @Override @@ -408,15 +546,37 @@ public class BraveNewTabPageLayout extends NewTabPageLayout { } } mNTPBackgroundImagesBridge.removeObserver(mNTPBackgroundImageServiceObserver); + + if (ChromeFeatureList.isEnabled(BraveFeatureList.BRAVE_NEWS)) { + if (mNewsItemsFeedCard != null && mNewsItemsFeedCard.size() > 0) { + BraveActivity.getBraveActivity().setNewsItemsFeedCards(mNewsItemsFeedCard); + } + + if (mSettingsBar != null && mCompositorView != null) { + if (mSettingsBar.getId() == R.id.news_settings_bar) { + mSettingsBar.setVisibility(View.GONE); + mSettingsBar.setAlpha(0f); + } + } + + if (mBraveNewsController != null) { + mBraveNewsController.close(); + } + + // removes preference observer + SharedPreferencesManager.getInstance().removeObserver(mPreferenceObserver); + mPreferenceObserver = null; + } + super.onDetachedFromWindow(); } @Override public void onConfigurationChanged(Configuration newConfig) { if (sponsoredTab != null && NTPUtil.shouldEnableNTPFeature()) { - if (bgImageView != null) { + if (mBgImageView != null && !ChromeFeatureList.isEnabled(BraveFeatureList.BRAVE_NEWS)) { // We need to redraw image to fit parent properly - bgImageView.setImageResource(android.R.color.transparent); + mBgImageView.setImageResource(android.R.color.transparent); } NTPImage ntpImage = sponsoredTab.getTabNTPImage(false); if (ntpImage == null) { @@ -435,6 +595,620 @@ public class BraveNewTabPageLayout extends NewTabPageLayout { } } + // corrects position of image credit and for the loading spinner. Used when News is active + private void correctPosition(boolean toTop) { + DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); + float dpHeight = displayMetrics.heightPixels / displayMetrics.density; + int pxHeight = dpToPx(mActivity, dpHeight); + + boolean isTablet = ConfigurationUtils.isTablet(mActivity); + boolean isLandscape = ConfigurationUtils.isLandscape(mActivity); + + if (imageCreditLayout == null || mFeedSpinner == null) { + imageCreditLayout = findViewById(R.id.image_credit_layout); + mFeedSpinner = findViewById(R.id.feed_spinner); + } + + if (imageCreditLayout != null) { + LinearLayout.LayoutParams linearLayoutParams = + (LinearLayout.LayoutParams) imageCreditLayout.getLayoutParams(); + + int imageCreditCorrection = + NTPUtil.correctImageCreditLayoutTopPosition(mNtpImageGlobal) - 140; + if (toTop) { + imageCreditCorrection = 0; + } + linearLayoutParams.setMargins(0, imageCreditCorrection, 0, 0); + imageCreditLayout.setLayoutParams(linearLayoutParams); + int imageCreditLayoutBottom = imageCreditLayout.getBottom(); + } + + if (mFeedSpinner != null) { + FrameLayout.LayoutParams feedSpinnerParams = + (FrameLayout.LayoutParams) mFeedSpinner.getLayoutParams(); + FrameLayout bottomToolbar = (FrameLayout) findViewById(R.id.bottom_toolbar); + feedSpinnerParams.topMargin = (int) (dpHeight - 30); + mFeedSpinner.setLayoutParams(feedSpinnerParams); + } + } + + private void getFeed() { + ExecutorService executors = Executors.newFixedThreadPool(1); + Runnable runnable = new Runnable() { + @Override + public void run() { + mBraveNewsController.getFeed((feed) -> { + if (feed == null) { + processFeed(); + return; + } + try { + mFeedHash = feed.hash; + SharedPreferencesManager.getInstance().writeString( + BravePreferenceKeys.BRAVE_NEWS_FEED_HASH, feed.hash); + if (feed.featuredItem != null) { + // process Featured item + FeedItem featuredItem = feed.featuredItem; + FeedItemsCard featuredItemsCard = new FeedItemsCard(); + + FeedItemMetadata featuredItemMetaData = new FeedItemMetadata(); + Article featuredArticle = featuredItem.getArticle(); + FeedItemMetadata featuredArticleData = featuredArticle.data; + + FeedItemCard featuredItemCard = new FeedItemCard(); + List featuredCardItems = new ArrayList<>(); + + featuredItemsCard.setCardType(CardType.HEADLINE); + featuredItemsCard.setUuid(UUID.randomUUID().toString()); + + featuredItemCard.setFeedItem(featuredItem); + featuredCardItems.add(featuredItemCard); + + featuredItemsCard.setFeedItems(featuredCardItems); + mNewsItemsFeedCard.add(featuredItemsCard); + } + + // adds empty card to trigger Display ad call for the second card, when the + // user starts scrolling + FeedItemsCard displayAdCard = new FeedItemsCard(); + DisplayAd displayAd = new DisplayAd(); + displayAdCard.setCardType(CardType.DISPLAY_AD); + displayAdCard.setDisplayAd(displayAd); + displayAdCard.setUuid(UUID.randomUUID().toString()); + mNewsItemsFeedCard.add(displayAdCard); + + // start page loop + int noPages = 0; + int itemIndex = 0; + int totalPages = feed.pages.length; + for (FeedPage page : feed.pages) { + for (FeedPageItem cardData : page.items) { + // if for any reason we get an empty object, unless it's a + // DISPLAY_AD we skip it + if (cardData.cardType != CardType.DISPLAY_AD) { + if (cardData.items.length == 0) { + continue; + } + } + FeedItemsCard feedItemsCard = new FeedItemsCard(); + + feedItemsCard.setCardType(cardData.cardType); + feedItemsCard.setUuid(UUID.randomUUID().toString()); + List cardItems = new ArrayList<>(); + for (FeedItem item : cardData.items) { + FeedItemMetadata itemMetaData = new FeedItemMetadata(); + FeedItemCard feedItemCard = new FeedItemCard(); + feedItemCard.setFeedItem(item); + + cardItems.add(feedItemCard); + + feedItemsCard.setFeedItems(cardItems); + } + mNewsItemsFeedCard.add(feedItemsCard); + } + } // end page loop + processFeed(); + BraveActivity.getBraveActivity().setNewsItemsFeedCards(mNewsItemsFeedCard); + BraveActivity.getBraveActivity().setLoadedFeed(true); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + }; + executors.submit(runnable); + } + + private void refreshFeed() { + mNewsItemsFeedCard.clear(); + mAdapterFeedCard = + new BraveNewsAdapterFeedCard(mActivity, mNewsItemsFeedCard, mBraveNewsController); + mRecyclerView.setAdapter(mAdapterFeedCard); + + if (!mIsShowNewsOn) { + correctPosition(false); + mRecyclerView.setVisibility(View.GONE); + return; + } + getFeed(); + mParentScrollView.fullScroll(ScrollView.FOCUS_UP); + mRecyclerView.scrollToPosition(0); + } + + private void initNews() { + mSettingsBarIsClickable = false; + mRecyclerView = findViewById(R.id.newsRecycler); + mContainer = (LinearLayout) findViewById(R.id.ntp_main_layout); + mOptinButton = findViewById(R.id.optin_button); + mOptinClose = findViewById(R.id.close_optin); + mOptinLearnMore = findViewById(R.id.optin_learnmore); + mOptinLayout = findViewById(R.id.optin_layout_id); + mOptinText = findViewById(R.id.optin_button_text); + mLoading = findViewById(R.id.loading); + mLoadingView = findViewById(R.id.optin_loading_spinner); + mFeedSpinner = findViewById(R.id.feed_spinner); + mParentLayout = (LinearLayout) findViewById(R.id.parent_layout); + mNtpContent = (BraveNewTabPageLayout) findViewById(R.id.ntp_content); + + SharedPreferencesManager.getInstance().writeBoolean( + BravePreferenceKeys.BRAVE_NEWS_CHANGE_SOURCE, false); + // init Brave news parameters + mStartCardViewTime = 0; + mEndCardViewTime = 0; + mCreativeInstanceId = ""; + mUuid = ""; + //@TODO alex make an enum + mCardType = ""; + mItemPosition = 0; + mVisibleCard = null; + + mRecyclerView.setItemViewCacheSize(250); + mRecyclerView.setDrawingCacheEnabled(true); + mRecyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH); + + mRecyclerView.setVisibility(View.GONE); + + mRecyclerView.setLayoutManager(new LinearLayoutManager(mActivity)); + mAdapterFeedCard = + new BraveNewsAdapterFeedCard(mActivity, mNewsItemsFeedCard, mBraveNewsController); + mRecyclerView.setAdapter(mAdapterFeedCard); + + mLoadingView.setVisibility(View.GONE); + + mParentScrollView = (ScrollView) mNtpContent.getParent(); + ViewGroup rootView = (ViewGroup) mParentScrollView.getParent(); + mCompositorView = (CompositorViewHolder) rootView.getParent(); + + imageCreditLayout = findViewById(R.id.image_credit_layout); + + mOptinLayout.setVisibility(View.GONE); + SharedPreferences sharedPreferences = ContextUtils.getAppSharedPreferences(); + + mIsNewsOn = BravePrefServiceBridge.getInstance().getNewsOptIn(); + mIsShowOptin = sharedPreferences.getBoolean(BraveNewsPreferences.PREF_SHOW_OPTIN, false); + mIsShowNewsOn = BravePrefServiceBridge.getInstance().getShowNews(); + + if (!mIsNewsOn || (!mIsNewsOn && mIsShowOptin)) { + // correctPosition(false); + mOptinLayout.setVisibility(View.VISIBLE); + } else if (mIsShowNewsOn) { + mOptinLayout.setVisibility(View.GONE); + mParentLayout.removeView(mOptinLayout); + mFeedSpinner.setVisibility(View.VISIBLE); + + boolean isFeedLoaded = BraveActivity.getBraveActivity().isLoadedFeed(); + CopyOnWriteArrayList existingNewsFeedObject = + BraveActivity.getBraveActivity().getNewsItemsFeedCards(); + int prevScrollPosition = SharedPreferencesManager.getInstance().readInt( + Integer.toString(BraveActivity.getBraveActivity().getActivityTab().getId())); + + mmViewedNewsCardsCount = + SharedPreferencesManager.getInstance().readInt("mViewedNewsCardsCount_" + + BraveActivity.getBraveActivity().getActivityTab().getId()); + if (prevScrollPosition == 0) { + isFeedLoaded = false; + existingNewsFeedObject = null; + mmViewedNewsCardsCount = 0; + } + + if (!isFeedLoaded) { + getFeed(); + SharedPreferencesManager.getInstance().writeInt( + Integer.toString(BraveActivity.getBraveActivity().getActivityTab().getId()), + 1); + // Brave News interaction started + if (mBraveNewsController != null) { + mBraveNewsController.onInteractionSessionStarted(); + } + } + + else { + if (mActivity == null) { + mActivity = BraveActivity.getBraveActivity(); + } + setFeed(); + + if (mRecyclerView != null) { + mRecyclerView.post(new Runnable() { + @Override + public void run() { + int scrollPosition = prevScrollPosition + 1; + if (prevScrollPosition <= 1) { + scrollPosition = 2; + } + final int scrollPositionFinal = scrollPosition; + if (mParentScrollView != null) { + mParentScrollView.fullScroll(ScrollView.FOCUS_UP); + mRecyclerView.scrollToPosition(scrollPositionFinal); + imageCreditLayout.scrollTo(0, 0); + } + } + }); + } + } + } else { + // correctPosition(false); + mOptinLayout.setVisibility(View.GONE); + } + + ViewTreeObserver parentScrollViewObserver = mParentScrollView.getViewTreeObserver(); + mParentScrollView.getViewTreeObserver().addOnScrollChangedListener( + new ViewTreeObserver.OnScrollChangedListener() { + @Override + public void onScrollChanged() { + try { + int scrollY = mParentScrollView.getScrollY(); + if (mRecyclerView.getLayoutManager().findViewByPosition(0) != null) { + if (isScrolled) { + correctPosition(false); + } + } + isScrolled = false; + + float value = (float) scrollY / mParentScrollView.getMaxScrollAmount(); + if (value >= 1) { + value = 1; + } + float alpha = (float) (1 - value); + if (alpha < 1f) { + imageCreditLayout.setAlpha(alpha); + } + + if (mSettingsBar != null + && mSettingsBar.getVisibility() == View.VISIBLE) { + if (value > 0.4 && mSettingsBar.getAlpha() <= 1f) { + mSettingsBar.setAlpha((float) (value + 0.5)); + } else if (value < 0.4 && mSettingsBar.getAlpha() > 0f) { + mSettingsBar.setAlpha((float) (value - 0.2)); + } + if (mSettingsBar.getAlpha() >= 1) { + mSettingsBarIsClickable = true; + LinearLayout.LayoutParams imageCreditLayoutParams = + (LinearLayout.LayoutParams) + imageCreditLayout.getLayoutParams(); + } else { + mSettingsBarIsClickable = false; + } + } + } catch (Exception e) { + Log.e("bn", "Exception parentScrollViewObserver e:" + e); + } + } + }); + + parentScrollViewObserver.addOnGlobalLayoutListener( + new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + try { + if (mSettingsBar != null) { + if (!isScrolled) { + mSettingsBar.setAlpha(0f); + } + + ImageView newsSettingsButton = + (ImageView) mSettingsBar.findViewById( + R.id.news_settings_button); + ViewTreeObserver.OnGlobalLayoutListener listener = this; + + newsSettingsButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mSettingsBarIsClickable) { + SettingsLauncher settingsLauncher = + new SettingsLauncherImpl(); + settingsLauncher.launchSettingsActivity( + getContext(), BraveNewsPreferences.class); + mParentScrollView.getViewTreeObserver() + .removeOnGlobalLayoutListener(listener); + } + } + }); + } + + if (mNewContentButton != null) { + ProgressBar loadingSpinner = + (ProgressBar) mNewContentButton.findViewById( + R.id.new_content_loading_spinner); + TextView newContentButtonText = + (TextView) mNewContentButton.findViewById( + R.id.new_content_button_text); + mNewContentButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + //@TODO alex check why visibility change doesn't work + newContentButtonText.setVisibility(View.INVISIBLE); + loadingSpinner.setVisibility(View.VISIBLE); + + mNewContentButton.invalidate(); + + mNewContentButton.setClickable(false); + SharedPreferencesManager.getInstance().writeBoolean( + BravePreferenceKeys.BRAVE_NEWS_CHANGE_SOURCE, + false); + if (!mIsShowNewsOn) { + mIsShowNewsOn = true; + } + refreshFeed(); + + newContentButtonText.setVisibility(View.VISIBLE); + loadingSpinner.setVisibility(View.GONE); + mNewContentButton.setClickable(true); + mNewContentButton.setVisibility(View.INVISIBLE); + + mNewContentButton.invalidate(); + } + }); + } + + } catch (Exception e) { + Log.e("bn", "Exception addOnGlobalLayoutListener e: " + e); + } + } + }); + + RecyclerView.LayoutManager manager = mRecyclerView.getLayoutManager(); + if (manager instanceof LinearLayoutManager) { + LinearLayoutManager linearLayoutManager = (LinearLayoutManager) manager; + mFirstVisibleCard = linearLayoutManager.findFirstVisibleItemPosition(); + + mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { + private int lastFirstVisibleItem; + + @Override + public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { + super.onScrollStateChanged(recyclerView, newState); + + int firstCompletelyVisibleItemPosition = + linearLayoutManager.findFirstCompletelyVisibleItemPosition(); + + if (newState == RecyclerView.SCROLL_STATE_DRAGGING) { + mEndCardViewTime = System.currentTimeMillis(); + long timeDiff = mEndCardViewTime - mStartCardViewTime; + // if viewed for more than 100 ms send the event + if (timeDiff > BraveNewsUtils.BRAVE_NEWS_VIEWD_CARD_TIME) { + if (firstCompletelyVisibleItemPosition > 0) { + SharedPreferencesManager.getInstance().writeInt( + Integer.toString(BraveActivity.getBraveActivity() + .getActivityTab() + .getId()), + firstCompletelyVisibleItemPosition); + } + + if (mVisibleCard != null) { + if (!mVisibleCard.isViewStatSent()) { + // send viewed cards events + if (mCardType.equals("promo")) { + if (!mUuid.equals("") && !mCreativeInstanceId.equals("")) { + mBraveNewsController.onPromotedItemView( + mUuid, mCreativeInstanceId); + } + } else if (mCardType.equals("displayad")) { + if (mUuid != null && mCreativeInstanceId != null) { + if (!mUuid.equals("") + && !mCreativeInstanceId.equals("")) { + mBraveNewsController.onDisplayAdView( + mUuid, mCreativeInstanceId); + } + } + } else { + mmViewedNewsCardsCount++; + SharedPreferencesManager.getInstance().writeInt( + "mViewedNewsCardsCount_" + + BraveActivity.getBraveActivity() + .getActivityTab() + .getId(), + mmViewedNewsCardsCount); + + if (mmViewedNewsCardsCount > 0) { + mBraveNewsController.onSessionCardViewsCountChanged( + (short) mmViewedNewsCardsCount); + } + } + mVisibleCard.setViewStatSent(true); + } + } + } + } + + if (newState == RecyclerView.SCROLL_STATE_IDLE) { + mStartCardViewTime = System.currentTimeMillis(); + int firstVisibleItemPosition = + linearLayoutManager.findFirstVisibleItemPosition(); + + int lastVisibleItemPosition = + linearLayoutManager.findLastVisibleItemPosition(); + + mFeedHash = SharedPreferencesManager.getInstance().readString( + BravePreferenceKeys.BRAVE_NEWS_FEED_HASH, ""); + + //@TODO alex optimize feed availability check + mBraveNewsController.isFeedUpdateAvailable( + mFeedHash, isNewsFeedAvailable -> { + if (isNewsFeedAvailable) { + if (mNewContentButton != null) { + mNewContentButton.setVisibility(View.VISIBLE); + } + } + }); + + for (int viewPosition = firstVisibleItemPosition; + viewPosition <= lastVisibleItemPosition; viewPosition++) { + View itemView = manager.findViewByPosition(viewPosition); + int visiblePercentage = (int) getVisibleHeightPercentage(itemView); + if (visiblePercentage >= MINIMUM_VISIBLE_HEIGHT_THRESHOLD) { + mVisibleCard = mNewsItemsFeedCard.get(viewPosition); + // get params for view PROMOTED_ARTICLE + if (mVisibleCard.getCardType() == CardType.PROMOTED_ARTICLE) { + mItemPosition = viewPosition; + mCreativeInstanceId = + BraveNewsUtils.getPromotionIdItem(mVisibleCard); + mUuid = mVisibleCard.getUuid(); + mCardType = "promo"; + } + // get params for view DISPLAY_AD + if (mVisibleCard.getCardType() == CardType.DISPLAY_AD) { + mItemPosition = viewPosition; + DisplayAd currentDisplayAd = NTPUtil.getsCurrentDisplayAd(); + if (currentDisplayAd != null) { + mCreativeInstanceId = currentDisplayAd != null + ? currentDisplayAd.creativeInstanceId + : ""; + mUuid = currentDisplayAd != null ? currentDisplayAd.uuid + : ""; + mCardType = "displayad"; + } + } + } + } + } + } + + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + super.onScrolled(recyclerView, dx, dy); + + try { + int offset = recyclerView.computeVerticalScrollOffset(); + mFirstVisibleCard = linearLayoutManager.findFirstVisibleItemPosition(); + + mParentScrollView.post(new Runnable() { + @Override + public void run() { + try { + mParentScrollView.scrollBy(0, offset + 20); + } catch (Exception e) { + Log.e("bn", "Exception crashinvestigation e:" + e); + } + } + }); + + } catch (Exception e) { + Log.e("bn", "Exception onScrolled:" + e); + } + } + }); + } + + mOptinClose.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + SharedPreferences.Editor sharedPreferencesEditor = sharedPreferences.edit(); + sharedPreferencesEditor.putBoolean(BraveNewsPreferences.PREF_SHOW_OPTIN, false); + sharedPreferencesEditor.apply(); + + if (mOptinLayout != null) { + mOptinLayout.setVisibility(View.GONE); + } + correctPosition(false); + mParentScrollView.scrollTo(0, 0); + imageCreditLayout.setAlpha(1.0f); + } + }); + + mOptinLearnMore.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent browserIntent = + new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.brave.com")); + browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mActivity.startActivity(browserIntent); + } + }); + + mOptinButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mOptinButton.setClickable(false); + mOptinText.setVisibility(View.INVISIBLE); + mLoadingView.setVisibility(View.VISIBLE); + + SharedPreferences.Editor sharedPreferencesEditor = sharedPreferences.edit(); + sharedPreferencesEditor.putBoolean(BraveNewsPreferences.PREF_SHOW_OPTIN, false); + sharedPreferencesEditor.apply(); + BravePrefServiceBridge.getInstance().setNewsOptIn(true); + BravePrefServiceBridge.getInstance().setShowNews(true); + if (BraveActivity.getBraveActivity() != null) { + BraveActivity.getBraveActivity().inflateNewsSettingsBar(); + mSettingsBar = + (LinearLayout) mCompositorView.findViewById(R.id.news_settings_bar); + } + + getFeed(); + } + }); + } + + private double getVisibleHeightPercentage(View view) { + Rect itemRect = new Rect(); + double viewVisibleHeightPercentage = 0; + if (view != null) { + view.getLocalVisibleRect(itemRect); + + // Find the height of the item. + double visibleHeight = itemRect.height(); + double height = view.getMeasuredHeight(); + + viewVisibleHeightPercentage = ((visibleHeight / height) * 100); + } + + return viewVisibleHeightPercentage; + } + + private void setFeed() { + mFeedSpinner.setVisibility(View.GONE); + mOptinLayout.setVisibility(View.GONE); + + mContainer.setVisibility(View.VISIBLE); + mRecyclerView.setVisibility(View.VISIBLE); + + try { + mParentScrollView.scrollTo(0, 0); + } catch (Exception e) { + Log.e("bn", "Exception processfeed e:" + e); + } + + isScrolled = true; + } + + private void processFeed() { + mFeedSpinner.setVisibility(View.GONE); + mOptinLayout.setVisibility(View.GONE); + + mContainer.setVisibility(View.VISIBLE); + mRecyclerView.setVisibility(View.VISIBLE); + + mAdapterFeedCard.notifyItemRangeChanged(0, mAdapterFeedCard.getItemCount()); + + try { + mParentScrollView.scrollTo(0, 0); + } catch (Exception e) { + Log.e("bn", "Exception processfeed e:" + e); + } + + isScrolled = true; + } + @Override public void initialize(NewTabPageManager manager, Activity activity, TileGroup.Delegate tileGroupDelegate, boolean searchProviderHasLogo, @@ -455,10 +1229,11 @@ public class BraveNewTabPageLayout extends NewTabPageLayout { Display display = mActivity.getWindowManager().getDefaultDisplay(); Point size = new Point(); display.getSize(size); - NTPUtil.updateOrientedUI(mActivity, this, size); + NTPUtil.updateOrientedUI(mActivity, this, size, ntpImage); ImageView mSponsoredLogo = (ImageView) findViewById(R.id.sponsored_logo); FloatingActionButton mSuperReferralLogo = (FloatingActionButton) findViewById(R.id.super_referral_logo); TextView mCreditText = (TextView) findViewById(R.id.credit_text); + mNtpImageGlobal = ntpImage; if (ntpImage instanceof Wallpaper && NTPUtil.isReferralEnabled() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { @@ -494,6 +1269,7 @@ public class BraveNewTabPageLayout extends NewTabPageLayout { BackgroundImage backgroundImage = (BackgroundImage) ntpImage; mSponsoredLogo.setVisibility(View.GONE); mSuperReferralLogo.setVisibility(View.GONE); + if (backgroundImage.getImageCredit() != null) { String imageCreditStr = String.format(getResources().getString(R.string.photo_by, backgroundImage.getImageCredit().getName())); @@ -506,6 +1282,7 @@ public class BraveNewTabPageLayout extends NewTabPageLayout { mCreditText.setText(spannableString); mCreditText.setVisibility(View.VISIBLE); + mCreditText.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { @@ -521,17 +1298,19 @@ public class BraveNewTabPageLayout extends NewTabPageLayout { } private void setBackgroundImage(NTPImage ntpImage) { - bgImageView = (ImageView) findViewById(R.id.bg_image_view); - bgImageView.setScaleType(ImageView.ScaleType.MATRIX); + mBgImageView = (ImageView) findViewById(R.id.bg_image_view); + mBgImageView.setScaleType(ImageView.ScaleType.MATRIX); - ViewTreeObserver observer = bgImageView.getViewTreeObserver(); + ViewTreeObserver observer = mBgImageView.getViewTreeObserver(); observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { - mWorkerTask = new FetchWallpaperWorkerTask(ntpImage, bgImageView.getMeasuredWidth(), bgImageView.getMeasuredHeight(), wallpaperRetrievedCallback); + mWorkerTask = + new FetchWallpaperWorkerTask(ntpImage, mBgImageView.getMeasuredWidth(), + mBgImageView.getMeasuredHeight(), wallpaperRetrievedCallback); mWorkerTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - bgImageView.getViewTreeObserver().removeOnGlobalLayoutListener(this); + mBgImageView.getViewTreeObserver().removeOnGlobalLayoutListener(this); } }); } @@ -584,6 +1363,10 @@ public class BraveNewTabPageLayout extends NewTabPageLayout { initilizeSponsoredTab(); } checkAndShowNTPImage(false); + mParentScrollView = (ScrollView) mNtpContent.getParent(); + ViewGroup rootView = (ViewGroup) mParentScrollView.getParent(); + CompositorViewHolder compositorView = (CompositorViewHolder) rootView.getParent(); + final int childCount = compositorView.getChildCount(); } @Override @@ -606,6 +1389,7 @@ public class BraveNewTabPageLayout extends NewTabPageLayout { } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } + }; private NTPBackgroundImagesBridge.NTPBackgroundImageServiceObserver mNTPBackgroundImageServiceObserver = new NTPBackgroundImagesBridge.NTPBackgroundImageServiceObserver() { @@ -617,13 +1401,50 @@ public class BraveNewTabPageLayout extends NewTabPageLayout { mNTPBackgroundImagesBridge.getTopSites(); } } - } + } }; private FetchWallpaperWorkerTask.WallpaperRetrievedCallback wallpaperRetrievedCallback = new FetchWallpaperWorkerTask.WallpaperRetrievedCallback() { @Override public void bgWallpaperRetrieved(Bitmap bgWallpaper) { - bgImageView.setImageBitmap(bgWallpaper); + if (ChromeFeatureList.isEnabled(BraveFeatureList.BRAVE_NEWS)) { + if (BraveActivity.getBraveActivity() != null) { + BitmapDrawable bgWallpaperDrawable = new BitmapDrawable(bgWallpaper); + BraveActivity.getBraveActivity().setBackground(bgWallpaperDrawable); + } + + mParentScrollView = (ScrollView) mNtpContent.getParent(); + ViewGroup rootView = (ViewGroup) mParentScrollView.getParent(); + ViewGroup coordinator = (ViewGroup) rootView.getParent(); + + CompositorViewHolder compositorView = (CompositorViewHolder) rootView.getParent(); + + try { + if (BraveActivity.getBraveActivity() != null + && BraveActivity.getBraveActivity().getActivityTab() != null) { + if (UrlUtilities.isNTPUrl(BraveActivity.getBraveActivity() + .getActivityTab() + .getUrl() + .getSpec())) { + if (compositorView.getChildAt(2).getId() == R.id.news_settings_bar) { + mSettingsBar = (LinearLayout) compositorView.getChildAt(2); + mSettingsBar.setVisibility(View.VISIBLE); + mSettingsBar.setAlpha(0f); + } + + if (compositorView.getChildAt(3).getId() + == R.id.new_content_layout_id) { + mNewContentButton = (RelativeLayout) compositorView.getChildAt(3); + } + } + } + } catch (Exception e) { + Log.e("bn", "crashinvestigation exception: " + e.getMessage()); + } + + } else { + mBgImageView.setImageBitmap(bgWallpaper); + } } @Override @@ -817,4 +1638,22 @@ public class BraveNewTabPageLayout extends NewTabPageLayout { mTopsiteErrorMessage.setVisibility(View.VISIBLE); } } + + @Override + public void onConnectionError(MojoException e) { + if (mBraveNewsController != null) { + mBraveNewsController.close(); + } + mBraveNewsController = null; + InitBraveNewsController(); + } + + private void InitBraveNewsController() { + if (mBraveNewsController != null) { + return; + } + + mBraveNewsController = + BraveNewsControllerFactory.getInstance().getBraveNewsController(this); + } } diff --git a/android/java/org/chromium/chrome/browser/ntp_background_images/util/NTPUtil.java b/android/java/org/chromium/chrome/browser/ntp_background_images/util/NTPUtil.java index f8969b16486..af449f186d4 100644 --- a/android/java/org/chromium/chrome/browser/ntp_background_images/util/NTPUtil.java +++ b/android/java/org/chromium/chrome/browser/ntp_background_images/util/NTPUtil.java @@ -18,9 +18,6 @@ import android.os.Bundle; import android.os.Handler; import android.text.SpannableString; import android.text.Spanned; -import android.text.TextPaint; -import android.text.method.LinkMovementMethod; -import android.text.style.ClickableSpan; import android.text.style.ForegroundColorSpan; import android.util.DisplayMetrics; import android.view.Gravity; @@ -36,13 +33,16 @@ import androidx.cardview.widget.CardView; import org.chromium.base.ContextUtils; import org.chromium.base.Log; +import org.chromium.brave_news.mojom.DisplayAd; import org.chromium.chrome.R; import org.chromium.chrome.browser.BraveAdsNativeHelper; +import org.chromium.chrome.browser.BraveFeatureList; import org.chromium.chrome.browser.BraveRewardsHelper; import org.chromium.chrome.browser.BraveRewardsNativeWorker; -import org.chromium.chrome.browser.BraveRewardsPanelPopup; import org.chromium.chrome.browser.app.BraveActivity; import org.chromium.chrome.browser.app.ChromeActivity; +import org.chromium.chrome.browser.compositor.CompositorViewHolder; +import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.ntp_background_images.NTPBackgroundImagesBridge; import org.chromium.chrome.browser.ntp_background_images.RewardsBottomSheetDialogFragment; import org.chromium.chrome.browser.ntp_background_images.model.BackgroundImage; @@ -51,21 +51,19 @@ import org.chromium.chrome.browser.ntp_background_images.model.SponsoredTab; import org.chromium.chrome.browser.ntp_background_images.model.Wallpaper; import org.chromium.chrome.browser.ntp_background_images.util.SponsoredImageUtil; import org.chromium.chrome.browser.preferences.BravePref; -import org.chromium.chrome.browser.preferences.BravePreferenceKeys; -import org.chromium.chrome.browser.preferences.SharedPreferencesManager; +import org.chromium.chrome.browser.preferences.BravePrefServiceBridge; import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.settings.BackgroundImagesPreferences; -import org.chromium.chrome.browser.tab.TabLaunchType; import org.chromium.chrome.browser.util.ConfigurationUtils; import org.chromium.chrome.browser.util.ImageUtils; import org.chromium.chrome.browser.util.PackageUtils; import org.chromium.components.user_prefs.UserPrefs; -import org.chromium.content_public.browser.LoadUrlParams; import org.chromium.ui.base.DeviceFormFactor; import java.io.IOException; import java.io.InputStream; import java.lang.ref.SoftReference; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Set; @@ -73,6 +71,15 @@ import java.util.Set; public class NTPUtil { private static final int BOTTOM_TOOLBAR_HEIGHT = 56; private static final String REMOVED_SITES = "removed_sites"; + private static DisplayAd sCurrentDisplayAd; + + public static void setsCurrentDisplayAd(DisplayAd displayAd) { + sCurrentDisplayAd = displayAd; + } + + public static DisplayAd getsCurrentDisplayAd() { + return sCurrentDisplayAd; + } public static HashMap> imageCache = new HashMap>(); @@ -82,25 +89,79 @@ public class NTPUtil { BraveRewardsNativeWorker.getInstance().SetAutoContributeEnabled(true); } - public static void updateOrientedUI(Context context, ViewGroup view, Point size) { + public static int correctImageCreditLayoutTopPosition(NTPImage ntpImage) { + int imageCreditCorrection = 0; + boolean isCompensate = false; + + if (ChromeFeatureList.isEnabled(BraveFeatureList.BRAVE_NEWS)) { + if (BravePrefServiceBridge.getInstance().getNewsOptIn() + && BravePrefServiceBridge.getInstance().getShowNews()) { + isCompensate = true; + } + } + + if (BraveActivity.getBraveActivity() != null) { + BraveActivity activity = BraveActivity.getBraveActivity(); + + float dpHeight = ConfigurationUtils.getDpDisplayMetrics(activity).get("height"); + int pxHeight = dpToPx(activity, dpHeight); + + boolean isTablet = ConfigurationUtils.isTablet(activity); + boolean isLandscape = ConfigurationUtils.isLandscape(activity); + imageCreditCorrection = isLandscape ? (int) (pxHeight * (isCompensate ? 0.46 : 0.54)) + : (int) (pxHeight * (isCompensate ? 0.70 : 0.30)); + if (ntpImage instanceof BackgroundImage) { + if (!isTablet) { + imageCreditCorrection = isLandscape + ? (int) (pxHeight * (isCompensate ? 0.12 : 0.88)) + : (int) (pxHeight * (isCompensate ? 0.46 : 0.59)); + } + if (!ChromeFeatureList.isEnabled(BraveFeatureList.BRAVE_NEWS)) { + imageCreditCorrection = (int) imageCreditCorrection - (int) (pxHeight * 0.04); + } + } else { + if (!isTablet) { + imageCreditCorrection = isLandscape + ? (int) (pxHeight * (isCompensate ? 0.02 : 0.98)) + : (int) (pxHeight * (isCompensate ? 0.30 : 0.50)); + } else { + imageCreditCorrection = isLandscape + ? (int) (pxHeight * (isCompensate ? 0.28 : 0.72)) + : (int) (pxHeight * (isCompensate ? 0.56 : 0.44)); + } + if (!ChromeFeatureList.isEnabled(BraveFeatureList.BRAVE_NEWS)) { + imageCreditCorrection = (int) imageCreditCorrection - (int) (pxHeight * 0.12); + } + } + } + + return imageCreditCorrection; + } + + public static void updateOrientedUI( + Context context, ViewGroup view, Point size, NTPImage ntpImage) { LinearLayout parentLayout = (LinearLayout)view.findViewById(R.id.parent_layout); - ViewGroup mainLayout = view.findViewById(R.id.ntp_main_layout); + CompositorViewHolder compositorView = view.findViewById(R.id.compositor_view_holder); ViewGroup imageCreditLayout = view.findViewById(R.id.image_credit_layout); + ViewGroup newsRecyclerLayout = view.findViewById(R.id.newsRecycler); + + ViewGroup mainLayout = view.findViewById(R.id.ntp_main_layout); ImageView sponsoredLogo = (ImageView)view.findViewById(R.id.sponsored_logo); FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(dpToPx(context, 170), dpToPx(context, 170)); parentLayout.removeView(mainLayout); parentLayout.removeView(imageCreditLayout); + parentLayout.removeView(newsRecyclerLayout); parentLayout.addView(mainLayout); parentLayout.addView(imageCreditLayout); - - parentLayout.setOrientation(LinearLayout.VERTICAL); + parentLayout.addView(newsRecyclerLayout); boolean isTablet = DeviceFormFactor.isNonMultiDisplayContextOnTablet(context); DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); float dpWidth = displayMetrics.widthPixels / displayMetrics.density; + float dpHeight = displayMetrics.heightPixels / displayMetrics.density; CardView widgetLayout = (CardView) view.findViewById(R.id.ntp_widget_cardview_layout); LinearLayout.LayoutParams widgetLayoutParams = new LinearLayout.LayoutParams( (isTablet ? (int) (dpWidth * 0.75) @@ -117,10 +178,36 @@ public class NTPUtil { LinearLayout.LayoutParams imageCreditLayoutParams = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); - imageCreditLayout.setLayoutParams(imageCreditLayoutParams); - layoutParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL; - layoutParams.setMargins(0, 0, 0, dpToPx(context, 5)); + if (ChromeFeatureList.isEnabled(BraveFeatureList.BRAVE_NEWS)) { + int topMargin = correctImageCreditLayoutTopPosition(ntpImage); + if ((BravePrefServiceBridge.getInstance().getNewsOptIn() + && BravePrefServiceBridge.getInstance().getShowNews())) { + imageCreditLayoutParams.setMargins(0, topMargin, 0, 50); + } + + ViewGroup optinLayout = view.findViewById(R.id.optin_layout_id); + LinearLayout.LayoutParams optinLayoutParams = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); + optinLayoutParams.setMargins(30, imageCreditLayout.getBottom(), 30, 500); + optinLayout.setLayoutParams(optinLayoutParams); + + View feedSpinner = (View) view.findViewById(R.id.feed_spinner); + FrameLayout.LayoutParams feedSpinnerParams = + (FrameLayout.LayoutParams) feedSpinner.getLayoutParams(); + feedSpinnerParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL; + feedSpinnerParams.setMargins(0, 0, 0, dpToPx(context, 35)); + feedSpinner.setLayoutParams(feedSpinnerParams); + + layoutParams.gravity = Gravity.CENTER_HORIZONTAL; + + } else { + layoutParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL; + layoutParams.setMargins(0, 0, 0, dpToPx(context, 5)); + } + parentLayout.setOrientation(LinearLayout.VERTICAL); + + imageCreditLayout.setLayoutParams(imageCreditLayoutParams); sponsoredLogo.setLayoutParams(layoutParams); } diff --git a/android/java/org/chromium/chrome/browser/preferences/BravePrefServiceBridge.java b/android/java/org/chromium/chrome/browser/preferences/BravePrefServiceBridge.java index 61a6ccff062..d68c7883047 100644 --- a/android/java/org/chromium/chrome/browser/preferences/BravePrefServiceBridge.java +++ b/android/java/org/chromium/chrome/browser/preferences/BravePrefServiceBridge.java @@ -264,6 +264,23 @@ public class BravePrefServiceBridge { return BravePrefServiceBridgeJni.get().getWebrtcPolicy(); } + public void setNewsOptIn(boolean value) { + BravePrefServiceBridgeJni.get().setNewsOptIn(value); + } + + public boolean getNewsOptIn() { + boolean getNewsOptIn = BravePrefServiceBridgeJni.get().getNewsOptIn(); + return BravePrefServiceBridgeJni.get().getNewsOptIn(); + } + public void setShowNews(boolean value) { + BravePrefServiceBridgeJni.get().setShowNews(value); + } + + public boolean getShowNews() { + boolean getShowNews = BravePrefServiceBridgeJni.get().getShowNews(); + return BravePrefServiceBridgeJni.get().getShowNews(); + } + @NativeMethods interface Natives { void setCosmeticFilteringControlType(int type); @@ -338,5 +355,11 @@ public class BravePrefServiceBridge { void setWebrtcPolicy(int policy); int getWebrtcPolicy(); + + void setNewsOptIn(boolean value); + boolean getNewsOptIn(); + + void setShowNews(boolean value); + boolean getShowNews(); } } diff --git a/android/java/org/chromium/chrome/browser/preferences/BravePreferenceKeys.java b/android/java/org/chromium/chrome/browser/preferences/BravePreferenceKeys.java index c4b0cd464d8..97b72d2e3f0 100644 --- a/android/java/org/chromium/chrome/browser/preferences/BravePreferenceKeys.java +++ b/android/java/org/chromium/chrome/browser/preferences/BravePreferenceKeys.java @@ -25,4 +25,8 @@ public final class BravePreferenceKeys { "org.chromium.chrome.browser.Brave_Tab_Groups_Enabled"; public static final String BRAVE_DISABLE_SHARING_HUB = "org.chromium.chrome.browser.Brave_Disable_Sharing_Hub"; + public static final String BRAVE_NEWS_CARDS_VISITED = "brave_news_cards_visited"; + public static final String BRAVE_NEWS_CHANGE_SOURCE = "brave_news_change_source"; + public static final String BRAVE_NEWS_FEED_HASH = "brave_news_feed_hash"; + public static final String BRAVE_NEWS_PREF_SHOW_NEWS = "kNewTabPageShowToday"; } diff --git a/android/java/org/chromium/chrome/browser/settings/BraveAddNewsSources.java b/android/java/org/chromium/chrome/browser/settings/BraveAddNewsSources.java new file mode 100644 index 00000000000..9b5794d7f64 --- /dev/null +++ b/android/java/org/chromium/chrome/browser/settings/BraveAddNewsSources.java @@ -0,0 +1,154 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.chromium.chrome.browser.settings; + +import android.os.Bundle; +import android.widget.EditText; + +import androidx.preference.CheckBoxPreference; +import androidx.preference.EditTextPreference; +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceGroup; +import androidx.preference.PreferenceManager; +import androidx.preference.PreferenceScreen; + +import org.chromium.base.ContextUtils; +import org.chromium.brave_news.mojom.BraveNewsController; +import org.chromium.brave_news.mojom.Publisher; +import org.chromium.chrome.R; +import org.chromium.chrome.browser.BraveLaunchIntentDispatcher; +import org.chromium.chrome.browser.brave_news.BraveNewsControllerFactory; +import org.chromium.chrome.browser.settings.BravePreferenceFragment; +import org.chromium.components.browser_ui.settings.SettingsUtils; +import org.chromium.mojo.bindings.ConnectionErrorHandler; +import org.chromium.mojo.system.MojoException; + +import java.util.ArrayList; +import java.util.Map; + +public class BraveAddNewsSources extends BravePreferenceFragment + implements Preference.OnPreferenceChangeListener, ConnectionErrorHandler { + private static final String PREF_ADD_SOURCES = "news_source_1"; + private EditTextPreference addSource; + private EditText mEditText; + private PreferenceScreen mainScreen; + private BraveNewsController mBraveNewsController; + private ArrayList mPublishers; + + public static int getPreferenceSummary() { + return BraveLaunchIntentDispatcher.useCustomTabs() ? R.string.text_on : R.string.text_off; + } + + @Override + public void onConnectionError(MojoException e) { + if (mBraveNewsController != null) { + mBraveNewsController.close(); + } + mBraveNewsController = null; + InitBraveNewsController(); + } + + private void InitBraveNewsController() { + if (mBraveNewsController != null) { + return; + } + + mBraveNewsController = + BraveNewsControllerFactory.getInstance().getBraveNewsController(this); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getActivity().setTitle(R.string.news_add_source); + SettingsUtils.addPreferencesFromResource(this, R.xml.brave_news_sources_default); + findPreference(PREF_ADD_SOURCES).setOnPreferenceChangeListener(this); + InitBraveNewsController(); + + mainScreen = getPreferenceManager().getPreferenceScreen(); + + addSource = (EditTextPreference) findPreference(PREF_ADD_SOURCES); + addSource.setPositiveButtonText(R.string.search_title); + mPublishers = new ArrayList<>(); + + mBraveNewsController.getPublishers((publishers) -> { + for (Map.Entry entry : publishers.entrySet()) { + String key = entry.getKey(); + Publisher publisher = entry.getValue(); + mPublishers.add(publisher); + CheckBoxPreference source = + new CheckBoxPreference(ContextUtils.getApplicationContext()); + source.setTitle(publisher.publisherName); + mainScreen.addPreference(source); + } + }); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + String key = preference.getKey(); + + if (PREF_ADD_SOURCES.equals(key)) { + PreferenceManager manager = getPreferenceManager(); + PreferenceScreen sourcesScreen = + manager.createPreferenceScreen(ContextUtils.getApplicationContext()); + sourcesScreen.setTitle((String) newValue); + // fetch results from API + // populate checkboxes + CheckBoxPreference source1 = + new CheckBoxPreference(ContextUtils.getApplicationContext()); + source1.setTitle((String) newValue + " 1"); + source1.setChecked(true); + sourcesScreen.addPreference(source1); + + CheckBoxPreference source2 = + new CheckBoxPreference(ContextUtils.getApplicationContext()); + source2.setTitle((String) newValue + " 2"); + source2.setChecked(true); + sourcesScreen.addPreference(source2); + + // end fetch. finish the layout + + Preference button = new Preference(ContextUtils.getApplicationContext()); + button.setTitle("Add"); + button.setKey("add_news_source"); + button.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + setPreferencesFromResource(R.xml.brave_news_preferences, null); + return true; + } + }); + sourcesScreen.addPreference(button); + + setPreferenceScreen(sourcesScreen); + return true; + } + return true; + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (mBraveNewsController != null) { + mBraveNewsController.close(); + } + } + + private ArrayList getPreferenceList(Preference p, ArrayList list) { + if (p instanceof PreferenceCategory || p instanceof PreferenceScreen) { + PreferenceGroup pGroup = (PreferenceGroup) p; + int pCount = pGroup.getPreferenceCount(); + for (int i = 0; i < pCount; i++) { + getPreferenceList(pGroup.getPreference(i), list); // recursive call + } + } else { + list.add(p); + } + return list; + } +} diff --git a/android/java/org/chromium/chrome/browser/settings/BraveMainPreferencesBase.java b/android/java/org/chromium/chrome/browser/settings/BraveMainPreferencesBase.java index bc145b8d179..b0ccf438277 100644 --- a/android/java/org/chromium/chrome/browser/settings/BraveMainPreferencesBase.java +++ b/android/java/org/chromium/chrome/browser/settings/BraveMainPreferencesBase.java @@ -65,6 +65,7 @@ public class BraveMainPreferencesBase private static final String PREF_PRIVACY = "privacy"; private static final String PREF_SHIELDS_AND_PRIVACY = "brave_shields_and_privacy"; private static final String PREF_BRAVE_SEARCH_ENGINES = "brave_search_engines"; + private static final String PREF_BRAVE_NEWS = "brave_news"; private static final String PREF_SYNC = "brave_sync_layout"; private static final String PREF_PASSWORDS = "passwords"; private static final String PREF_NOTIFICATIONS = "notifications"; @@ -133,7 +134,9 @@ public class BraveMainPreferencesBase removePreferenceIfPresent(PREF_ADVANCED_SECTION); removePreferenceIfPresent(PREF_PRIVACY); removePreferenceIfPresent(PREF_BRAVE_VPN_CALLOUT); - + if (!ChromeFeatureList.isEnabled(BraveFeatureList.BRAVE_NEWS)) { + removePreferenceIfPresent(PREF_BRAVE_NEWS); + } updateSearchEnginePreference(); updateSummaries(); diff --git a/android/java/org/chromium/chrome/browser/settings/BraveNewsCategorySources.java b/android/java/org/chromium/chrome/browser/settings/BraveNewsCategorySources.java new file mode 100644 index 00000000000..3a2a08f5680 --- /dev/null +++ b/android/java/org/chromium/chrome/browser/settings/BraveNewsCategorySources.java @@ -0,0 +1,185 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.chromium.chrome.browser.settings; + +import android.content.SharedPreferences; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; + +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceManager; +import androidx.preference.PreferenceScreen; +import androidx.preference.SwitchPreference; +import androidx.preference.SwitchPreferenceCompat; + +import org.chromium.base.ContextUtils; +import org.chromium.base.Log; +import org.chromium.brave_news.mojom.BraveNewsController; +import org.chromium.brave_news.mojom.Publisher; +import org.chromium.brave_news.mojom.UserEnabled; +import org.chromium.chrome.R; +import org.chromium.chrome.browser.brave_news.BraveNewsControllerFactory; +import org.chromium.chrome.browser.preferences.BravePreferenceKeys; +import org.chromium.chrome.browser.preferences.SharedPreferencesManager; +import org.chromium.chrome.browser.settings.SearchPreference; +import org.chromium.components.browser_ui.settings.ChromeSwitchPreference; +import org.chromium.mojo.bindings.ConnectionErrorHandler; +import org.chromium.mojo.system.MojoException; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.TreeMap; + +public class BraveNewsCategorySources + extends PreferenceFragmentCompat implements ConnectionErrorHandler { + private PreferenceManager mPreferenceManager; + private BraveNewsController mBraveNewsController; + private String mCategoryArg; + + private TreeMap> mCategsPublishers; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Bundle args = getArguments(); + mCategoryArg = args.getString("category"); + } + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + InitBraveNewsController(); + addPreferencesFromResource(R.xml.brave_news_sources_default); + getActivity().setTitle(mCategoryArg); + + mCategsPublishers = new TreeMap<>(); + mPreferenceManager = getPreferenceManager(); + mBraveNewsController.getPublishers((publishers) -> { + List allPublishers = new ArrayList<>(); + List categoryPublishers = new ArrayList<>(); + for (Map.Entry entry : publishers.entrySet()) { + String key = entry.getKey(); + + Publisher publisher = entry.getValue(); + if (publisher.categoryName.toLowerCase(Locale.ROOT) + .equals(mCategoryArg.toLowerCase(Locale.ROOT))) { + categoryPublishers.add(publisher); + } + + mCategsPublishers.put(publisher.categoryName, categoryPublishers); + allPublishers.add(publisher); + } + Comparator compareByName = + (Publisher o1, Publisher o2) -> o1.publisherName.compareTo(o2.publisherName); + Collections.sort(categoryPublishers, compareByName); + Collections.sort(allPublishers, compareByName); + mCategsPublishers.put("All Sources", allPublishers); + + addCategs(mCategsPublishers); + }); + } + + @Override + public void onConnectionError(MojoException e) { + if (mBraveNewsController != null) { + mBraveNewsController.close(); + } + mBraveNewsController = null; + InitBraveNewsController(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (mBraveNewsController != null) { + mBraveNewsController.close(); + } + } + + private void InitBraveNewsController() { + if (mBraveNewsController != null) { + return; + } + + mBraveNewsController = + BraveNewsControllerFactory.getInstance().getBraveNewsController(this); + } + + private void addCategs(TreeMap> publisherCategories) { + PreferenceScreen sourcesScreen = getPreferenceScreen(); + + SearchPreference search = new SearchPreference(ContextUtils.getApplicationContext()); + search.setKey("search_pref"); + + sourcesScreen.addPreference(search); + + final List publishers = publisherCategories.get(mCategoryArg); + + if (publishers == null) { + return; + } + + search.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + for (Publisher publisher : publishers) { + SwitchPreference publisherSource = + mPreferenceManager.findPreference(publisher.publisherName); + if (publisher.publisherName.toLowerCase(Locale.ROOT) + .contains((String) newValue)) { + publisherSource.setVisible(true); + } else { + publisherSource.setVisible(false); + } + } + return false; + } + }); + + for (Publisher publisher : publishers) { + SwitchPreference source = new SwitchPreference(ContextUtils.getApplicationContext()); + source.setOnPreferenceChangeListener(null); + boolean enabled = false; + if (publisher.userEnabledStatus == UserEnabled.ENABLED) { + enabled = true; + } else if (publisher.userEnabledStatus == UserEnabled.NOT_MODIFIED) { + enabled = publisher.isEnabled; + } + source.setTitle(publisher.publisherName); + source.setKey(publisher.publisherName); + source.setChecked(enabled); + source.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + @UserEnabled.EnumType + int type = UserEnabled.NOT_MODIFIED; + if ((boolean) newValue) { + type = UserEnabled.ENABLED; + } else { + type = UserEnabled.DISABLED; + } + + SharedPreferencesManager.getInstance().writeBoolean( + BravePreferenceKeys.BRAVE_NEWS_CHANGE_SOURCE, true); + + if (mBraveNewsController != null) { + mBraveNewsController.setPublisherPref(publisher.publisherId, type); + } + + source.setChecked((boolean) newValue); + return false; + } + }); + sourcesScreen.addPreference(source); + } + } +} diff --git a/android/java/org/chromium/chrome/browser/settings/BraveNewsPreferences.java b/android/java/org/chromium/chrome/browser/settings/BraveNewsPreferences.java new file mode 100644 index 00000000000..f8be87f18cf --- /dev/null +++ b/android/java/org/chromium/chrome/browser/settings/BraveNewsPreferences.java @@ -0,0 +1,197 @@ + +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.chromium.chrome.browser.settings; + +import android.os.Bundle; +import android.widget.EditText; + +import androidx.annotation.Nullable; +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceManager; +import androidx.preference.PreferenceScreen; +import androidx.preference.SwitchPreference; + +import org.chromium.base.ContextUtils; +import org.chromium.brave_news.mojom.BraveNewsController; +import org.chromium.brave_news.mojom.Publisher; +import org.chromium.chrome.R; +import org.chromium.chrome.browser.BraveLaunchIntentDispatcher; +import org.chromium.chrome.browser.brave_news.BraveNewsControllerFactory; +import org.chromium.chrome.browser.preferences.BravePrefServiceBridge; +import org.chromium.chrome.browser.settings.BravePreferenceFragment; +import org.chromium.components.browser_ui.settings.ChromeBasePreference; +import org.chromium.components.browser_ui.settings.ChromeSwitchPreference; +import org.chromium.components.browser_ui.settings.SettingsUtils; +import org.chromium.mojo.bindings.ConnectionErrorHandler; +import org.chromium.mojo.system.MojoException; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +public class BraveNewsPreferences extends BravePreferenceFragment + implements Preference.OnPreferenceChangeListener, ConnectionErrorHandler { + public static final String PREF_TURN_ON_NEWS = "kBraveTodayOptedIn"; + public static final String PREF_SHOW_NEWS = "kNewTabPageShowToday"; + public static final String PREF_SHOW_OPTIN = "show_optin"; + public static final String PREF_SOURCES_SECTION = "your_sources_section"; + public static final String PREF_ADD_SOURCES = "add_source_news"; + + private ChromeSwitchPreference mTurnOnNews; + private ChromeSwitchPreference mShowNews; + private PreferenceScreen mMainScreen; + private PreferenceManager mPreferenceManager; + private TreeMap> mCategsPublishers; + + private final HashMap mRemovedPreferences = new HashMap<>(); + private BraveNewsController mBraveNewsController; + private PreferenceFragmentCompat mSettingsFragment; + + public static int getPreferenceSummary() { + return BraveLaunchIntentDispatcher.useCustomTabs() ? R.string.text_on : R.string.text_off; + } + + @Override + public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) { + SettingsUtils.addPreferencesFromResource(this, R.xml.brave_news_preferences); + InitBraveNewsController(); + mTurnOnNews = (ChromeSwitchPreference) findPreference(PREF_TURN_ON_NEWS); + mShowNews = (ChromeSwitchPreference) findPreference(PREF_SHOW_NEWS); + mSettingsFragment = this; + + mTurnOnNews.setOnPreferenceChangeListener(this); + mShowNews.setOnPreferenceChangeListener(this); + + mCategsPublishers = new TreeMap<>(); + mBraveNewsController.getPublishers((publishers) -> { + List allPublishers = new ArrayList<>(); + List categoryPublishers = new ArrayList<>(); + for (Map.Entry entry : publishers.entrySet()) { + Publisher publisher = entry.getValue(); + categoryPublishers.add(publisher); + mCategsPublishers.put(publisher.categoryName, categoryPublishers); + } + mCategsPublishers.put("All Sources", allPublishers); + addCategs(mCategsPublishers); + }); + } + + private void addCategs(TreeMap> publisherCategories) { + for (Map.Entry> map : publisherCategories.entrySet()) { + String category = map.getKey(); + + ChromeBasePreference source = + new ChromeBasePreference(ContextUtils.getApplicationContext()); + source.setTitle(category); + source.setKey(category); + Bundle prefExtras = source.getExtras(); + prefExtras.putString("category", category); + source.setFragment("org.chromium.chrome.browser.settings.BraveNewsCategorySources"); + mMainScreen.addPreference(source); + } + } + + @Override + public void onConnectionError(MojoException e) { + if (mBraveNewsController != null) { + mBraveNewsController.close(); + } + mBraveNewsController = null; + InitBraveNewsController(); + } + + private void InitBraveNewsController() { + if (mBraveNewsController != null) { + return; + } + + mBraveNewsController = + BraveNewsControllerFactory.getInstance().getBraveNewsController(this); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getActivity().setTitle(R.string.brave_news_title); + + mPreferenceManager = getPreferenceManager(); + mMainScreen = mPreferenceManager.getPreferenceScreen(); + + boolean isNewsOn = BravePrefServiceBridge.getInstance().getNewsOptIn(); + + if (!isNewsOn) { + mTurnOnNews.setChecked(false); + mShowNews.setVisible(false); + } else { + mTurnOnNews.setChecked(true); + mTurnOnNews.setVisible(false); + mShowNews.setVisible(true); + if (BravePrefServiceBridge.getInstance().getShowNews()) { + mShowNews.setChecked(true); + } else { + mShowNews.setChecked(false); + } + } + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + String key = preference.getKey(); + if (PREF_TURN_ON_NEWS.equals(key)) { + BravePrefServiceBridge.getInstance().setNewsOptIn((boolean) newValue); + if ((boolean) newValue) { + mShowNews.setVisible(true); + mShowNews.setChecked(true); + BravePrefServiceBridge.getInstance().setShowNews(true); + } else { + mShowNews.setVisible(false); + } + } else if (PREF_SHOW_NEWS.equals(key)) { + BravePrefServiceBridge.getInstance().setShowNews((boolean) newValue); + for (int i = 0; i < getPreferenceScreen().getPreferenceCount(); i++) { + Preference pref = getPreferenceScreen().getPreference(i); + if (!pref.getKey().equals(PREF_SHOW_NEWS) + && !pref.getKey().equals(PREF_TURN_ON_NEWS)) { + pref.setVisible((boolean) newValue); + } + } + } + return true; + } + + private void removePreferenceIfPresent(String key) { + Preference preference = getPreferenceScreen().findPreference(key); + if (preference != null) { + getPreferenceScreen().removePreference(preference); + mRemovedPreferences.put(preference.getKey(), preference); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (mBraveNewsController != null) { + mBraveNewsController.close(); + } + } + + /** + * We need to override it to avoid NullPointerException in Chromium's child classes + */ + @Override + public T findPreference(CharSequence key) { + T result = super.findPreference(key); + if (result == null) { + result = (T) mRemovedPreferences.get((String) key); + } + return result; + } +} diff --git a/android/java/org/chromium/chrome/browser/settings/SearchPreference.java b/android/java/org/chromium/chrome/browser/settings/SearchPreference.java new file mode 100644 index 00000000000..9aed903bfff --- /dev/null +++ b/android/java/org/chromium/chrome/browser/settings/SearchPreference.java @@ -0,0 +1,45 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.chromium.chrome.browser.settings; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; + +import androidx.appcompat.widget.SearchView; +import androidx.preference.Preference; +import androidx.preference.PreferenceViewHolder; + +import org.chromium.chrome.R; + +public class SearchPreference extends Preference { + public SearchPreference(Context context) { + super(context); + setLayoutResource(R.layout.custom_layout_preference); + setWidgetLayoutResource(R.layout.search_preference); + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + SearchView searchView = (SearchView) holder.findViewById(R.id.search_preference); + + searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String s) { + getOnPreferenceChangeListener().onPreferenceChange(SearchPreference.this, s); + return false; + } + + @Override + public boolean onQueryTextChange(String s) { + getOnPreferenceChangeListener().onPreferenceChange(SearchPreference.this, s); + return false; + } + }); + } +} diff --git a/android/java/org/chromium/chrome/browser/toolbar/top/BraveToolbarLayoutImpl.java b/android/java/org/chromium/chrome/browser/toolbar/top/BraveToolbarLayoutImpl.java index d725f172220..96185fa5c3c 100644 --- a/android/java/org/chromium/chrome/browser/toolbar/top/BraveToolbarLayoutImpl.java +++ b/android/java/org/chromium/chrome/browser/toolbar/top/BraveToolbarLayoutImpl.java @@ -76,7 +76,6 @@ import org.chromium.chrome.browser.local_database.BraveStatsTable; import org.chromium.chrome.browser.local_database.DatabaseHelper; import org.chromium.chrome.browser.local_database.SavedBandwidthTable; import org.chromium.chrome.browser.notifications.retention.RetentionNotificationUtil; -import org.chromium.chrome.browser.ntp.BraveNewTabPageLayout; import org.chromium.chrome.browser.ntp.NewTabPage; import org.chromium.chrome.browser.onboarding.BraveTalkOptInPopup; import org.chromium.chrome.browser.onboarding.BraveTalkOptInPopupListener; diff --git a/android/java/org/chromium/chrome/browser/util/ConfigurationUtils.java b/android/java/org/chromium/chrome/browser/util/ConfigurationUtils.java index 610dd9229a9..4fe70a19aa4 100644 --- a/android/java/org/chromium/chrome/browser/util/ConfigurationUtils.java +++ b/android/java/org/chromium/chrome/browser/util/ConfigurationUtils.java @@ -7,8 +7,14 @@ package org.chromium.chrome.browser.util; +import android.app.Activity; import android.content.Context; import android.content.res.Configuration; +import android.util.DisplayMetrics; + +import org.chromium.ui.base.DeviceFormFactor; + +import java.util.HashMap; public class ConfigurationUtils { public static boolean isLandscape(Context context) { @@ -19,4 +25,34 @@ public class ConfigurationUtils { return false; } } -} \ No newline at end of file + + public static boolean isTablet(Context context) { + return DeviceFormFactor.isNonMultiDisplayContextOnTablet(context); + } + + public static HashMap getDisplayMetrics(Activity activity) { + DisplayMetrics displayMetrics = new DisplayMetrics(); + activity.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); + int deviceWidth = displayMetrics.widthPixels; + int deviceHeight = displayMetrics.heightPixels; + + HashMap map = new HashMap(); + map.put("width", deviceWidth); + map.put("height", deviceHeight); + + return map; + } + + public static HashMap getDpDisplayMetrics(Activity activity) { + DisplayMetrics displayMetrics = new DisplayMetrics(); + activity.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); + float dpWidth = displayMetrics.widthPixels / displayMetrics.density; + float dpHeight = displayMetrics.heightPixels / displayMetrics.density; + + HashMap map = new HashMap(); + map.put("width", dpWidth); + map.put("height", dpHeight); + + return map; + } +} diff --git a/android/java/res/drawable-hdpi/sparkling_newsreader.png b/android/java/res/drawable-hdpi/sparkling_newsreader.png new file mode 100644 index 0000000000000000000000000000000000000000..95a9b703f7211b96f76b3b53a59f4d44265457cc GIT binary patch literal 878 zcmV-!1CjiRP)`(`(BvztvsbEp;d=uHr%M|;qVSiHC%JS%t+QBPh%6og_SUWGym1(Bsv z@aCy{lnS1LARc0+Ag!{yGn3uS?9BW|fX`Vsf)i;2_LQX4JyT0G%=1nyuHGnvZtKGk$q6*)|3|3a!%Vrp$L9A~i zX(yG^Y_i(RYyWf_45{6fkGlgXyC zR9X$kBAs;tN2BrzpPz?u4zj{>+$TZ6b3q+kDV02jAOIYfR4}rNe>f{&@Fc-}4Fjjv z0?z}@(Ka~`l?J~-vw?3mh@+Tq+1-U!l&KfI+wFRIg-*x*?!+Jo)$5EiD9CYUmI3{H zISfKMv%`D1x?Ru1o5tC-+-A`4r(W<*r!$q&%#51%SQN8Zn=fSEar?GfL4ePe?jHYw zK0Zy!oCk^ep>d~_-^}TDC#Q4WtG~a|z1n;G&U?o}DtC>cp4>^5?xcIW%6NG16~-z` zoA=5%!M?Z#kX}Cje*XD|PqneWBru(D&!4)&1*e=-17LIW_^+&)6_RXm(SGQ~I@6dd zUQNdC(ImyBqGskK6?;W8C-1>LqhZ38X2ZTL<7E2+?mVUJ6gO|2Qa>T{#<@#{R3^RFHO$dPgdc70HufL`pwx_I{*Lx07*qoM6N<$ Ef;|(nBme*a literal 0 HcmV?d00001 diff --git a/android/java/res/drawable-mdpi/sparkling_newsreader.png b/android/java/res/drawable-mdpi/sparkling_newsreader.png new file mode 100644 index 0000000000000000000000000000000000000000..0c9b9114c84acbfa35010064ba83e4108d5d42a4 GIT binary patch literal 612 zcmV-q0-ODbP)7 z=~NID9HoLyNps01x!m_SoPiEONi{c!KltEyU*7wkhxfbtUii=SkyP5>S6Rx}=($J@ zR>UTf0)TeR)|o2HaY70NfJ~%OMA%LEJXfvMQlZuzZ~=HK$(fm-ww^*vOR9GJ!&Qmdeu|=5R_Eu52A+dzWN+JT^}P`np#`RF zT2f$US+~orN+o=m^yy}b~vXZdk2ghDz!6lb#@n}{nWb~4X>WrXrB zTUp^;vq?RykQJc?3Nj7IpyDBMj@uY_fq(!%^jKJ7&x{UvxBrcA`X}CCMxJ*b-pdF> z$nc;cB|;uhfMF=mw9mWP!?^C?j)Oo(W)5c|duAkngXtghQ<|2+1b3-J=IH`egJ-px zy*Qa}HZ>QN*=CGkNe1vY@9tBYo=kvw!Z6^{Fry~VQmd!SX-rrukpU`~SsY~4$JS1H zQ28y5Mm|}+dVP4W@#g#5$=Em~Ie;Y5Qm?NMokYjSv09_miq&d&bd(Nt&}!?^s74zb z4nU2^i)uXiIC%c>W}eIAf67FiUSH3DGy`SZ&dsDJW8)Z2S5ahNKsO{V9wm7SfL=mr yA>uHOd*^GlZ=)KG@<(ax7s{W=g8szLe!&mB^$j!SCwN5w00009(E6vuz(zV~L@na;FD5(pa!0X4uzt82i-g$u0#X>>LXx`=+tdw!)OBbz=g%t(uxAhcF3g;n)w|@<10!`p zOA5>^nP1f5T$ZovF^1^x%$TGwK)tfWm&E})(SBB!UG53tgLYt}IRg$z0>^<&hHdpG zi@-I6VRRCEcN@G2oanBny(M)*_kl^W#VU*5Bg7BIg1yy=?bWNw1tC?5k_w?GCKO1r`9&Rhb_c(%)VXNJy8tpWi&cHR zHRmq`%Up_i&;Uln zAB8OQK<4ymd9(B_H`}C2Xl{fem(TO_!V*sbI8U}{b{+U>9lHcTvr6hIl{#%g15m9p zBqjY2AXu5N==q@m4pb_{zLl3a2i_y+@PK(w{7-|EeQ|5Woib7>wD%-o5VXslK>(iv zBZb2Hb_+|)BndT)pxZw7np6r6;BQv96$cy!_E)Pk6Z`KB$Gd+mXw0C19SC!hf;m&x?T!`{==T6bw`Dx8>@=w|#AK%+qe zh+A!mnI&e{G&9}i=gmwjE7Vs%r-PT7rb?lvN})FSA&#Do=#CRUwhe<9{{S5Boq({*4cM45QoOQ+ouhpCqkD>a>tLO$shkY7FcxY z$SZ@VN|GBl+t^s>nF9hDzd4Aj%Xr{$4qi??8>>nF%=*E7^rc36!F0>>wGrwa*xt)WLeN`^c{SKVkH@R_B-N*>BB%7Jx zGZ(SFzmJ2Nh@DLj&fyHA_3Q-%z)GE&T9aSMbF6h~%|D_hCITB9OX9xl!Eo#FM*nzcprM{I{{R3007*qoM6N<$f|5^01ONa4 literal 0 HcmV?d00001 diff --git a/android/java/res/drawable/card_bg.xml b/android/java/res/drawable/card_bg.xml new file mode 100644 index 00000000000..e82d93516c4 --- /dev/null +++ b/android/java/res/drawable/card_bg.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/android/java/res/drawable/ic_close.xml b/android/java/res/drawable/ic_close.xml new file mode 100644 index 00000000000..1107bb7469e --- /dev/null +++ b/android/java/res/drawable/ic_close.xml @@ -0,0 +1,9 @@ + + + diff --git a/android/java/res/drawable/ic_news.xml b/android/java/res/drawable/ic_news.xml new file mode 100644 index 00000000000..39ea550350a --- /dev/null +++ b/android/java/res/drawable/ic_news.xml @@ -0,0 +1,11 @@ + + + diff --git a/android/java/res/drawable/ic_news_settings.xml b/android/java/res/drawable/ic_news_settings.xml new file mode 100644 index 00000000000..678d404a2a3 --- /dev/null +++ b/android/java/res/drawable/ic_news_settings.xml @@ -0,0 +1,11 @@ + + + diff --git a/android/java/res/drawable/ic_promoted.xml b/android/java/res/drawable/ic_promoted.xml new file mode 100644 index 00000000000..d0e8ba5405b --- /dev/null +++ b/android/java/res/drawable/ic_promoted.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/java/res/drawable/news_button_bg.xml b/android/java/res/drawable/news_button_bg.xml new file mode 100644 index 00000000000..14e7f53c518 --- /dev/null +++ b/android/java/res/drawable/news_button_bg.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/android/java/res/drawable/rounded_top_corners.xml b/android/java/res/drawable/rounded_top_corners.xml new file mode 100644 index 00000000000..03ad302b0ac --- /dev/null +++ b/android/java/res/drawable/rounded_top_corners.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/android/java/res/layout/brave_news_card_menu.xml b/android/java/res/layout/brave_news_card_menu.xml new file mode 100644 index 00000000000..d5dc1734ed2 --- /dev/null +++ b/android/java/res/layout/brave_news_card_menu.xml @@ -0,0 +1,78 @@ + + + + + + + +