@@ -29,6 +29,8 @@ public sealed class MainActivity : Activity
2929
3030 private readonly Handler _handler = new ( Looper . MainLooper ! ) ;
3131 private TextView ? _permissionText ;
32+ private TextView ? _overlayPermissionText ;
33+ private Button ? _overlayToggleButton ;
3234 private TextView ? _lyricsStatusText ;
3335 private TextView ? _lineText ;
3436 private TextView ? _translationText ;
@@ -38,6 +40,8 @@ public sealed class MainActivity : Activity
3840 // 구독 해제를 위해 델리게이트 보관
3941 private Action < PlaybackViewState > ? _onStateChanged ;
4042 private Action < LyricsStatus > ? _onStatusChanged ;
43+ private Action < TranslationDisplayStatus > ? _onTranslationStatusChanged ;
44+ private LyricsStatus _lastLyricsStatus = new ( LyricsStatusKind . NoTrack ) ; // 번역상태만 바뀔 때 재렌더용
4145
4246 protected override void OnCreate ( Bundle ? savedInstanceState )
4347 {
@@ -63,6 +67,24 @@ protected override void OnCreate(Bundle? savedInstanceState)
6367 StartActivity ( new Intent ( global ::Android . Provider . Settings . ActionNotificationListenerSettings ) ) ;
6468 root . AddView ( permissionButton ) ;
6569
70+ // ---- 오버레이(다른 앱 위 표시) ----
71+ _overlayPermissionText = new TextView ( this ) ;
72+ _overlayPermissionText . SetPadding ( 0 , 32 , 0 , 0 ) ;
73+ root . AddView ( _overlayPermissionText ) ;
74+
75+ var overlayPermissionButton = new Button ( this ) { Text = "오버레이 권한 허용" } ;
76+ overlayPermissionButton . Click += ( _ , _ ) => RequestOverlayPermission ( ) ;
77+ root . AddView ( overlayPermissionButton ) ;
78+
79+ _overlayToggleButton = new Button ( this ) { Text = "가사 오버레이 켜기" } ;
80+ _overlayToggleButton . Click += ( _ , _ ) => ToggleOverlay ( ) ;
81+ root . AddView ( _overlayToggleButton ) ;
82+
83+ // ---- 번역 설정(엔진/DeepL 키/대상 언어) ----
84+ var settingsButton = new Button ( this ) { Text = "번역 설정" } ;
85+ settingsButton . Click += ( _ , _ ) => StartActivity ( new Intent ( this , typeof ( SettingsActivity ) ) ) ;
86+ root . AddView ( settingsButton ) ;
87+
6688 // ---- 가사 영역 ----
6789 _lyricsStatusText = new TextView ( this ) { Text = "가사 대기 중" } ;
6890 _lyricsStatusText . SetTextSize ( global ::Android . Util . ComplexUnitType . Sp , 13f ) ;
@@ -96,11 +118,14 @@ protected override void OnCreate(Bundle? savedInstanceState)
96118 if ( MusebaseApp . Instance is { } app )
97119 {
98120 _onStateChanged = RenderLine ;
99- _onStatusChanged = s => RenderLyricsStatus ( s ) ;
121+ _onStatusChanged = s => { _lastLyricsStatus = s ; RenderLyricsStatus ( ) ; } ;
122+ _onTranslationStatusChanged = _ => RenderLyricsStatus ( ) ; // 번역 상태만 바뀌어도 소스 옆 표기 갱신
100123 app . Coordinator . StateChanged += _onStateChanged ;
101124 app . Coordinator . StatusChanged += _onStatusChanged ;
125+ app . Coordinator . TranslationStatusChanged += _onTranslationStatusChanged ;
102126 RenderLine ( app . Coordinator . CurrentState ) ; // 초기 스냅샷 반영
103- RenderLyricsStatus ( app . LastStatus ) ;
127+ _lastLyricsStatus = app . LastStatus ;
128+ RenderLyricsStatus ( ) ;
104129 }
105130 }
106131
@@ -143,10 +168,11 @@ private void RenderLine(PlaybackViewState state)
143168 }
144169
145170 /// <summary>가사 검색 상태 문구(엔진의 구조화 상태 → 간단 한국어. i18n은 다음 단계).</summary>
146- private void RenderLyricsStatus ( LyricsStatus s )
171+ private void RenderLyricsStatus ( )
147172 {
148173 if ( _lyricsStatusText is null ) return ;
149- _lyricsStatusText . Text = s . Kind switch
174+ var s = _lastLyricsStatus ;
175+ var baseText = s . Kind switch
150176 {
151177 LyricsStatusKind . NoTrack => "재생 중인 곡 없음" ,
152178 LyricsStatusKind . HiddenByUser => "이 곡은 틀린 가사로 표시되어 숨김" ,
@@ -159,10 +185,67 @@ private void RenderLyricsStatus(LyricsStatus s)
159185 LyricsStatusKind . Edited => "가사: 사용자 편집" ,
160186 _ => "" ,
161187 } ;
188+ var suffix = ( MusebaseApp . Instance ? . Coordinator . CurrentTranslationStatus ?? TranslationDisplayStatus . None ) switch
189+ {
190+ TranslationDisplayStatus . Translating => " · 번역: 번역 중" ,
191+ TranslationDisplayStatus . Live => " · 번역: 정상 번역" ,
192+ TranslationDisplayStatus . Cache => " · 번역: 캐시 이용" ,
193+ TranslationDisplayStatus . Quota => " · 번역: 한도 초과" ,
194+ TranslationDisplayStatus . Failed => " · 번역: 실패" ,
195+ _ => "" ,
196+ } ;
197+ _lyricsStatusText . Text = baseText + suffix ;
198+ }
199+
200+ /// <summary>오버레이 그리기 권한 요청(시스템 설정의 "다른 앱 위에 표시" 화면으로 이동).</summary>
201+ private void RequestOverlayPermission ( )
202+ {
203+ if ( global ::Android . Provider . Settings . CanDrawOverlays ( this ) ) return ;
204+ StartActivity ( new Intent (
205+ global ::Android . Provider . Settings . ActionManageOverlayPermission ,
206+ global ::Android . Net . Uri . Parse ( "package:" + PackageName ) ) ) ;
207+ }
208+
209+ /// <summary>오버레이 서비스 시작/중지 토글. 권한 없으면 권한 화면으로 유도.</summary>
210+ private void ToggleOverlay ( )
211+ {
212+ if ( Services . OverlayService . IsRunning )
213+ {
214+ StopService ( new Intent ( this , typeof ( Services . OverlayService ) ) ) ;
215+ }
216+ else
217+ {
218+ if ( ! global ::Android . Provider . Settings . CanDrawOverlays ( this ) )
219+ {
220+ Toast . MakeText ( this , "먼저 '오버레이 권한 허용'을 눌러 주세요." , ToastLength . Long ) ? . Show ( ) ;
221+ RequestOverlayPermission ( ) ;
222+ return ;
223+ }
224+ var intent = new Intent ( this , typeof ( Services . OverlayService ) ) ;
225+ if ( Build . VERSION . SdkInt >= BuildVersionCodes . O ) StartForegroundService ( intent ) ;
226+ else StartService ( intent ) ;
227+ }
228+ UpdateOverlayControls ( ) ;
229+ }
230+
231+ /// <summary>오버레이 권한 상태 문구 + 토글 버튼 라벨을 현재 상태로 갱신.</summary>
232+ private void UpdateOverlayControls ( )
233+ {
234+ if ( _overlayPermissionText is not null )
235+ {
236+ _overlayPermissionText . Text = global ::Android . Provider . Settings . CanDrawOverlays ( this )
237+ ? "다른 앱 위 표시: 허용됨 ✓"
238+ : "다른 앱 위 표시: 미허용 — '오버레이 권한 허용'을 눌러 설정에서 켜 주세요." ;
239+ }
240+ if ( _overlayToggleButton is not null )
241+ _overlayToggleButton . Text = Services . OverlayService . IsRunning
242+ ? "가사 오버레이 끄기" : "가사 오버레이 켜기" ;
162243 }
163244
164245 private void RenderStatus ( )
165246 {
247+ UpdateOverlayControls ( ) ;
248+
166249 var source = MusebaseApp . Instance ? . Source ;
167250 if ( source is null || _permissionText is null || _statusText is null ) return ;
168251
@@ -204,9 +287,11 @@ protected override void OnDestroy()
204287 {
205288 if ( _onStateChanged is not null ) app . Coordinator . StateChanged -= _onStateChanged ;
206289 if ( _onStatusChanged is not null ) app . Coordinator . StatusChanged -= _onStatusChanged ;
290+ if ( _onTranslationStatusChanged is not null ) app . Coordinator . TranslationStatusChanged -= _onTranslationStatusChanged ;
207291 }
208292 _onStateChanged = null ;
209293 _onStatusChanged = null ;
294+ _onTranslationStatusChanged = null ;
210295 base . OnDestroy ( ) ;
211296 }
212297}
0 commit comments