feat: add device token management and safe area utilities; enhance notification settings and chore components
This commit is contained in:
@@ -72,7 +72,6 @@ export const TASK_COLOR = {
|
||||
PENDING_REVIEW: '#8B6CE1',
|
||||
|
||||
// For the calendar
|
||||
OVERDUE: '#F03A47',
|
||||
TODAY: '#ffc107',
|
||||
TOMORROW: '#4ec1a2',
|
||||
NEXT_7_DAYS: '#00bcd4',
|
||||
|
||||
126
src/utils/FeatureToggle.js
Normal file
126
src/utils/FeatureToggle.js
Normal file
@@ -0,0 +1,126 @@
|
||||
export const FEATURES = {
|
||||
OFFLINE_MODE: 'experimental_feature_offline_mode',
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current state of a feature flag from localStorage
|
||||
* @param {string} featureKey - The feature key from FEATURES constant
|
||||
* @param {boolean} defaultValue - Default value if feature is not set (default: false)
|
||||
* @returns {boolean} - Whether the feature is enabled
|
||||
*/
|
||||
export const isFeatureEnabled = (featureKey, defaultValue = false) => {
|
||||
try {
|
||||
const value = localStorage.getItem(featureKey)
|
||||
|
||||
if (value === 'true') return true
|
||||
if (value === 'false') return false
|
||||
|
||||
if (value === null || value === undefined) return defaultValue
|
||||
|
||||
return Boolean(value)
|
||||
} catch (error) {
|
||||
console.warn(`FeatureToggle: Error reading feature "${featureKey}":`, error)
|
||||
return defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the state of a feature flag in localStorage
|
||||
* @param {string} featureKey - The feature key from FEATURES constant
|
||||
* @param {boolean} enabled - Whether to enable the feature
|
||||
*/
|
||||
export const setFeatureEnabled = (featureKey, enabled) => {
|
||||
try {
|
||||
localStorage.setItem(featureKey, enabled.toString())
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`FeatureToggle: Error setting feature "${featureKey}":`,
|
||||
error,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const toggleFeature = featureKey => {
|
||||
const currentState = isFeatureEnabled(featureKey)
|
||||
const newState = !currentState
|
||||
setFeatureEnabled(featureKey, newState)
|
||||
return newState
|
||||
}
|
||||
|
||||
export const getAllFeatureStates = () => {
|
||||
const states = {}
|
||||
Object.entries(FEATURES).forEach(([name, key]) => {
|
||||
states[name] = isFeatureEnabled(key)
|
||||
})
|
||||
return states
|
||||
}
|
||||
|
||||
export const clearAllFeatures = () => {
|
||||
try {
|
||||
Object.values(FEATURES).forEach(featureKey => {
|
||||
localStorage.removeItem(featureKey)
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('FeatureToggle: Error clearing features:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current instance is the official donetick.com service
|
||||
* @returns {Promise<boolean>} - Whether this is the official donetick.com instance
|
||||
*/
|
||||
export const isOfficialDonetickInstance = async () => {
|
||||
try {
|
||||
// Import here to avoid circular dependencies
|
||||
const { Preferences } = await import('@capacitor/preferences')
|
||||
const { API_URL } = await import('../Config')
|
||||
|
||||
// Get custom server URL from preferences
|
||||
const { value: customServerUrl } = await Preferences.get({
|
||||
key: 'customServerUrl',
|
||||
})
|
||||
|
||||
// Use custom URL if set, otherwise fall back to API_URL
|
||||
const serverUrl = customServerUrl || API_URL
|
||||
|
||||
// Check if the server URL contains donetick.com
|
||||
return serverUrl.toLowerCase().includes('donetick.com')
|
||||
} catch (error) {
|
||||
console.warn('FeatureToggle: Error checking server instance:', error)
|
||||
// Default to false for safety (self-hosted assumption)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronous version that checks based on current API manager state
|
||||
* Note: This requires apiManager to be initialized first
|
||||
* @returns {boolean} - Whether this is the official donetick.com instance
|
||||
*/
|
||||
export const isOfficialDonetickInstanceSync = () => {
|
||||
try {
|
||||
// Import here to avoid circular dependencies
|
||||
const { apiManager } = require('../utils/TokenManager')
|
||||
|
||||
const currentApiUrl = apiManager.getApiURL()
|
||||
|
||||
// Check if the API URL contains donetick.com
|
||||
return currentApiUrl.toLowerCase().includes('donetick.com')
|
||||
} catch (error) {
|
||||
console.warn('FeatureToggle: Error checking server instance (sync):', error)
|
||||
// Default to false for safety (self-hosted assumption)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Export default object for easier imports
|
||||
export default {
|
||||
FEATURES,
|
||||
isFeatureEnabled,
|
||||
setFeatureEnabled,
|
||||
toggleFeature,
|
||||
getAllFeatureStates,
|
||||
clearAllFeatures,
|
||||
isOfficialDonetickInstance,
|
||||
isOfficialDonetickInstanceSync,
|
||||
}
|
||||
89
src/utils/SafeAreaUtils.js
Normal file
89
src/utils/SafeAreaUtils.js
Normal file
@@ -0,0 +1,89 @@
|
||||
import { Capacitor } from '@capacitor/core'
|
||||
|
||||
/**
|
||||
* Utility functions for handling safe area insets consistently across the app
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get the appropriate bottom value that accounts for safe area insets
|
||||
* @param {number|string} baseBottom - The base bottom value (default: 0)
|
||||
* @param {number|string} extraPadding - Additional padding to add (default: 0)
|
||||
* @returns {string} - CSS calc() expression for bottom positioning
|
||||
*/
|
||||
export const getSafeBottom = (baseBottom = 0, extraPadding = 0) => {
|
||||
const base = typeof baseBottom === 'number' ? `${baseBottom}px` : baseBottom
|
||||
const extra =
|
||||
typeof extraPadding === 'number' ? `${extraPadding}px` : extraPadding
|
||||
|
||||
if (Capacitor.getPlatform() === 'android') {
|
||||
if (extraPadding) {
|
||||
return `calc(var(--safe-area-inset-bottom, 0px) + ${base} + ${extra})`
|
||||
}
|
||||
return `calc(var(--safe-area-inset-bottom, 0px) + ${base})`
|
||||
}
|
||||
|
||||
// For iOS and web, safe area is already handled by the system
|
||||
if (extraPadding) {
|
||||
return `calc(${base} + ${extra})`
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
/**
|
||||
* Get safe area padding for bottom elements
|
||||
* @param {number|string} extraPadding - Additional padding to add
|
||||
* @returns {string} - CSS calc() expression for padding
|
||||
*/
|
||||
export const getSafeBottomPadding = (extraPadding = 0) => {
|
||||
const extra =
|
||||
typeof extraPadding === 'number' ? `${extraPadding * 8}px` : extraPadding
|
||||
|
||||
if (Capacitor.getPlatform() === 'android') {
|
||||
if (extraPadding) {
|
||||
return `calc(var(--safe-area-inset-bottom, 0px) + ${extra})`
|
||||
}
|
||||
return `var(--safe-area-inset-bottom, 0px)`
|
||||
}
|
||||
|
||||
return extra || '0px'
|
||||
}
|
||||
|
||||
/**
|
||||
* Get safe area styles object for common bottom-positioned elements
|
||||
* @param {object} options - Configuration options
|
||||
* @param {number|string} options.bottom - Bottom position value
|
||||
* @param {number|string} options.padding - Additional padding
|
||||
* @param {'fixed'|'absolute'|'sticky'} options.position - Position type
|
||||
* @returns {object} - Style object
|
||||
*/
|
||||
export const getSafeBottomStyles = ({
|
||||
bottom = 0,
|
||||
padding = 0,
|
||||
position = 'fixed',
|
||||
} = {}) => {
|
||||
return {
|
||||
position,
|
||||
bottom: getSafeBottom(bottom, padding),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook-like function to get safe area values for use in components
|
||||
* @returns {object} - Object with safe area utility functions
|
||||
*/
|
||||
export const useSafeArea = () => {
|
||||
return {
|
||||
getSafeBottom,
|
||||
getSafeBottomPadding,
|
||||
getSafeBottomStyles,
|
||||
isAndroid: Capacitor.getPlatform() === 'android',
|
||||
isNative: Capacitor.isNativePlatform(),
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
getSafeBottom,
|
||||
getSafeBottomPadding,
|
||||
getSafeBottomStyles,
|
||||
useSafeArea,
|
||||
}
|
||||
Reference in New Issue
Block a user