From 9c8fbe831fe69f44eb18e1c58e0856c58276bb1f Mon Sep 17 00:00:00 2001 From: Henry Heino <46334387+personalizedrefrigerator@users.noreply.github.com> Date: Tue, 18 Jul 2023 06:46:11 -0700 Subject: [PATCH] Mobile: Resolves #8490: Add option to autodetect theme (#8498) --- .eslintignore | 1 + .gitignore | 1 + .../app-mobile/components/FolderPicker.tsx | 2 +- .../app-mobile/components/ScreenHeader.tsx | 5 ++- packages/app-mobile/ios/Joplin/Info.plist | 2 +- packages/app-mobile/root.tsx | 22 ++++++++++- packages/app-mobile/utils/autodetectTheme.ts | 38 +++++++++++++++++++ packages/app-mobile/utils/types.ts | 1 + packages/lib/models/Setting.ts | 6 +-- 9 files changed, 70 insertions(+), 8 deletions(-) create mode 100644 packages/app-mobile/utils/autodetectTheme.ts diff --git a/.eslintignore b/.eslintignore index e166bef5b..6c095ee64 100644 --- a/.eslintignore +++ b/.eslintignore @@ -443,6 +443,7 @@ packages/app-mobile/tools/buildInjectedJs.js packages/app-mobile/utils/ShareExtension.js packages/app-mobile/utils/ShareUtils.js packages/app-mobile/utils/TlsUtils.js +packages/app-mobile/utils/autodetectTheme.js packages/app-mobile/utils/checkPermissions.js packages/app-mobile/utils/createRootStyle.js packages/app-mobile/utils/debounce.js diff --git a/.gitignore b/.gitignore index 79528ffc6..2778ecc77 100644 --- a/.gitignore +++ b/.gitignore @@ -428,6 +428,7 @@ packages/app-mobile/tools/buildInjectedJs.js packages/app-mobile/utils/ShareExtension.js packages/app-mobile/utils/ShareUtils.js packages/app-mobile/utils/TlsUtils.js +packages/app-mobile/utils/autodetectTheme.js packages/app-mobile/utils/checkPermissions.js packages/app-mobile/utils/createRootStyle.js packages/app-mobile/utils/debounce.js diff --git a/packages/app-mobile/components/FolderPicker.tsx b/packages/app-mobile/components/FolderPicker.tsx index 522bfbaf2..947ae2475 100644 --- a/packages/app-mobile/components/FolderPicker.tsx +++ b/packages/app-mobile/components/FolderPicker.tsx @@ -15,7 +15,7 @@ interface FolderPickerProps { folders: FolderEntity[]; placeholder?: string; darkText?: boolean; - themeId?: string; + themeId?: number; } diff --git a/packages/app-mobile/components/ScreenHeader.tsx b/packages/app-mobile/components/ScreenHeader.tsx index 76df8ef25..bb26aa167 100644 --- a/packages/app-mobile/components/ScreenHeader.tsx +++ b/packages/app-mobile/components/ScreenHeader.tsx @@ -86,6 +86,7 @@ interface ScreenHeaderProps { shouldUpgradeSyncTarget?: boolean; showShouldUpgradeSyncTargetMessage?: boolean; + themeId: number; } interface ScreenHeaderState { @@ -100,7 +101,7 @@ class ScreenHeaderComponent extends PureComponentUIInterfaceOrientationPortraitUpsideDown UIUserInterfaceStyle - Light + Automatic UIViewControllerBasedStatusBarAppearance NSFaceIDUsageDescription diff --git a/packages/app-mobile/root.tsx b/packages/app-mobile/root.tsx index b690f18a6..0bf3c687a 100644 --- a/packages/app-mobile/root.tsx +++ b/packages/app-mobile/root.tsx @@ -29,7 +29,7 @@ import SyncTargetOneDrive from '@joplin/lib/SyncTargetOneDrive'; import initProfile from '@joplin/lib/services/profileConfig/initProfile'; const VersionInfo = require('react-native-version-info').default; const { Keyboard, BackHandler, View, StatusBar, Platform, Dimensions } = require('react-native'); -import { AppState as RNAppState, EmitterSubscription, Linking, NativeEventSubscription } from 'react-native'; +import { AppState as RNAppState, EmitterSubscription, Linking, NativeEventSubscription, Appearance } from 'react-native'; import getResponsiveValue from './components/getResponsiveValue'; import NetInfo from '@react-native-community/netinfo'; const DropdownAlert = require('react-native-dropdownalert').default; @@ -118,6 +118,7 @@ import { getCurrentProfile } from '@joplin/lib/services/profileConfig'; import { getDatabaseName, getProfilesRootDir, getResourceDir, setDispatch } from './services/profiles'; import { ReactNode } from 'react'; import { parseShareCache } from '@joplin/lib/services/share/reducer'; +import autodetectTheme, { onSystemColorSchemeChange } from './utils/autodetectTheme'; type SideMenuPosition = 'left' | 'right'; @@ -186,6 +187,14 @@ const generalMiddleware = (store: any) => (next: any) => async (action: any) => void reg.scheduleSync(null, null, true); } + if ( + action.type === 'AUTODETECT_THEME' + || action.type === 'SETTING_UPDATE_ALL' + || (action.type === 'SETTING_UPDATE_ONE' && ['themeAutoDetect', 'preferredLightTheme', 'preferredDarkTheme'].includes(action.key)) + ) { + autodetectTheme(); + } + if (action.type === 'NAV_GO' && action.routeName === 'Notes') { Setting.setValue('activeFolderId', newState.selectedFolderId); } @@ -712,6 +721,7 @@ class AppComponent extends React.Component { private urlOpenListener_: EmitterSubscription|null = null; private appStateChangeListener_: NativeEventSubscription|null = null; + private themeChangeListener_: NativeEventSubscription|null = null; public constructor() { super(); @@ -849,6 +859,11 @@ class AppComponent extends React.Component { this.appStateChangeListener_ = RNAppState.addEventListener('change', this.onAppStateChange_); this.unsubscribeScreenWidthChangeHandler_ = Dimensions.addEventListener('change', this.handleScreenWidthChange_); + this.themeChangeListener_ = Appearance.addChangeListener( + ({ colorScheme }) => onSystemColorSchemeChange(colorScheme) + ); + onSystemColorSchemeChange(Appearance.getColorScheme()); + setupQuickActions(this.props.dispatch, this.props.selectedFolderId); await setupNotifications(this.props.dispatch); @@ -868,6 +883,11 @@ class AppComponent extends React.Component { this.urlOpenListener_ = null; } + if (this.themeChangeListener_) { + this.themeChangeListener_.remove(); + this.themeChangeListener_ = null; + } + if (this.unsubscribeScreenWidthChangeHandler_) { this.unsubscribeScreenWidthChangeHandler_.remove(); this.unsubscribeScreenWidthChangeHandler_ = null; diff --git a/packages/app-mobile/utils/autodetectTheme.ts b/packages/app-mobile/utils/autodetectTheme.ts new file mode 100644 index 000000000..d0ff9e3df --- /dev/null +++ b/packages/app-mobile/utils/autodetectTheme.ts @@ -0,0 +1,38 @@ +import Logger from '@joplin/lib/Logger'; +import Setting from '@joplin/lib/models/Setting'; +import { Appearance, ColorSchemeName } from 'react-native'; + +const logger = Logger.create('autodetectTheme'); + +let systemColorScheme: ColorSchemeName|null = null; + +// We export an `onThemeChange`, rather than using `Appearance.getColorScheme()` directly +// to work around https://github.com/facebook/react-native/issues/36061. On some devices, +// `Appearance.getColorScheme()` returns incorrect values. +export const onSystemColorSchemeChange = (newColorScheme: ColorSchemeName|null) => { + if (systemColorScheme !== newColorScheme) { + systemColorScheme = newColorScheme; + autodetectTheme(); + } +}; + +const autodetectTheme = () => { + if (!Setting.value('themeAutoDetect')) { + logger.info('Theme autodetect disabled, not switching theme to match system.'); + return; + } + + const colorScheme = systemColorScheme; + logger.debug( + 'Autodetecting theme. getColorScheme returns', Appearance.getColorScheme(), + 'and the expected theme is', systemColorScheme + ); + + if (colorScheme === 'dark') { + Setting.setValue('theme', Setting.value('preferredDarkTheme')); + } else { + Setting.setValue('theme', Setting.value('preferredLightTheme')); + } +}; + +export default autodetectTheme; diff --git a/packages/app-mobile/utils/types.ts b/packages/app-mobile/utils/types.ts index fa3fbef1a..5f3601bc9 100644 --- a/packages/app-mobile/utils/types.ts +++ b/packages/app-mobile/utils/types.ts @@ -6,4 +6,5 @@ export interface AppState extends State { route: any; smartFilterId: string; noteSideMenuOptions: any; + themeId: number; } diff --git a/packages/lib/models/Setting.ts b/packages/lib/models/Setting.ts index e16c66968..e5e577667 100644 --- a/packages/lib/models/Setting.ts +++ b/packages/lib/models/Setting.ts @@ -840,7 +840,7 @@ class Setting extends BaseModel { value: false, type: SettingItemType.Bool, section: 'appearance', - appTypes: [AppType.Desktop], + appTypes: [AppType.Mobile, AppType.Desktop], public: true, label: () => _('Automatically switch theme to match system theme'), storage: SettingStorage.File, @@ -854,7 +854,7 @@ class Setting extends BaseModel { show: (settings) => { return settings['themeAutoDetect']; }, - appTypes: [AppType.Desktop], + appTypes: [AppType.Mobile, AppType.Desktop], isEnum: true, label: () => _('Preferred light theme'), section: 'appearance', @@ -870,7 +870,7 @@ class Setting extends BaseModel { show: (settings) => { return settings['themeAutoDetect']; }, - appTypes: [AppType.Desktop], + appTypes: [AppType.Mobile, AppType.Desktop], isEnum: true, label: () => _('Preferred dark theme'), section: 'appearance',