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.
|
||||
|
||||
## 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.
|
||||
- Mac DNS profile hardened with `ProhibitDisablement` — the filter can no longer be toggled off in System Settings.
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
Image,
|
||||
Platform,
|
||||
ScrollView,
|
||||
Text,
|
||||
@ -33,21 +34,10 @@ import { AppHeader } from '../components/AppHeader';
|
||||
import { MagicSheet } from '../components/devices/MagicSheet';
|
||||
import { DeviceProgressBar } from '../components/devices/DeviceProgressBar';
|
||||
import { DeviceDetailSheet, type DeviceDetail } from '../components/devices/DeviceDetailSheet';
|
||||
import { deviceImage } from '../components/devices/deviceIcon';
|
||||
|
||||
// ─── 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 {
|
||||
const ms = Date.now() - new Date(iso).getTime();
|
||||
const min = Math.floor(ms / 60_000);
|
||||
@ -193,7 +183,7 @@ function MobileDeviceRow({
|
||||
function openDetail() {
|
||||
onOpenDetail({
|
||||
name: deviceName,
|
||||
icon: mobileIcon(device.platform),
|
||||
icon: deviceImage(device.platform, device.model),
|
||||
platform: device.platform,
|
||||
createdAt: device.createdAt,
|
||||
lastSeenAt: device.lastSeenAt,
|
||||
@ -229,7 +219,11 @@ function MobileDeviceRow({
|
||||
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 style={{ flex: 1, minWidth: 0 }}>
|
||||
@ -364,7 +358,7 @@ function ProtectedDeviceRow({
|
||||
function openDetail() {
|
||||
onOpenDetail({
|
||||
name: device.label,
|
||||
icon: protectedDeviceIcon(device.platform),
|
||||
icon: deviceImage(device.platform),
|
||||
platform: device.platform,
|
||||
createdAt: device.createdAt,
|
||||
lastSeenAt: null,
|
||||
@ -423,7 +417,11 @@ function ProtectedDeviceRow({
|
||||
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 style={{ flex: 1, minWidth: 0 }}>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
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 { useTranslation } from 'react-i18next';
|
||||
import { useColors } from '../../lib/theme';
|
||||
@ -14,7 +14,7 @@ const DAY_MS = 86_400_000;
|
||||
|
||||
export type DeviceDetail = {
|
||||
name: string;
|
||||
icon: React.ComponentProps<typeof Ionicons>['name'];
|
||||
icon: ImageSourcePropType;
|
||||
platform: string;
|
||||
/** ISO — Bindungs-/Verbindungsdatum */
|
||||
createdAt: string;
|
||||
@ -106,7 +106,7 @@ export function DeviceDetailSheet({
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Ionicons name={device.icon} size={24} color={colors.text} />
|
||||
<Image source={device.icon} style={{ width: 30, height: 30 }} resizeMode="contain" />
|
||||
</View>
|
||||
<View style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Image,
|
||||
Linking,
|
||||
Pressable,
|
||||
Share,
|
||||
@ -15,6 +16,7 @@ import type { ColorScheme } from '../../lib/theme';
|
||||
import { apiFetch } from '../../lib/api';
|
||||
import { useUserPlan } from '../../hooks/useUserPlan';
|
||||
import { FormSheet } from '../FormSheet';
|
||||
import { deviceImage } from './deviceIcon';
|
||||
|
||||
type PairResponse = {
|
||||
code: string;
|
||||
@ -360,14 +362,10 @@ export function MagicSheet({
|
||||
marginRight: 12,
|
||||
}}
|
||||
>
|
||||
<Ionicons
|
||||
name={
|
||||
d.model?.toLowerCase().includes('windows')
|
||||
? 'desktop-outline'
|
||||
: 'laptop-outline'
|
||||
}
|
||||
size={20}
|
||||
color={colors.text}
|
||||
<Image
|
||||
source={deviceImage(undefined, d.model)}
|
||||
style={{ width: 24, height: 24 }}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
</View>
|
||||
<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