chore(release): bump to v0.2.1 / versionCode 9 — theme-crash, double-splash, dm-reopen fixes
- Android Theme parent → Theme.MaterialComponents.DayNight.NoActionBar.Bridge (fix BadgeDrawable crash in react-native-bottom-tabs after AccessibilityService toggle) - Plugin with-material-theme-android keeps theme idempotent across prebuilds - Plugin with-release-signing-android wires release signingConfig from key.properties - Splash: align native splash image with JS BrandSplash (icon.png) to eliminate double-splash flicker on app start - DM: reset partner/messages/replyTo state on userId change, disable cache for history query, switch spinner condition to isLoading||isFetching so reopens always load fresh and never show empty-state with stale partner Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
6ac6a26b9c
commit
4124463097
@ -10,6 +10,14 @@ Versioning: `version` follows SemVer, `versionCode` is monotonically increasing.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## [0.2.1] — versionCode 9 — 2026-05-16
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **Android-Crash nach AccessibilityService-Aktivierung**: BadgeDrawable in der Tab-Bar craste mit `IllegalArgumentException`, weil das Theme `AppCompat` statt `MaterialComponents` nutzte. Theme-Parent auf `Theme.MaterialComponents.DayNight.NoActionBar.Bridge` geändert via Config-Plugin — idempotent nach jedem prebuild.
|
||||||
|
- **Doppel-Splash auf Android**: Native Android-Splash und JS-Splash sahen unterschiedlich aus (zwei aufeinanderfolgende Splash-Screens). Splash-Image auf `icon.png` umgestellt, matched jetzt exakt den JS-Splash und Landing-Screen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [0.2.0] — versionCode 8 — 2026-05-16
|
## [0.2.0] — versionCode 8 — 2026-05-16
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@ -4,7 +4,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
|
|||||||
...config,
|
...config,
|
||||||
name: "ReBreak",
|
name: "ReBreak",
|
||||||
slug: "rebreak",
|
slug: "rebreak",
|
||||||
version: "0.2.0",
|
version: "0.2.1",
|
||||||
orientation: "portrait",
|
orientation: "portrait",
|
||||||
icon: "./assets/icon.png",
|
icon: "./assets/icon.png",
|
||||||
scheme: "rebreak",
|
scheme: "rebreak",
|
||||||
@ -12,7 +12,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
|
|||||||
newArchEnabled: true,
|
newArchEnabled: true,
|
||||||
|
|
||||||
splash: {
|
splash: {
|
||||||
image: "./assets/splash.png",
|
image: "./assets/icon.png",
|
||||||
resizeMode: "contain",
|
resizeMode: "contain",
|
||||||
backgroundColor: "#0f172a",
|
backgroundColor: "#0f172a",
|
||||||
},
|
},
|
||||||
@ -20,7 +20,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
|
|||||||
ios: {
|
ios: {
|
||||||
supportsTablet: true,
|
supportsTablet: true,
|
||||||
bundleIdentifier: "org.rebreak.app",
|
bundleIdentifier: "org.rebreak.app",
|
||||||
buildNumber: "8",
|
buildNumber: "9",
|
||||||
config: {
|
config: {
|
||||||
usesNonExemptEncryption: false,
|
usesNonExemptEncryption: false,
|
||||||
},
|
},
|
||||||
@ -39,7 +39,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
|
|||||||
|
|
||||||
android: {
|
android: {
|
||||||
package: "org.rebreak.app",
|
package: "org.rebreak.app",
|
||||||
versionCode: 8,
|
versionCode: 9,
|
||||||
adaptiveIcon: {
|
adaptiveIcon: {
|
||||||
// Foreground muss in der ~66%-Safe-Zone bleiben (Launcher-Mask clippt den
|
// Foreground muss in der ~66%-Safe-Zone bleiben (Launcher-Mask clippt den
|
||||||
// Außenring) → adaptive-foreground.png ist das Logo auf transparentem
|
// Außenring) → adaptive-foreground.png ist das Logo auf transparentem
|
||||||
@ -88,6 +88,10 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
|
|||||||
"./plugins/with-rebreak-protection-android",
|
"./plugins/with-rebreak-protection-android",
|
||||||
// Rive-Asset (lyra-avatar.riv) als Android raw-resource bundlen
|
// Rive-Asset (lyra-avatar.riv) als Android raw-resource bundlen
|
||||||
"./plugins/with-rive-asset-android",
|
"./plugins/with-rive-asset-android",
|
||||||
|
// MaterialComponents-Theme-Fix für BadgeDrawable in react-native-bottom-tabs
|
||||||
|
"./plugins/with-material-theme-android",
|
||||||
|
// Release-Signing-Block in build.gradle (liest android/key.properties — nicht committen)
|
||||||
|
"./plugins/with-release-signing-android",
|
||||||
],
|
],
|
||||||
|
|
||||||
experiments: {
|
experiments: {
|
||||||
|
|||||||
@ -74,6 +74,14 @@ export default function DmScreen() {
|
|||||||
);
|
);
|
||||||
const [sending, setSending] = useState(false);
|
const [sending, setSending] = useState(false);
|
||||||
|
|
||||||
|
// Reset aller conversation-spezifischen States wenn userId wechselt (Stack-Reuse)
|
||||||
|
useEffect(() => {
|
||||||
|
setMessages([]);
|
||||||
|
setPartner(null);
|
||||||
|
partnerRef.current = null;
|
||||||
|
setReplyTo(null);
|
||||||
|
}, [userId]);
|
||||||
|
|
||||||
// Lade meine User-ID
|
// Lade meine User-ID
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
supabase.auth.getSession().then(({ data }) => {
|
supabase.auth.getSession().then(({ data }) => {
|
||||||
@ -81,8 +89,8 @@ export default function DmScreen() {
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Lade DM-History
|
// Lade DM-History — staleTime:0 erzwingt immer frischen Fetch (kein Cache-Hit-Bug)
|
||||||
const { isLoading } = useQuery({
|
const { isLoading, isFetching } = useQuery({
|
||||||
queryKey: ['dm-history', userId],
|
queryKey: ['dm-history', userId],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
console.log('[dm] fetching history for partner', userId, 'me', myUserId);
|
console.log('[dm] fetching history for partner', userId, 'me', myUserId);
|
||||||
@ -124,6 +132,8 @@ export default function DmScreen() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
enabled: !!userId && !!myUserId,
|
enabled: !!userId && !!myUserId,
|
||||||
|
staleTime: 0,
|
||||||
|
gcTime: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Realtime: neue DMs vom Partner
|
// Realtime: neue DMs vom Partner
|
||||||
@ -270,7 +280,7 @@ export default function DmScreen() {
|
|||||||
>
|
>
|
||||||
<View style={{ flex: 1, backgroundColor: chatBg }}>
|
<View style={{ flex: 1, backgroundColor: chatBg }}>
|
||||||
<DmChatBackground />
|
<DmChatBackground />
|
||||||
{isLoading && messages.length === 0 ? (
|
{(isLoading || isFetching) && messages.length === 0 ? (
|
||||||
<View style={styles.loadingBox}>
|
<View style={styles.loadingBox}>
|
||||||
<ActivityIndicator color={colors.brandOrange} />
|
<ActivityIndicator color={colors.brandOrange} />
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
38
apps/rebreak-native/plugins/with-material-theme-android.js
Normal file
38
apps/rebreak-native/plugins/with-material-theme-android.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
|
/**
|
||||||
|
* Expo Config-Plugin — setzt den AppTheme-Parent auf
|
||||||
|
* Theme.MaterialComponents.DayNight.NoActionBar.Bridge damit react-native-bottom-tabs'
|
||||||
|
* BadgeDrawable nicht mit IllegalArgumentException crasht.
|
||||||
|
*
|
||||||
|
* Warum Bridge-Variante: bridged MaterialComponents ist ein Superset von AppCompat —
|
||||||
|
* alle AppCompat-Abhängigkeiten im RN-Stack bleiben kompatibel, aber Material-APIs
|
||||||
|
* (BadgeDrawable, NavigationBarMenuView etc.) funktionieren.
|
||||||
|
*
|
||||||
|
* Warum Plugin statt Hand-Edit: `prebuild --clean` regeneriert styles.xml aus dem
|
||||||
|
* Expo-AppCompat-Default. Dieses Plugin patcht den Parent-Wert nach jedem prebuild
|
||||||
|
* idempotent per withAndroidStyles (offizielles Config-Plugins-API).
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { withAndroidStyles } = require('@expo/config-plugins');
|
||||||
|
|
||||||
|
const MATERIAL_THEME = 'Theme.MaterialComponents.DayNight.NoActionBar.Bridge';
|
||||||
|
|
||||||
|
function withMaterialThemeAndroid(config) {
|
||||||
|
return withAndroidStyles(config, (cfg) => {
|
||||||
|
const styles = cfg.modResults.resources.style;
|
||||||
|
|
||||||
|
if (!Array.isArray(styles)) return cfg;
|
||||||
|
|
||||||
|
const appTheme = styles.find(
|
||||||
|
(s) => s.$ && s.$['name'] === 'AppTheme',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (appTheme) {
|
||||||
|
appTheme.$['parent'] = MATERIAL_THEME;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = withMaterialThemeAndroid;
|
||||||
82
apps/rebreak-native/plugins/with-release-signing-android.js
Normal file
82
apps/rebreak-native/plugins/with-release-signing-android.js
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
|
/**
|
||||||
|
* Expo Config-Plugin — fügt einen Release-Signing-Block in build.gradle ein,
|
||||||
|
* der key.properties aus android/key.properties liest.
|
||||||
|
*
|
||||||
|
* Warum Plugin statt Hand-Edit: `prebuild --clean` regeneriert build.gradle und
|
||||||
|
* überschreibt manuelle Änderungen. Dieses Plugin patcht den Signing-Block nach
|
||||||
|
* jedem prebuild idempotent via withAppBuildGradle.
|
||||||
|
*
|
||||||
|
* key.properties muss NACH prebuild in android/ abgelegt werden (nicht committen).
|
||||||
|
* Keystore muss in android/app/rebreak-release.keystore liegen (nicht committen).
|
||||||
|
*
|
||||||
|
* Format key.properties (in android/ — wird von rootProject.file() gefunden):
|
||||||
|
* storePassword=<password>
|
||||||
|
* keyPassword=<password>
|
||||||
|
* keyAlias=rebreak
|
||||||
|
* storeFile=rebreak-release.keystore
|
||||||
|
*
|
||||||
|
* Hinweis: storeFile ist relativ zu android/app/ (Gradle-Modul-Root), also
|
||||||
|
* Keystore in android/app/rebreak-release.keystore ablegen.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { withAppBuildGradle } = require('@expo/config-plugins');
|
||||||
|
|
||||||
|
function withReleaseSigningAndroid(config) {
|
||||||
|
return withAppBuildGradle(config, (cfg) => {
|
||||||
|
let gradle = cfg.modResults.contents;
|
||||||
|
|
||||||
|
// Idempotenz: Plugin bereits angewandt
|
||||||
|
if (gradle.includes('REBREAK_RELEASE_SIGNING')) {
|
||||||
|
return cfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Properties-Loader vor android { Block einfügen
|
||||||
|
const propertiesLoader = `
|
||||||
|
// REBREAK_RELEASE_SIGNING — injected by with-release-signing-android plugin
|
||||||
|
def keystorePropertiesFile = rootProject.file("key.properties")
|
||||||
|
def keystoreProperties = new Properties()
|
||||||
|
if (keystorePropertiesFile.exists()) {
|
||||||
|
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
gradle = gradle.replace(/\nandroid \{/, `${propertiesLoader}\nandroid {`);
|
||||||
|
|
||||||
|
// 2. Release-signingConfig in signingConfigs-Block einfügen (nach debug-Block)
|
||||||
|
// Expo generiert: signingConfigs { debug { ... } }
|
||||||
|
// Wir fügen release { ... } danach ein, direkt vor der schließenden }
|
||||||
|
const releaseSigningConfig = `
|
||||||
|
release {
|
||||||
|
if (keystorePropertiesFile.exists()) {
|
||||||
|
keyAlias keystoreProperties['keyAlias']
|
||||||
|
keyPassword keystoreProperties['keyPassword']
|
||||||
|
storeFile file(keystoreProperties['storeFile'])
|
||||||
|
storePassword keystoreProperties['storePassword']
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
// Findet den signingConfigs-Block und fügt release vor der letzten } ein
|
||||||
|
gradle = gradle.replace(
|
||||||
|
/(signingConfigs \{[\s\S]*?)( \}\n)( buildTypes)/,
|
||||||
|
`$1${releaseSigningConfig}\n }\n $3`
|
||||||
|
);
|
||||||
|
|
||||||
|
// 3. Im release buildType: signingConfig.debug → signingConfig.release
|
||||||
|
// Der release-Block liegt nach dem debug-Block. Wir ersetzen die zweite
|
||||||
|
// Occurrence von signingConfig signingConfigs.debug (im release-Block).
|
||||||
|
const parts = gradle.split('signingConfig signingConfigs.debug');
|
||||||
|
if (parts.length >= 3) {
|
||||||
|
// parts[0] = vor debug-Block, parts[1] = zwischen debug und release, parts[2] = nach release
|
||||||
|
gradle = parts[0]
|
||||||
|
+ 'signingConfig signingConfigs.debug'
|
||||||
|
+ parts[1]
|
||||||
|
+ 'signingConfig signingConfigs.release'
|
||||||
|
+ parts.slice(2).join('signingConfig signingConfigs.debug');
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.modResults.contents = gradle;
|
||||||
|
return cfg;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = withReleaseSigningAndroid;
|
||||||
Loading…
x
Reference in New Issue
Block a user