From d3d2abf5b02ade072277b449a4c6462f5d85ff89 Mon Sep 17 00:00:00 2001 From: Manuel Martin Date: Thu, 18 Jun 2020 01:59:39 +0200 Subject: [PATCH] What's new implementation (#3441) Show what's new on first run --- app/build.gradle | 2 + .../vrbrowser/browser/SettingsStore.java | 64 +++++++++++++++++-- .../ui/viewmodel/SettingsViewModel.java | 58 +++++++++++++++-- .../ui/viewmodel/WindowViewModel.java | 26 ++++++++ .../ui/widgets/NavigationBarWidget.java | 24 ++++++- .../vrbrowser/ui/widgets/WindowWidget.java | 17 ++++- .../ui/widgets/settings/SettingsWidget.java | 22 +++++++ .../vrbrowser/utils/RemoteProperties.kt | 5 ++ app/src/main/res/drawable/ic_whats_new.xml | 10 +++ app/src/main/res/layout/navigation_bar.xml | 12 +++- .../res/layout/navigation_bar_fullscreen.xml | 3 + .../main/res/layout/navigation_bar_menu.xml | 3 + .../res/layout/navigation_bar_navigation.xml | 28 ++++++++ app/src/main/res/layout/settings.xml | 30 ++++++++- app/src/main/res/values/non_L10n.xml | 3 + app/src/main/res/values/strings.xml | 8 +++ 16 files changed, 301 insertions(+), 14 deletions(-) create mode 100644 app/src/common/shared/org/mozilla/vrbrowser/utils/RemoteProperties.kt create mode 100644 app/src/main/res/drawable/ic_whats_new.xml diff --git a/app/build.gradle b/app/build.gradle index 28a7b5b47..e21a3597c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -105,10 +105,12 @@ android { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' signingConfig getUseDebugSigningOnRelease() ? debug.signingConfig : release.signingConfig + buildConfigField 'String', 'PROPS_ENDPOINT', '"http://mixedreality.mozilla.org/FirefoxReality/props.json"' } debug { applicationIdSuffix getDevApplicationIdSuffix() pseudoLocalesEnabled true + buildConfigField 'String', 'PROPS_ENDPOINT', '"http://mixedreality.mozilla.org/FirefoxReality/props.json"' } } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/browser/SettingsStore.java b/app/src/common/shared/org/mozilla/vrbrowser/browser/SettingsStore.java index bfaee812a..6c838b516 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/browser/SettingsStore.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/browser/SettingsStore.java @@ -2,7 +2,6 @@ import android.content.Context; import android.content.SharedPreferences; -import android.database.Observable; import android.graphics.Color; import android.os.StrictMode; import android.preference.PreferenceManager; @@ -10,8 +9,6 @@ import androidx.annotation.IntDef; import androidx.annotation.NonNull; -import androidx.databinding.ObservableBoolean; -import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProvider; import org.json.JSONArray; @@ -22,6 +19,8 @@ import org.mozilla.vrbrowser.BuildConfig; import org.mozilla.vrbrowser.R; import org.mozilla.vrbrowser.VRBrowserActivity; +import org.mozilla.vrbrowser.VRBrowserApplication; +import org.mozilla.vrbrowser.browser.engine.EngineProvider; import org.mozilla.vrbrowser.telemetry.GleanMetricsService; import org.mozilla.vrbrowser.telemetry.TelemetryWrapper; import org.mozilla.vrbrowser.ui.viewmodel.SettingsViewModel; @@ -30,11 +29,16 @@ import org.mozilla.vrbrowser.utils.StringUtils; import org.mozilla.vrbrowser.utils.SystemUtils; +import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Locale; +import mozilla.components.concept.fetch.Request; +import mozilla.components.concept.fetch.Response; + import static org.mozilla.vrbrowser.utils.ServoUtils.isServoAvailable; public class SettingsStore { @@ -125,6 +129,46 @@ public void initModel(@NonNull Context context) { ViewModelProvider.AndroidViewModelFactory.getInstance(((VRBrowserActivity) context).getApplication())) .get(SettingsViewModel.class); mSettingsViewModel.refresh(); + update(); + } + + /** + * Synchronizes the remote properties with the settings storage and notifies the model. + * Any consumer listening to the SettingsViewModel will get notified of the properties updates. + */ + private void update() { + ((VRBrowserApplication) mContext.getApplicationContext()).getExecutors().backgroundThread().post(() -> { + Request request = new Request( + BuildConfig.PROPS_ENDPOINT, + Request.Method.GET, + null, + null, + null, + null, + Request.Redirect.FOLLOW, + Request.CookiePolicy.INCLUDE, + false + ); + try { + Response response = EngineProvider.INSTANCE.getDefaultClient(mContext).fetch(request); + if (response.getStatus() == 200) { + String json = response.getBody().string(StandardCharsets.UTF_8); + SharedPreferences.Editor editor = mPrefs.edit(); + editor.putString(mContext.getString(R.string.settings_key_remote_props), json); + editor.commit(); + + mSettingsViewModel.setProps(json); + + } else { + String json = mPrefs.getString(mContext.getString(R.string.settings_key_remote_props), null); + mSettingsViewModel.setProps(json); + } + + } catch (IOException e) { + String json = mPrefs.getString(mContext.getString(R.string.settings_key_remote_props), null); + mSettingsViewModel.setProps(json); + } + }); } public boolean isCrashReportingEnabled() { @@ -738,5 +782,17 @@ public void setDownloadsSortingOrder(@SortingContextMenuWidget.Order int order) public @Storage int getDownloadsSortingOrder() { return mPrefs.getInt(mContext.getString(R.string.settings_key_downloads_sorting_order), DOWNLOADS_SORTING_ORDER_DEFAULT); } -} + public void setRemotePropsVersionName(String versionName) { + SharedPreferences.Editor editor = mPrefs.edit(); + editor.putString(mContext.getString(R.string.settings_key_remote_props_version_name), versionName); + editor.commit(); + + mSettingsViewModel.setPropsVersionName(versionName); + } + + public String getRemotePropsVersionName() { + return mPrefs.getString(mContext.getString(R.string.settings_key_remote_props_version_name), "0"); + } + +} diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/viewmodel/SettingsViewModel.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/viewmodel/SettingsViewModel.java index 038e4b12f..f18525188 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/viewmodel/SettingsViewModel.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/viewmodel/SettingsViewModel.java @@ -7,8 +7,18 @@ import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.MutableLiveData; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; + import org.mozilla.geckoview.ContentBlocking; +import org.mozilla.vrbrowser.BuildConfig; import org.mozilla.vrbrowser.browser.SettingsStore; +import org.mozilla.vrbrowser.utils.RemoteProperties; + +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.Map; public class SettingsViewModel extends AndroidViewModel { @@ -16,6 +26,9 @@ public class SettingsViewModel extends AndroidViewModel { private MutableLiveData isDRMEnabled; private MutableLiveData isPopupBlockingEnabled; private MutableLiveData isWebXREnabled; + private MutableLiveData propsVersionName; + private MutableLiveData> props; + private MutableLiveData isWhatsNewVisible; public SettingsViewModel(@NonNull Application application) { super(application); @@ -24,21 +37,36 @@ public SettingsViewModel(@NonNull Application application) { isDRMEnabled = new MutableLiveData<>(new ObservableBoolean(false)); isPopupBlockingEnabled = new MutableLiveData<>(new ObservableBoolean(false)); isWebXREnabled = new MutableLiveData<>(new ObservableBoolean(false)); + propsVersionName = new MutableLiveData<>(); + props = new MutableLiveData<>(Collections.emptyMap()); + isWhatsNewVisible = new MutableLiveData<>(new ObservableBoolean(false)); + + propsVersionName.observeForever(props -> isWhatsNewVisible()); + props.observeForever(versionName -> isWhatsNewVisible()); } public void refresh() { int level = SettingsStore.getInstance(getApplication().getBaseContext()).getTrackingProtectionLevel(); boolean isEnabled = level != ContentBlocking.EtpLevel.NONE; - isTrackingProtectionEnabled.setValue(new ObservableBoolean(isEnabled)); + isTrackingProtectionEnabled.postValue(new ObservableBoolean(isEnabled)); boolean drmEnabled = SettingsStore.getInstance(getApplication().getBaseContext()).isDrmContentPlaybackEnabled(); - isDRMEnabled = new MutableLiveData<>(new ObservableBoolean(drmEnabled)); + isDRMEnabled.postValue(new ObservableBoolean(drmEnabled)); boolean popupBlockingEnabled = SettingsStore.getInstance(getApplication().getBaseContext()).isPopUpsBlockingEnabled(); - isPopupBlockingEnabled = new MutableLiveData<>(new ObservableBoolean(popupBlockingEnabled)); + isPopupBlockingEnabled.postValue(new ObservableBoolean(popupBlockingEnabled)); boolean webxrEnabled = SettingsStore.getInstance(getApplication().getBaseContext()).isWebXREnabled(); - isWebXREnabled = new MutableLiveData<>(new ObservableBoolean(webxrEnabled)); + isWebXREnabled.postValue(new ObservableBoolean(webxrEnabled)); + + String appVersionName = SettingsStore.getInstance(getApplication().getBaseContext()).getRemotePropsVersionName(); + propsVersionName.postValue(appVersionName); + } + + private void isWhatsNewVisible() { + boolean value = !BuildConfig.VERSION_NAME.equals(propsVersionName.getValue()) && + props.getValue().containsKey(BuildConfig.VERSION_NAME); + isWhatsNewVisible.postValue(new ObservableBoolean(value)); } public void setIsTrackingProtectionEnabled(boolean isEnabled) { @@ -73,4 +101,26 @@ public MutableLiveData getIsWebXREnabled() { return isWebXREnabled; } + public void setPropsVersionName(String appVersionName) { + this.propsVersionName.setValue(appVersionName); + } + + public MutableLiveData getPropsVersionName() { + return propsVersionName; + } + + public void setProps(String json) { + Gson gson = new GsonBuilder().create(); + Type type = new TypeToken>() {}.getType(); + this.props.postValue(gson.fromJson(json, type)); + } + + public MutableLiveData> getProps() { + return props; + } + + public MutableLiveData getIsWhatsNewVisible() { + return isWhatsNewVisible; + } + } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/viewmodel/WindowViewModel.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/viewmodel/WindowViewModel.java index 87d3316df..3281eb700 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/viewmodel/WindowViewModel.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/viewmodel/WindowViewModel.java @@ -11,6 +11,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.databinding.ObservableBoolean; +import androidx.databinding.ObservableInt; import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.MediatorLiveData; import androidx.lifecycle.MutableLiveData; @@ -71,6 +72,8 @@ public class WindowViewModel extends AndroidViewModel { private MutableLiveData isDrmUsed; private MediatorLiveData isUrlBarButtonsVisible; private MediatorLiveData isUrlBarIconsVisible; + private MutableLiveData mWidth; + private MutableLiveData mHeight; public WindowViewModel(Application application) { super(application); @@ -180,6 +183,9 @@ public WindowViewModel(Application application) { isUrlBarIconsVisible.addSource(isLoading, mIsUrlBarIconsVisibleObserver); isUrlBarIconsVisible.addSource(isInsecureVisible, mIsUrlBarIconsVisibleObserver); isUrlBarIconsVisible.setValue(new ObservableBoolean(false)); + + mWidth = new MutableLiveData<>(new ObservableInt()); + mHeight = new MutableLiveData<>(new ObservableInt()); } private Observer mIsTopBarVisibleObserver = new Observer() { @@ -373,6 +379,8 @@ public void refresh() { isWebXRBlocked.postValue(isWebXRBlocked.getValue()); isTrackingEnabled.postValue(isTrackingEnabled.getValue()); isDrmUsed.postValue(isDrmUsed.getValue()); + mWidth.postValue(mWidth.getValue()); + mHeight.postValue(mHeight.getValue()); } @NonNull @@ -785,4 +793,22 @@ public MutableLiveData getIsUrlBarButtonsVisible() { public MutableLiveData getIsUrlBarIconsVisible() { return isUrlBarIconsVisible; } + + @NonNull + public MutableLiveData getWidth() { + return mWidth; + } + + public void setWidth(int width) { + this.mWidth.setValue(new ObservableInt(width)); + } + + @NonNull + public MutableLiveData getHeight() { + return mHeight; + } + + public void setHeight(int height) { + this.mHeight.setValue(new ObservableInt(height)); + } } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/NavigationBarWidget.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/NavigationBarWidget.java index fbe0c72e4..f18ce169c 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/NavigationBarWidget.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/NavigationBarWidget.java @@ -15,7 +15,6 @@ import android.util.Log; import android.util.Pair; import android.view.LayoutInflater; -import android.view.MotionEvent; import android.view.View; import android.webkit.URLUtil; import android.widget.EditText; @@ -29,6 +28,7 @@ import org.mozilla.geckoview.GeckoSession; import org.mozilla.geckoview.GeckoSessionSettings; +import org.mozilla.vrbrowser.BuildConfig; import org.mozilla.vrbrowser.R; import org.mozilla.vrbrowser.VRBrowserActivity; import org.mozilla.vrbrowser.VRBrowserApplication; @@ -44,6 +44,7 @@ import org.mozilla.vrbrowser.search.suggestions.SuggestionsProvider; import org.mozilla.vrbrowser.telemetry.GleanMetricsService; import org.mozilla.vrbrowser.telemetry.TelemetryWrapper; +import org.mozilla.vrbrowser.ui.viewmodel.SettingsViewModel; import org.mozilla.vrbrowser.ui.viewmodel.TrayViewModel; import org.mozilla.vrbrowser.ui.viewmodel.WindowViewModel; import org.mozilla.vrbrowser.ui.views.NavigationURLBar; @@ -59,6 +60,7 @@ import org.mozilla.vrbrowser.ui.widgets.menus.VideoProjectionMenuWidget; import org.mozilla.vrbrowser.utils.AnimationHelper; import org.mozilla.vrbrowser.utils.ConnectivityReceiver; +import org.mozilla.vrbrowser.utils.RemoteProperties; import org.mozilla.vrbrowser.utils.UrlUtils; import java.util.ArrayList; @@ -92,6 +94,7 @@ public interface NavigationListener { private WindowViewModel mViewModel; private TrayViewModel mTrayViewModel; + private SettingsViewModel mSettingsViewModel; private NavigationBarBinding mBinding; private AudioEngine mAudio; private WindowWidget mAttachedWindow; @@ -254,6 +257,14 @@ private void updateUI() { } }); + mBinding.navigationBarNavigation.whatsNew.setOnClickListener(v -> { + SettingsStore.getInstance(getContext()).setRemotePropsVersionName(BuildConfig.VERSION_NAME); + RemoteProperties props = mSettingsViewModel.getProps().getValue().get(BuildConfig.VERSION_NAME); + if (props != null) { + mWidgetManager.openNewTabForeground(props.getWhatsNewUrl()); + } + }); + mBinding.navigationBarNavigation.menuButton.setOnClickListener(view -> { view.requestFocusFromTouch(); @@ -510,6 +521,10 @@ public void detachFromWindow() { mViewModel.getIsPopUpBlocked().removeObserver(mIsPopUpBlockedListener); mViewModel = null; } + + if (mSettingsViewModel != null) { + mSettingsViewModel = null; + } } @Override @@ -526,8 +541,15 @@ public void attachToWindow(@NonNull WindowWidget aWindow) { (VRBrowserActivity)getContext(), ViewModelProvider.AndroidViewModelFactory.getInstance(((VRBrowserActivity) getContext()).getApplication())) .get(String.valueOf(mAttachedWindow.hashCode()), WindowViewModel.class); + mSettingsViewModel = new ViewModelProvider( + (VRBrowserActivity)getContext(), + ViewModelProvider.AndroidViewModelFactory.getInstance(((VRBrowserActivity) getContext()).getApplication())) + .get(SettingsViewModel.class); mBinding.setViewmodel(mViewModel); + mBinding.setSettingsmodel(mSettingsViewModel); + + mSettingsViewModel.refresh(); mViewModel.getIsActiveWindow().observeForever(mIsActiveWindowObserver); mViewModel.getIsPopUpBlocked().observeForever(mIsPopUpBlockedListener); diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/WindowWidget.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/WindowWidget.java index d4c383de8..6e6f5744d 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/WindowWidget.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/WindowWidget.java @@ -187,7 +187,7 @@ private void initialize(Context aContext) { mDownloadsManager = mWidgetManager.getServicesProvider().getDownloadsManager(); - // ModelView creation and observers setup + // ModelView creation and observers setup mViewModel = new ViewModelProvider( (VRBrowserActivity)getContext(), ViewModelProvider.AndroidViewModelFactory.getInstance(((VRBrowserActivity) getContext()).getApplication())) @@ -235,6 +235,9 @@ private void initialize(Context aContext) { if (mSession.getGeckoSession() != null) { onCurrentSessionChange(null, mSession.getGeckoSession()); } + + mViewModel.setWidth(mWidgetPlacement.width); + mViewModel.setHeight(mWidgetPlacement.height); } @Override @@ -646,6 +649,9 @@ public void enableVRVideoMode(int aVideoWidth, int aVideoHeight, boolean aResetB mWidgetPlacement.width = aVideoWidth + mBorderWidth * 2; mWidgetPlacement.height = aVideoHeight + mBorderWidth * 2; mWidgetManager.updateWidget(this); + + mViewModel.setWidth(mWidgetPlacement.width); + mViewModel.setHeight(mWidgetPlacement.height); } public void disableVRVideoMode() { @@ -661,6 +667,9 @@ public void disableVRVideoMode() { mWidgetPlacement.width = mWidthBackup; mWidgetPlacement.height = mHeightBackup; mWidgetManager.updateWidget(this); + + mViewModel.setWidth(mWidgetPlacement.width); + mViewModel.setHeight(mWidgetPlacement.height); } public void setWindowPlacement(@NonNull Windows.WindowPlacement aPlacement) { @@ -1010,6 +1019,9 @@ public void handleResizeEvent(float aWorldWidth, float aWorldHeight) { mWidgetPlacement.worldWidth = aWorldWidth; mWidgetManager.updateWidget(this); mWidgetManager.updateVisibleWidgets(); + + mViewModel.setWidth(mWidgetPlacement.width); + mViewModel.setHeight(mWidgetPlacement.height); } @Override @@ -1459,6 +1471,9 @@ public void setMaxWindowScale(float aScale) { mWidgetPlacement.worldWidth = maxSize.first; mWidgetPlacement.width = getWindowWidth(maxSize.first); mWidgetPlacement.height = (int) Math.ceil((float)mWidgetPlacement.width / currentAspect); + + mViewModel.setWidth(mWidgetPlacement.width); + mViewModel.setHeight(mWidgetPlacement.height); } } } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/SettingsWidget.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/SettingsWidget.java index 913ff4405..5e9edadb5 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/SettingsWidget.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/SettingsWidget.java @@ -24,22 +24,27 @@ import androidx.annotation.NonNull; import androidx.databinding.DataBindingUtil; +import androidx.lifecycle.ViewModelProvider; import org.mozilla.vrbrowser.BuildConfig; import org.mozilla.vrbrowser.R; +import org.mozilla.vrbrowser.VRBrowserActivity; import org.mozilla.vrbrowser.VRBrowserApplication; import org.mozilla.vrbrowser.audio.AudioEngine; import org.mozilla.vrbrowser.browser.Accounts; +import org.mozilla.vrbrowser.browser.SettingsStore; import org.mozilla.vrbrowser.browser.engine.Session; import org.mozilla.vrbrowser.browser.engine.SessionStore; import org.mozilla.vrbrowser.databinding.SettingsBinding; import org.mozilla.vrbrowser.db.SitePermission; import org.mozilla.vrbrowser.telemetry.GleanMetricsService; +import org.mozilla.vrbrowser.ui.viewmodel.SettingsViewModel; import org.mozilla.vrbrowser.ui.widgets.UIWidget; import org.mozilla.vrbrowser.ui.widgets.WidgetPlacement; import org.mozilla.vrbrowser.ui.widgets.WindowWidget; import org.mozilla.vrbrowser.ui.widgets.dialogs.RestartDialogWidget; import org.mozilla.vrbrowser.ui.widgets.dialogs.UIDialog; +import org.mozilla.vrbrowser.utils.RemoteProperties; import org.mozilla.vrbrowser.utils.StringUtils; import java.io.UnsupportedEncodingException; @@ -63,6 +68,7 @@ public class SettingsWidget extends UIDialog implements SettingsView.Delegate { private Accounts mAccounts; private Executor mUIThreadExecutor; private SettingsView.SettingViewType mOpenDialog; + private SettingsViewModel mSettingsViewModel; class VersionGestureListener extends GestureDetector.SimpleOnGestureListener { @@ -121,6 +127,13 @@ public void updateUI() { // Inflate this data binding layout mBinding = DataBindingUtil.inflate(inflater, R.layout.settings, this, true); + mSettingsViewModel = new ViewModelProvider( + (VRBrowserActivity)getContext(), + ViewModelProvider.AndroidViewModelFactory.getInstance(((VRBrowserActivity) getContext()).getApplication())) + .get(SettingsViewModel.class); + + mBinding.setSettingsmodel(mSettingsViewModel); + mBinding.backButton.setOnClickListener(v -> { if (mAudio != null) { mAudio.playSound(AudioEngine.Sound.CLICK); @@ -222,6 +235,15 @@ public void updateUI() { showView(SettingsView.SettingViewType.CONTROLLER); }); + mBinding.whatsNewButton.setOnClickListener(v -> { + SettingsStore.getInstance(getContext()).setRemotePropsVersionName(BuildConfig.VERSION_NAME); + RemoteProperties props = mSettingsViewModel.getProps().getValue().get(BuildConfig.VERSION_NAME); + if (props != null) { + mWidgetManager.openNewTabForeground(props.getWhatsNewUrl()); + } + onDismiss(); + }); + mCurrentView = null; } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/utils/RemoteProperties.kt b/app/src/common/shared/org/mozilla/vrbrowser/utils/RemoteProperties.kt new file mode 100644 index 000000000..3afca5e0e --- /dev/null +++ b/app/src/common/shared/org/mozilla/vrbrowser/utils/RemoteProperties.kt @@ -0,0 +1,5 @@ +package org.mozilla.vrbrowser.utils + +data class RemoteProperties( + val whatsNewUrl: String +) \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_whats_new.xml b/app/src/main/res/drawable/ic_whats_new.xml new file mode 100644 index 000000000..cebfaa869 --- /dev/null +++ b/app/src/main/res/drawable/ic_whats_new.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/navigation_bar.xml b/app/src/main/res/layout/navigation_bar.xml index 1578dd90d..8c11dd977 100644 --- a/app/src/main/res/layout/navigation_bar.xml +++ b/app/src/main/res/layout/navigation_bar.xml @@ -5,6 +5,9 @@ + + app:viewmodel="@{viewmodel}" + app:settingsmodel="@{settingsmodel}"/> + app:viewmodel="@{viewmodel}" + app:settingsmodel="@{settingsmodel}"/> + app:viewmodel="@{viewmodel}" + app:settingsmodel="@{settingsmodel}"/> diff --git a/app/src/main/res/layout/navigation_bar_fullscreen.xml b/app/src/main/res/layout/navigation_bar_fullscreen.xml index 346d7f9e8..7bc290910 100644 --- a/app/src/main/res/layout/navigation_bar_fullscreen.xml +++ b/app/src/main/res/layout/navigation_bar_fullscreen.xml @@ -5,6 +5,9 @@ + diff --git a/app/src/main/res/layout/navigation_bar_menu.xml b/app/src/main/res/layout/navigation_bar_menu.xml index 3de067c6a..6a8abcb85 100644 --- a/app/src/main/res/layout/navigation_bar_menu.xml +++ b/app/src/main/res/layout/navigation_bar_menu.xml @@ -5,6 +5,9 @@ + + + + + + + + + + + + + + + + + + settings_key_multi_e10s settings_key_downloads_external settings_key_downloads_sorting_order + settings_key_remote_props_version_name + settings_key_remote_props https://github.com/MozillaReality/FirefoxReality/wiki/Environments https://www.mozilla.org/privacy/firefox/ https://mixedreality.mozilla.org/fxr/report?src=browser-fxr&label=browser-firefox-reality&url=%1$s @@ -82,6 +84,7 @@ ABC %&= + https://github.com/MozillaReality/FirefoxReality/wiki/Release-notes-for-version-%1$s 选定 空格 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 286336208..57d6d62e2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -339,6 +339,10 @@ Most common reason for this state is that the account password was changed on another device. --> Reconnect + + What’s New + Restart Required @@ -1355,6 +1359,10 @@ tray and the Downloads view is closed. The button it labels, when pressed, closes the Downloads view. --> Close Downloads + + What’s New + Remove All