feat(native): geräte-spezifische PNG-Icons (iphone/android/macbook/computer)
deviceImage()-Helper mappt Plattform→assets/devices/*.png; ersetzt Ionicons in Geräte-Rows, MagicSheet und Detail-Sheet. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
a95e66560d
commit
2c1eecd1f7
@ -7,6 +7,7 @@
|
|||||||
- Onboarding now sets up the full protection using the exact same guided, gated step flow as the protection screen (single source of truth) — Android: VPN → Device Administrator → Accessibility (strict order: the tamper lock has to come last, otherwise it would block the device-admin screen); iOS: App Lock → Screen Time passcode → content filter. Previously the device-admin / screen-time hardening steps only existed in the protection screen after onboarding.
|
- Onboarding now sets up the full protection using the exact same guided, gated step flow as the protection screen (single source of truth) — Android: VPN → Device Administrator → Accessibility (strict order: the tamper lock has to come last, otherwise it would block the device-admin screen); iOS: App Lock → Screen Time passcode → content filter. Previously the device-admin / screen-time hardening steps only existed in the protection screen after onboarding.
|
||||||
|
|
||||||
## Changed
|
## Changed
|
||||||
|
- Device list now shows device-specific icons (iPhone / Android / MacBook / PC) instead of generic outlines
|
||||||
- Stationary protection (Mac/Windows) now runs exclusively via Rebreak Magic — the manual offline profile download has been removed. The offline profile would have shipped the removal password in plain text inside the file (bypass risk); with Magic the lock password stays server-side and is never shown to the user.
|
- Stationary protection (Mac/Windows) now runs exclusively via Rebreak Magic — the manual offline profile download has been removed. The offline profile would have shipped the removal password in plain text inside the file (bypass risk); with Magic the lock password stays server-side and is never shown to the user.
|
||||||
- Mac DNS profile hardened with `ProhibitDisablement` — the filter can no longer be toggled off in System Settings.
|
- Mac DNS profile hardened with `ProhibitDisablement` — the filter can no longer be toggled off in System Settings.
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
Alert,
|
Alert,
|
||||||
|
Image,
|
||||||
Platform,
|
Platform,
|
||||||
ScrollView,
|
ScrollView,
|
||||||
Text,
|
Text,
|
||||||
@ -33,21 +34,10 @@ import { AppHeader } from '../components/AppHeader';
|
|||||||
import { MagicSheet } from '../components/devices/MagicSheet';
|
import { MagicSheet } from '../components/devices/MagicSheet';
|
||||||
import { DeviceProgressBar } from '../components/devices/DeviceProgressBar';
|
import { DeviceProgressBar } from '../components/devices/DeviceProgressBar';
|
||||||
import { DeviceDetailSheet, type DeviceDetail } from '../components/devices/DeviceDetailSheet';
|
import { DeviceDetailSheet, type DeviceDetail } from '../components/devices/DeviceDetailSheet';
|
||||||
|
import { deviceImage } from '../components/devices/deviceIcon';
|
||||||
|
|
||||||
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function mobileIcon(platform: string): React.ComponentProps<typeof Ionicons>['name'] {
|
|
||||||
if (platform === 'ios') return 'logo-apple';
|
|
||||||
if (platform === 'android') return 'logo-android';
|
|
||||||
return 'phone-portrait-outline';
|
|
||||||
}
|
|
||||||
|
|
||||||
function protectedDeviceIcon(platform: string): React.ComponentProps<typeof Ionicons>['name'] {
|
|
||||||
if (platform === 'mac') return 'laptop-outline';
|
|
||||||
if (platform === 'windows') return 'desktop-outline';
|
|
||||||
return 'globe-outline';
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatLastSeen(iso: string, t: (k: string, o?: any) => string): string {
|
function formatLastSeen(iso: string, t: (k: string, o?: any) => string): string {
|
||||||
const ms = Date.now() - new Date(iso).getTime();
|
const ms = Date.now() - new Date(iso).getTime();
|
||||||
const min = Math.floor(ms / 60_000);
|
const min = Math.floor(ms / 60_000);
|
||||||
@ -193,7 +183,7 @@ function MobileDeviceRow({
|
|||||||
function openDetail() {
|
function openDetail() {
|
||||||
onOpenDetail({
|
onOpenDetail({
|
||||||
name: deviceName,
|
name: deviceName,
|
||||||
icon: mobileIcon(device.platform),
|
icon: deviceImage(device.platform, device.model),
|
||||||
platform: device.platform,
|
platform: device.platform,
|
||||||
createdAt: device.createdAt,
|
createdAt: device.createdAt,
|
||||||
lastSeenAt: device.lastSeenAt,
|
lastSeenAt: device.lastSeenAt,
|
||||||
@ -229,7 +219,11 @@ function MobileDeviceRow({
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Ionicons name={mobileIcon(device.platform)} size={20} color={colors.text} />
|
<Image
|
||||||
|
source={deviceImage(device.platform, device.model)}
|
||||||
|
style={{ width: 26, height: 26 }}
|
||||||
|
resizeMode="contain"
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={{ flex: 1, minWidth: 0 }}>
|
<View style={{ flex: 1, minWidth: 0 }}>
|
||||||
@ -364,7 +358,7 @@ function ProtectedDeviceRow({
|
|||||||
function openDetail() {
|
function openDetail() {
|
||||||
onOpenDetail({
|
onOpenDetail({
|
||||||
name: device.label,
|
name: device.label,
|
||||||
icon: protectedDeviceIcon(device.platform),
|
icon: deviceImage(device.platform),
|
||||||
platform: device.platform,
|
platform: device.platform,
|
||||||
createdAt: device.createdAt,
|
createdAt: device.createdAt,
|
||||||
lastSeenAt: null,
|
lastSeenAt: null,
|
||||||
@ -423,7 +417,11 @@ function ProtectedDeviceRow({
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Ionicons name={protectedDeviceIcon(device.platform)} size={20} color={colors.text} />
|
<Image
|
||||||
|
source={deviceImage(device.platform)}
|
||||||
|
style={{ width: 26, height: 26 }}
|
||||||
|
resizeMode="contain"
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={{ flex: 1, minWidth: 0 }}>
|
<View style={{ flex: 1, minWidth: 0 }}>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { Text, View } from 'react-native';
|
import { Image, type ImageSourcePropType, Text, View } from 'react-native';
|
||||||
import { Ionicons } from '@expo/vector-icons';
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useColors } from '../../lib/theme';
|
import { useColors } from '../../lib/theme';
|
||||||
@ -14,7 +14,7 @@ const DAY_MS = 86_400_000;
|
|||||||
|
|
||||||
export type DeviceDetail = {
|
export type DeviceDetail = {
|
||||||
name: string;
|
name: string;
|
||||||
icon: React.ComponentProps<typeof Ionicons>['name'];
|
icon: ImageSourcePropType;
|
||||||
platform: string;
|
platform: string;
|
||||||
/** ISO — Bindungs-/Verbindungsdatum */
|
/** ISO — Bindungs-/Verbindungsdatum */
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
@ -106,7 +106,7 @@ export function DeviceDetailSheet({
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Ionicons name={device.icon} size={24} color={colors.text} />
|
<Image source={device.icon} style={{ width: 30, height: 30 }} resizeMode="contain" />
|
||||||
</View>
|
</View>
|
||||||
<View style={{ flex: 1, minWidth: 0 }}>
|
<View style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text
|
<Text
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
|
Image,
|
||||||
Linking,
|
Linking,
|
||||||
Pressable,
|
Pressable,
|
||||||
Share,
|
Share,
|
||||||
@ -15,6 +16,7 @@ import type { ColorScheme } from '../../lib/theme';
|
|||||||
import { apiFetch } from '../../lib/api';
|
import { apiFetch } from '../../lib/api';
|
||||||
import { useUserPlan } from '../../hooks/useUserPlan';
|
import { useUserPlan } from '../../hooks/useUserPlan';
|
||||||
import { FormSheet } from '../FormSheet';
|
import { FormSheet } from '../FormSheet';
|
||||||
|
import { deviceImage } from './deviceIcon';
|
||||||
|
|
||||||
type PairResponse = {
|
type PairResponse = {
|
||||||
code: string;
|
code: string;
|
||||||
@ -360,14 +362,10 @@ export function MagicSheet({
|
|||||||
marginRight: 12,
|
marginRight: 12,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Ionicons
|
<Image
|
||||||
name={
|
source={deviceImage(undefined, d.model)}
|
||||||
d.model?.toLowerCase().includes('windows')
|
style={{ width: 24, height: 24 }}
|
||||||
? 'desktop-outline'
|
resizeMode="contain"
|
||||||
: 'laptop-outline'
|
|
||||||
}
|
|
||||||
size={20}
|
|
||||||
color={colors.text}
|
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<View style={{ flex: 1 }}>
|
<View style={{ flex: 1 }}>
|
||||||
|
|||||||
35
apps/rebreak-native/components/devices/deviceIcon.ts
Normal file
35
apps/rebreak-native/components/devices/deviceIcon.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import type { ImageSourcePropType } from 'react-native';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Geräte-spezifische PNG-Icons (apps/rebreak-native/assets/devices).
|
||||||
|
* React Native braucht statische require()-Pfade — daher das feste Mapping.
|
||||||
|
*/
|
||||||
|
const ICONS = {
|
||||||
|
iphone: require('../../assets/devices/iphone.png'),
|
||||||
|
android: require('../../assets/devices/android.png'),
|
||||||
|
tablet: require('../../assets/devices/tablet.png'),
|
||||||
|
macbook: require('../../assets/devices/macbook.png'),
|
||||||
|
laptop: require('../../assets/devices/laptop.png'),
|
||||||
|
computer: require('../../assets/devices/computer.png'),
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wählt das passende Geräte-PNG anhand Plattform (+ optional Modell-String).
|
||||||
|
* Fällt auf `laptop` zurück (generischer Desktop).
|
||||||
|
*/
|
||||||
|
export function deviceImage(
|
||||||
|
platform?: string | null,
|
||||||
|
model?: string | null,
|
||||||
|
): ImageSourcePropType {
|
||||||
|
const p = (platform ?? '').toLowerCase();
|
||||||
|
const m = (model ?? '').toLowerCase();
|
||||||
|
|
||||||
|
if (p.startsWith('ipad') || m.includes('ipad') || p.includes('tablet')) return ICONS.tablet;
|
||||||
|
if (p.startsWith('ios') || p.startsWith('iphone') || m.includes('iphone')) return ICONS.iphone;
|
||||||
|
if (p.startsWith('android')) return ICONS.android;
|
||||||
|
if (p.startsWith('mac') || p === 'darwin' || m.includes('macbook') || m.includes('mac')) {
|
||||||
|
return ICONS.macbook;
|
||||||
|
}
|
||||||
|
if (p.startsWith('win') || m.includes('windows')) return ICONS.computer;
|
||||||
|
return ICONS.laptop;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user