diff --git a/frontend/src/stores/phone.ts b/frontend/src/stores/phone.ts index 68c1b49..ff31a8f 100644 --- a/frontend/src/stores/phone.ts +++ b/frontend/src/stores/phone.ts @@ -332,6 +332,9 @@ const defaultLocales: LocaleTree = { factoryReset: 'Erase All Content and Settings', factoryResetBody: 'This removes the account and all local data from this phone. Cloud data and the IMEI remain.', + factoryResetProgress: 'Erasing iFruit Phone', + factoryResetWarning: + 'Do not turn off this phone. This takes 60 seconds.', accountErrors: { invalid_email: 'Choose a valid 3–32 character iFruit address.', invalid_password: 'Password must be 6–64 characters.', diff --git a/frontend/src/views/apps/SettingsApp.vue b/frontend/src/views/apps/SettingsApp.vue index 3d80fb9..458ab83 100644 --- a/frontend/src/views/apps/SettingsApp.vue +++ b/frontend/src/views/apps/SettingsApp.vue @@ -32,7 +32,13 @@ import { UserRound, Volume2, } from 'lucide-vue-next' -import { computed, nextTick, ref, type ComponentPublicInstance } from 'vue' +import { + computed, + nextTick, + onBeforeUnmount, + ref, + type ComponentPublicInstance, +} from 'vue' import { PHONE_FRAME_IMAGES } from '@/config/appearance' import { PHONE_APPS } from '@/config/apps' @@ -66,6 +72,9 @@ type SettingsView = type RootToggleKey = 'airplaneMode' | 'streamerMode' type SubmenuView = Exclude +const FACTORY_RESET_DURATION_MS = 60_000 +const FACTORY_RESET_CIRCUMFERENCE = 2 * Math.PI * 48 + const phone = usePhoneStore() const account = useAccountStore() const query = ref('') @@ -82,6 +91,13 @@ const removeDeviceImei = ref('') const removeDevicePassword = ref('') const removeDeviceOpened = ref(false) const resetOpened = ref(false) +const factoryResetting = ref(false) +const factoryResetProgress = ref(0) +const factoryResetDashOffset = computed( + () => + FACTORY_RESET_CIRCUMFERENCE * (1 - factoryResetProgress.value / 100), +) +let factoryResetAnimationFrame: number | undefined const toggleRows = [ { @@ -293,8 +309,42 @@ async function confirmRemoveDevice(): Promise { async function confirmFactoryReset(): Promise { resetOpened.value = false - if (!(await account.factoryReset())) accountToast.value = accountError() + factoryResetting.value = true + factoryResetProgress.value = 0 + const startedAt = performance.now() + + const animateProgress = (now: number): void => { + factoryResetProgress.value = Math.min( + 100, + ((now - startedAt) / FACTORY_RESET_DURATION_MS) * 100, + ) + if (factoryResetProgress.value < 100) { + factoryResetAnimationFrame = requestAnimationFrame(animateProgress) + } + } + factoryResetAnimationFrame = requestAnimationFrame(animateProgress) + + const [success] = await Promise.all([ + account.factoryReset(), + new Promise((resolve) => + window.setTimeout(resolve, FACTORY_RESET_DURATION_MS), + ), + ]) + + if (factoryResetAnimationFrame !== undefined) { + cancelAnimationFrame(factoryResetAnimationFrame) + factoryResetAnimationFrame = undefined + } + factoryResetProgress.value = 100 + factoryResetting.value = false + if (!success) accountToast.value = accountError() } + +onBeforeUnmount(() => { + if (factoryResetAnimationFrame !== undefined) { + cancelAnimationFrame(factoryResetAnimationFrame) + } +}) +
+
+ + + {{ Math.floor(factoryResetProgress) }}% + +
+

+ {{ phone.t('Apps.settings.factoryResetProgress') }} +

+

+ {{ phone.t('Apps.settings.factoryResetWarning') }} +

+
+ Sky Phone - - + +