Files
android-ftp-server/app/src/main/res/layout/activity_main.xml
Gyubin Han 9594233c8e Feat: 사용자 설정 기능 구현 (포트, 루트 디렉토리)
- FTPConfig 클래스 생성: SharedPreferences 기반 설정 관리
  * 포트 번호 저장/로드
  * 루트 디렉토리 URI 및 경로 저장/로드
- MainActivity UI 업데이트
  * ScrollView로 전체 레이아웃 감싸기
  * 설정 섹션 추가 (포트 입력, 디렉토리 선택)
  * Storage Access Framework로 디렉토리 선택 기능
  * 설정 유효성 검사 (포트 범위: 1024-65535)
- MainActivity 로직 업데이트
  * ActivityResultLauncher로 디렉토리 선택
  * takePersistableUriPermission으로 지속적 권한 획득
  * 설정 로드 및 저장 기능
  * 서버 시작 전 루트 디렉토리 필수 체크
- FTPService 수정
  * Intent로 포트 및 루트 디렉토리 전달받음
  * FTPServer에 설정값 전달
- FTPServer 생성자 확장
  * 포트 및 루트 디렉토리 파라미터 추가
  * FTPSession에 설정값 전달
- FTPSession 생성자 확장
  * 루트 디렉토리 파라미터 추가
  * FTPFileSystem에 전달
- FTPFileSystem 생성자 확장
  * 사용자 지정 루트 디렉토리 지원
  * null일 경우 기본 디렉토리 사용
  * 경로 유효성 검증

이제 사용자가 원하는 디렉토리를 FTP 루트로 설정 가능
Android 11+ Scoped Storage 문제 해결

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-01 04:12:28 +09:00

159 lines
5.9 KiB
XML

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<TextView
android:id="@+id/titleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Android FTP Server"
android:textSize="24sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="32dp" />
<TextView
android:id="@+id/statusTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Server Status: Stopped"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/titleTextView"
android:layout_marginTop="24dp" />
<TextView
android:id="@+id/portTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Port: 2121"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/statusTextView"
android:layout_marginTop="8dp" />
<Button
android:id="@+id/startButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Start Server"
android:minWidth="150dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/portTextView"
android:layout_marginTop="32dp" />
<Button
android:id="@+id/stopButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Stop Server"
android:minWidth="150dp"
android:enabled="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/startButton"
android:layout_marginTop="16dp" />
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#CCCCCC"
android:layout_marginTop="32dp"
app:layout_constraintTop_toBottomOf="@id/stopButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:id="@+id/settingsTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Settings"
android:textSize="20sp"
android:textStyle="bold"
android:layout_marginTop="24dp"
app:layout_constraintTop_toBottomOf="@id/divider"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/portLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Port:"
android:textSize="16sp"
android:layout_marginTop="24dp"
app:layout_constraintTop_toBottomOf="@id/settingsTitle"
app:layout_constraintStart_toStartOf="parent" />
<EditText
android:id="@+id/portEditText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:inputType="number"
android:hint="2121"
android:layout_marginStart="16dp"
app:layout_constraintBaseline_toBaselineOf="@id/portLabel"
app:layout_constraintStart_toEndOf="@id/portLabel"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:id="@+id/rootDirLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Root Directory:"
android:textSize="16sp"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@id/portLabel"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/rootDirPathTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Not selected"
android:textSize="14sp"
android:layout_marginTop="8dp"
android:padding="8dp"
android:background="@android:color/darker_gray"
android:ellipsize="start"
android:singleLine="true"
app:layout_constraintTop_toBottomOf="@id/rootDirLabel"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<Button
android:id="@+id/selectDirButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Select Directory"
android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="@id/rootDirPathTextView"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/saveSettingsButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Save Settings"
android:minWidth="150dp"
android:layout_marginTop="24dp"
app:layout_constraintTop_toBottomOf="@id/selectDirButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>