Rebrand the entire application from "胡萝卜社区" (Carrot BBS) to "威友" (WithYou), including:
Some checks failed
Frontend CI / build-and-push-web (push) Failing after 2m51s
Frontend CI / ota-android (push) Successful in 11m16s
Frontend CI / build-android-apk (push) Failing after 12m54s

- Update app name, package name (skin.carrot.bbs → cn.qczlit.withyou), and bundle identifier
- Update API endpoints (bbs.littlelan.cn → withyou.littlelan.cn)
- Update URL scheme (carrotbbs:// → withyou://)
- Rename database from carrot_bbs to with_you
- Update CI/CD pipeline image names and artifact naming
- Add Android signing configuration for release builds
- Update all UI text and comments throughout the codebase
- Refactor registerStore to use in-memory state instead of AsyncStorage persistence
- Improve WebRTC track handling and call stream management
- Add metro platform resolution for native platform support

BREAKING app package, database, and URLs are no longer valid
This commit is contained in:
lafay
2026-04-22 16:54:51 +08:00
parent 1c3797ea7d
commit 437dab3b6e
47 changed files with 221 additions and 235 deletions

View File

@@ -1,12 +1,9 @@
/**
* 注册流程状态管理
* 使用 Zustand + AsyncStorage 实现步骤数据的持久化
* 使用 Zustand 纯内存状态,不持久化到 AsyncStorage
*/
import { create } from 'zustand';
import AsyncStorage from '@react-native-async-storage/async-storage';
const STORAGE_KEY = 'register_form_data';
export type RegisterStep = 0 | 1 | 2;
@@ -42,9 +39,6 @@ interface RegisterState {
resetRegisterData: () => void;
goToNextStep: () => void;
goToPrevStep: () => void;
// 持久化
hydrate: () => Promise<void>;
persistData: () => Promise<void>;
}
const initialFormData: RegisterFormData = {
@@ -63,31 +57,18 @@ const initialCompletedSteps: Record<RegisterStep, boolean> = {
2: false,
};
export const useRegisterStore = create<RegisterState>((set, get) => ({
export const useRegisterStore = create<RegisterState>((set) => ({
currentStep: 0,
formData: initialFormData,
countdown: 0,
completedSteps: initialCompletedSteps,
setCurrentStep: (step) => {
set({ currentStep: step });
get().persistData();
},
setCurrentStep: (step) => set({ currentStep: step }),
updateFormData: (data) =>
set((state) => {
const newFormData = { ...state.formData, ...data };
// 异步保存到 AsyncStorage
AsyncStorage.setItem(
STORAGE_KEY,
JSON.stringify({
currentStep: state.currentStep,
formData: newFormData,
completedSteps: state.completedSteps,
})
).catch(() => {});
return { formData: newFormData };
}),
set((state) => ({
formData: { ...state.formData, ...data },
})),
setCountdown: (seconds) => set({ countdown: seconds }),
@@ -97,81 +78,33 @@ export const useRegisterStore = create<RegisterState>((set, get) => ({
})),
markStepCompleted: (step) =>
set((state) => {
const newCompletedSteps = { ...state.completedSteps, [step]: true };
// 异步保存到 AsyncStorage
AsyncStorage.setItem(
STORAGE_KEY,
JSON.stringify({
currentStep: state.currentStep,
formData: state.formData,
completedSteps: newCompletedSteps,
})
).catch(() => {});
return { completedSteps: newCompletedSteps };
}),
set((state) => ({
completedSteps: { ...state.completedSteps, [step]: true },
})),
resetRegisterData: () => {
resetRegisterData: () =>
set({
currentStep: 0,
formData: initialFormData,
countdown: 0,
completedSteps: initialCompletedSteps,
});
AsyncStorage.removeItem(STORAGE_KEY).catch(() => {});
},
}),
goToNextStep: () => {
const { currentStep } = get();
if (currentStep < 2) {
const newStep = (currentStep + 1) as RegisterStep;
set({ currentStep: newStep });
get().persistData();
}
},
goToPrevStep: () => {
const { currentStep } = get();
if (currentStep > 0) {
const newStep = (currentStep - 1) as RegisterStep;
set({ currentStep: newStep });
get().persistData();
}
},
// 从 AsyncStorage 恢复数据
hydrate: async () => {
try {
const raw = await AsyncStorage.getItem(STORAGE_KEY);
if (raw) {
const data = JSON.parse(raw);
set({
currentStep: data.currentStep ?? 0,
formData: { ...initialFormData, ...data.formData },
completedSteps: { ...initialCompletedSteps, ...data.completedSteps },
});
goToNextStep: () =>
set((state) => {
if (state.currentStep < 2) {
return { currentStep: (state.currentStep + 1) as RegisterStep };
}
} catch {
// 忽略错误
}
},
return state;
}),
// 保存数据到 AsyncStorage
persistData: async () => {
const { currentStep, formData, completedSteps } = get();
try {
await AsyncStorage.setItem(
STORAGE_KEY,
JSON.stringify({
currentStep,
formData,
completedSteps,
})
);
} catch {
// 忽略错误
}
},
goToPrevStep: () =>
set((state) => {
if (state.currentStep > 0) {
return { currentStep: (state.currentStep - 1) as RegisterStep };
}
return state;
}),
}));
// 导出便捷 hooks

View File

@@ -146,13 +146,20 @@ function setupWebRTCEvents(callId: string, myUserId: string): void {
* Handle remote stream with video track detection
*/
function handleRemoteStream(stream: MediaStream): void {
callStore.setState({ peerStream: stream });
// Create a new MediaStream instance to force zustand subscribers to update
// because react-native-webrtc may add tracks to the same stream object reference
const newStream = new MediaStream();
stream.getTracks().forEach((track) => {
newStream.addTrack(track);
});
callStore.setState({ peerStream: newStream });
// Detect video tracks in remote stream
const videoTracks = stream.getVideoTracks();
const videoTracks = newStream.getVideoTracks();
const hasPeerVideo = videoTracks.length > 0 && videoTracks.some((t) => t.enabled);
console.log('[CallStore] Remote stream received, hasVideo:', hasPeerVideo, 'videoTracks:', videoTracks.length);
console.log('[CallStore] Remote stream received, hasVideo:', hasPeerVideo, 'videoTracks:', videoTracks.length, 'audioTracks:', newStream.getAudioTracks().length);
callStore.setState((s) => ({
currentCall: s.currentCall