@@ -23,6 +23,7 @@ struct AppScene: View {
2323 @StateObject private var tagManager = TagManager ( )
2424 @StateObject private var transferTracking : TransferTrackingManager
2525 @StateObject private var channelDetails = ChannelDetailsViewModel . shared
26+ @StateObject private var migrations = MigrationsService . shared
2627
2728 @State private var hideSplash = false
2829 @State private var removeSplash = false
@@ -72,6 +73,9 @@ struct AppScene: View {
7273 . onChange ( of: wallet. walletExists, perform: handleWalletExistsChange)
7374 . onChange ( of: wallet. nodeLifecycleState, perform: handleNodeLifecycleChange)
7475 . onChange ( of: scenePhase, perform: handleScenePhaseChange)
76+ . onChange ( of: migrations. isShowingMigrationLoading) { isLoading in
77+ if !isLoading { widgets. loadSavedWidgets ( ) }
78+ }
7579 . environmentObject ( app)
7680 . environmentObject ( navigation)
7781 . environmentObject ( network)
@@ -111,7 +115,9 @@ struct AppScene: View {
111115 @ViewBuilder
112116 private var mainContent : some View {
113117 ZStack {
114- if showRecoveryScreen {
118+ if migrations. isShowingMigrationLoading {
119+ migrationLoadingContent
120+ } else if showRecoveryScreen {
115121 RecoveryRouter ( )
116122 . accentColor ( . white)
117123 } else if hasCriticalUpdate {
@@ -127,6 +133,32 @@ struct AppScene: View {
127133 }
128134 }
129135
136+ @ViewBuilder
137+ private var migrationLoadingContent : some View {
138+ VStack ( spacing: 24 ) {
139+ Spacer ( )
140+
141+ ProgressView ( )
142+ . scaleEffect ( 1.5 )
143+ . tint ( . white)
144+
145+ VStack ( spacing: 8 ) {
146+ Text ( " Updating Wallet " )
147+ . font ( . system( size: 24 , weight: . semibold) )
148+ . foregroundColor ( . white)
149+
150+ Text ( " Please wait while we update the app... " )
151+ . font ( . system( size: 16 ) )
152+ . foregroundColor ( . white. opacity ( 0.7 ) )
153+ . multilineTextAlignment ( . center)
154+ }
155+
156+ Spacer ( )
157+ }
158+ . frame ( maxWidth: . infinity, maxHeight: . infinity)
159+ . background ( Color . black)
160+ }
161+
130162 @ViewBuilder
131163 private var walletContent : some View {
132164 if wallet. walletExists == true {
@@ -247,6 +279,7 @@ struct AppScene: View {
247279 @Sendable
248280 private func setupTask( ) async {
249281 do {
282+ await checkAndPerformRNMigration ( )
250283 try wallet. setWalletExistsState ( )
251284
252285 // Setup TimedSheetManager with all timed sheets
@@ -262,6 +295,43 @@ struct AppScene: View {
262295 }
263296 }
264297
298+ private func checkAndPerformRNMigration( ) async {
299+ let migrations = MigrationsService . shared
300+
301+ guard !migrations. isMigrationChecked else {
302+ Logger . debug ( " RN migration already checked, skipping " , context: " AppScene " )
303+ return
304+ }
305+
306+ guard !migrations. hasNativeWalletData ( ) else {
307+ Logger . info ( " Native wallet data exists, skipping RN migration " , context: " AppScene " )
308+ migrations. markMigrationChecked ( )
309+ return
310+ }
311+
312+ guard migrations. hasRNWalletData ( ) else {
313+ Logger . info ( " No RN wallet data found, skipping migration " , context: " AppScene " )
314+ migrations. markMigrationChecked ( )
315+ return
316+ }
317+
318+ await MainActor . run { migrations. isShowingMigrationLoading = true }
319+ Logger . info ( " RN wallet data found, starting migration... " , context: " AppScene " )
320+
321+ do {
322+ try await migrations. migrateFromReactNative ( )
323+ } catch {
324+ Logger . error ( " RN migration failed: \( error) " , context: " AppScene " )
325+ migrations. markMigrationChecked ( )
326+ await MainActor . run { migrations. isShowingMigrationLoading = false }
327+ app. toast (
328+ type: . error,
329+ title: " Migration Failed " ,
330+ description: " Please restore your wallet manually using your recovery phrase "
331+ )
332+ }
333+ }
334+
265335 private func handleNodeLifecycleChange( _ state: NodeLifecycleState ) {
266336 if state == . initializing {
267337 walletIsInitializing = true
0 commit comments